Skip to content

Commit 50201f7

Browse files
committed
chore: begin moving paths to nest
1 parent 0e008aa commit 50201f7

33 files changed

+421
-253
lines changed

api/src/config/paths.config.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { join, resolve as resolvePath } from 'path';
3+
4+
@Injectable()
5+
export class PathsConfig {
6+
private static instance: PathsConfig;
7+
8+
readonly core = import.meta.dirname;
9+
readonly unraidApiBase = '/usr/local/unraid-api/';
10+
readonly unraidData = resolvePath(
11+
process.env.PATHS_UNRAID_DATA ?? '/boot/config/plugins/dynamix.my.servers/data/'
12+
);
13+
readonly dockerAutostart = '/var/lib/docker/unraid-autostart';
14+
readonly dockerSocket = '/var/run/docker.sock';
15+
readonly parityChecks = '/boot/config/parity-checks.log';
16+
readonly htpasswd = '/etc/nginx/htpasswd';
17+
readonly emhttpdSocket = '/var/run/emhttpd.socket';
18+
readonly states = resolvePath(process.env.PATHS_STATES ?? '/usr/local/emhttp/state/');
19+
readonly dynamixBase = resolvePath(
20+
process.env.PATHS_DYNAMIX_BASE ?? '/boot/config/plugins/dynamix/'
21+
);
22+
23+
/**
24+
* Plugins have a default config and, optionally, a user-customized config.
25+
* You have to merge them to resolve a the correct config.
26+
*
27+
* i.e. the plugin author can update or change defaults without breaking user configs
28+
*
29+
* Thus, we've described this plugin's config paths as a list. The order matters!
30+
* Config data in earlier paths will be overwritten by configs from later paths.
31+
*
32+
* See [the original PHP implementation.](https://github.com/unraid/webgui/blob/95c6913c62e64314b985e08222feb3543113b2ec/emhttp/plugins/dynamix/include/Wrappers.php#L42)
33+
*
34+
* Here, the first path in the list is the default config.
35+
* The second is the user-customized config.
36+
*/
37+
readonly dynamixConfig = [
38+
resolvePath(
39+
process.env.PATHS_DYNAMIX_CONFIG_DEFAULT ??
40+
'/usr/local/emhttp/plugins/dynamix/default.cfg'
41+
),
42+
resolvePath(
43+
process.env.PATHS_DYNAMIX_CONFIG ?? '/boot/config/plugins/dynamix/dynamix.cfg'
44+
),
45+
];
46+
47+
readonly myserversBase = '/boot/config/plugins/dynamix.my.servers/';
48+
readonly myserversConfig = resolvePath(
49+
process.env.PATHS_MY_SERVERS_CONFIG ??
50+
'/boot/config/plugins/dynamix.my.servers/myservers.cfg'
51+
);
52+
readonly myserversConfigStates = join(
53+
resolvePath(process.env.PATHS_STATES ?? '/usr/local/emhttp/state/'),
54+
'myservers.cfg'
55+
);
56+
readonly myserversEnv = '/boot/config/plugins/dynamix.my.servers/env';
57+
readonly myserversKeepalive =
58+
process.env.PATHS_MY_SERVERS_FB ??
59+
'/boot/config/plugins/dynamix.my.servers/fb_keepalive';
60+
readonly keyfileBase = resolvePath(process.env.PATHS_KEYFILE_BASE ?? '/boot/config');
61+
readonly machineId = resolvePath(process.env.PATHS_MACHINE_ID ?? '/var/lib/dbus/machine-id');
62+
readonly logBase = resolvePath('/var/log/unraid-api/');
63+
readonly unraidLogBase = resolvePath('/var/log/');
64+
readonly varRun = '/var/run';
65+
readonly authSessions = process.env.PATHS_AUTH_SESSIONS ?? '/var/lib/php';
66+
readonly authKeys = resolvePath(
67+
process.env.PATHS_AUTH_KEY ?? '/boot/config/plugins/dynamix.my.servers/keys'
68+
);
69+
70+
// Singleton access
71+
static getInstance(): PathsConfig {
72+
if (!PathsConfig.instance) {
73+
PathsConfig.instance = new PathsConfig();
74+
}
75+
return PathsConfig.instance;
76+
}
77+
}

api/src/config/paths.module.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Global, Module } from '@nestjs/common';
2+
import { PathsConfig } from './paths.config.js';
3+
4+
@Global()
5+
@Module({
6+
providers: [PathsConfig],
7+
exports: [PathsConfig],
8+
})
9+
export class PathsModule {}

api/src/core/modules/docker/get-docker-containers.ts

Lines changed: 28 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import fs from 'fs';
1+
import { promises as fs } from 'fs';
22

33
import camelCaseKeys from 'camelcase-keys';
44

@@ -9,6 +9,7 @@ import { catchHandlers } from '@app/core/utils/misc/catch-handlers.js';
99
import { ContainerPortType, ContainerState } from '@app/graphql/generated/api/types.js';
1010
import { getters, store } from '@app/store/index.js';
1111
import { updateDockerState } from '@app/store/modules/docker.js';
12+
import { PathsConfig } from '../../../config/paths.config.js';
1213

1314
export interface ContainerListingOptions {
1415
useCache?: boolean;
@@ -18,9 +19,7 @@ export interface ContainerListingOptions {
1819
* Get all Docker containers.
1920
* @returns All the in/active Docker containers on the system.
2021
*/
21-
export const getDockerContainers = async (
22-
{ useCache }: ContainerListingOptions = { useCache: true }
23-
): Promise<Array<DockerContainer>> => {
22+
export const getDockerContainers = async ({ useCache = true }: ContainerListingOptions = {}): Promise<DockerContainer[]> => {
2423
const dockerState = getters.docker();
2524
if (useCache && dockerState.containers) {
2625
dockerLogger.trace('Using docker container cache');
@@ -29,57 +28,31 @@ export const getDockerContainers = async (
2928

3029
dockerLogger.trace('Skipping docker container cache');
3130

32-
/**
33-
* Docker auto start file
34-
*
35-
* @note Doesn't exist if array is offline.
36-
* @see https://github.com/limetech/webgui/issues/502#issue-480992547
37-
*/
38-
const autoStartFile = await fs.promises
39-
.readFile(getters.paths()['docker-autostart'], 'utf8')
40-
.then((file) => file.toString())
41-
.catch(() => '');
42-
const autoStarts = autoStartFile.split('\n');
43-
const rawContainers = await docker
44-
.listContainers({
45-
all: true,
46-
size: true,
47-
})
48-
// If docker throws an error return no containers
49-
.catch(catchHandlers.docker);
50-
51-
// Cleanup container object
52-
const containers: Array<DockerContainer> = rawContainers.map((container) => {
53-
const names = container.Names[0];
54-
const containerData: DockerContainer = camelCaseKeys<DockerContainer>(
55-
{
56-
labels: container.Labels ?? {},
57-
sizeRootFs: undefined,
58-
imageId: container.ImageID,
59-
state:
60-
typeof container.State === 'string'
61-
? (ContainerState[container.State.toUpperCase()] ?? ContainerState.EXITED)
62-
: ContainerState.EXITED,
63-
autoStart: autoStarts.includes(names.split('/')[1]),
64-
ports: container.Ports.map<ContainerPort>((port) => ({
65-
...port,
66-
type: ContainerPortType[port.Type.toUpperCase()],
67-
})),
68-
command: container.Command,
69-
created: container.Created,
70-
mounts: container.Mounts,
71-
networkSettings: container.NetworkSettings,
72-
hostConfig: {
73-
networkMode: container.HostConfig.NetworkMode,
74-
},
75-
id: container.Id,
76-
image: container.Image,
77-
status: container.Status,
78-
},
79-
{ deep: true }
80-
);
81-
return containerData;
82-
});
31+
const paths = PathsConfig.getInstance();
32+
const autostartFile = await fs.readFile(paths.dockerAutostart, 'utf8').catch(() => '');
33+
const autoStarts = autostartFile.split('\n');
34+
const rawContainers = await docker.listContainers({ all: true }).catch(catchHandlers.docker);
35+
36+
const containers: DockerContainer[] = rawContainers.map((container) => ({
37+
id: container.Id,
38+
image: container.Image,
39+
imageId: container.ImageID,
40+
command: container.Command,
41+
created: container.Created,
42+
state: ContainerState[container.State.toUpperCase() as keyof typeof ContainerState],
43+
status: container.Status,
44+
ports: container.Ports.map((port) => ({
45+
...port,
46+
type: ContainerPortType[port.Type.toUpperCase() as keyof typeof ContainerPortType],
47+
})) as ContainerPort[],
48+
autoStart: autoStarts.includes(container.Names[0].split('/')[1]),
49+
labels: container.Labels ?? {},
50+
mounts: container.Mounts,
51+
networkSettings: container.NetworkSettings,
52+
hostConfig: {
53+
networkMode: container.HostConfig.NetworkMode,
54+
},
55+
}));
8356

8457
// Get all of the current containers
8558
const installed = containers.length;

api/src/core/modules/get-parity-history.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { FileMissingError } from '@app/core/errors/file-missing-error.js';
66
import { type CoreContext, type CoreResult } from '@app/core/types/index.js';
77
import { ensurePermission } from '@app/core/utils/permissions/ensure-permission.js';
88
import { getters } from '@app/store/index.js';
9+
import { PathsConfig } from '../../config/paths.config.js';
910

1011
/**
1112
* Get parity history.
@@ -21,7 +22,8 @@ export const getParityHistory = async (context: CoreContext): Promise<CoreResult
2122
possession: 'any',
2223
});
2324

24-
const historyFilePath = getters.paths()['parity-checks'];
25+
const paths = PathsConfig.getInstance();
26+
const historyFilePath = paths.parityChecks;
2527
const history = await fs.readFile(historyFilePath).catch(() => {
2628
throw new FileMissingError(historyFilePath);
2729
});

api/src/core/types/state.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export enum StateFileKey {
2+
DISPLAY = 'display',
3+
DISKS = 'disks',
4+
DOCKER = 'docker',
5+
EMHTTP = 'emhttp',
6+
IDENT = 'ident',
7+
SHARES = 'shares',
8+
SLOTS = 'slots',
9+
USERS = 'users',
10+
DEVICES = 'devices',
11+
NETWORK = 'network',
12+
NFS = 'nfs',
13+
NGINX = 'nginx',
14+
SMB = 'smb',
15+
VAR = 'var',
16+
SEC = 'sec',
17+
NOTIFICATION = 'notification',
18+
}
Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import Docker from 'dockerode';
2+
import { PathsConfig } from '../../../config/paths.config.js';
23

3-
const socketPath = '/var/run/docker.sock';
4-
const client = new Docker({
5-
socketPath,
6-
});
4+
const createDockerClient = () => {
5+
const paths = PathsConfig.getInstance();
6+
const socketPath = paths.dockerSocket;
7+
return new Docker({
8+
socketPath,
9+
});
10+
};
711

812
/**
913
* Docker client
1014
*/
11-
export const docker = client;
15+
export const docker = createDockerClient();
16+
export { createDockerClient };

api/src/core/utils/clients/emcmd.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import { logger } from '@app/core/log.js';
55
import { type LooseObject } from '@app/core/types/index.js';
66
import { DRY_RUN } from '@app/environment.js';
77
import { getters } from '@app/store/index.js';
8+
import { PathsConfig } from '../../../config/paths.config.js';
89

910
/**
1011
* Run a command with emcmd.
1112
*/
1213
export const emcmd = async (commands: LooseObject) => {
13-
const socketPath = getters.paths()['emhttpd-socket'];
14+
const paths = PathsConfig.getInstance();
15+
const socketPath = paths.emhttpdSocket;
1416
const { csrfToken } = getters.emhttp().var;
1517

1618
const url = `http://unix:${socketPath}:/update.htm`;
@@ -39,3 +41,9 @@ export const emcmd = async (commands: LooseObject) => {
3941
throw error;
4042
});
4143
};
44+
45+
export const createEmcmdClient = () => {
46+
const paths = PathsConfig.getInstance();
47+
const socketPath = paths.emhttpdSocket;
48+
// Rest of implementation
49+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { PathsConfig } from '../../../config/paths.config.js';
2+
3+
export const createEmhttpdClient = () => {
4+
const paths = PathsConfig.getInstance();
5+
const socketPath = paths.emhttpdSocket;
6+
// Rest of implementation
7+
};

api/src/core/utils/clients/ssh.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { PathsConfig } from '../../../config/paths.config.js';
2+
3+
export const createSshClient = () => {
4+
const paths = PathsConfig.getInstance();
5+
const keyPath = paths.keyfileBase;
6+
// Rest of implementation
7+
};

api/src/core/utils/misc/catch-handlers.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { AppError } from '@app/core/errors/app-error.js';
22
import { getters } from '@app/store/index.js';
3+
import { PathsConfig } from '../../../config/paths.config.js';
34

45
interface DockerError extends NodeJS.ErrnoException {
56
address: string;
@@ -10,7 +11,8 @@ interface DockerError extends NodeJS.ErrnoException {
1011
*/
1112
export const catchHandlers = {
1213
docker(error: DockerError) {
13-
const socketPath = getters.paths()['docker-socket'];
14+
const paths = PathsConfig.getInstance();
15+
const socketPath = paths.dockerSocket;
1416

1517
// Throw custom error for docker socket missing
1618
if (error.code === 'ENOENT' && error.address === socketPath) {
@@ -27,3 +29,9 @@ export const catchHandlers = {
2729
throw error;
2830
},
2931
};
32+
33+
export const handleDockerError = (error: Error) => {
34+
const paths = PathsConfig.getInstance();
35+
const socketPath = paths.dockerSocket;
36+
// Rest of implementation
37+
};

0 commit comments

Comments
 (0)