From db1623f43f2b2f22abb5bb9fee1b73ce49b3e828 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Thu, 3 Oct 2024 15:28:36 -0400 Subject: [PATCH] refactor(server): worker env (#13160) --- server/bin/immich-healthcheck | 2 +- server/package.json | 1 - server/src/{utils => bin}/healthcheck.ts | 8 +-- server/src/enum.ts | 5 ++ server/src/interfaces/config.interface.ts | 4 +- server/src/main.ts | 52 +++++++++---------- .../config.repository.spec.ts} | 4 +- server/src/repositories/config.repository.ts | 20 ++++++- server/src/utils/workers.ts | 21 -------- .../repositories/config.repository.mock.ts | 4 +- 10 files changed, 63 insertions(+), 58 deletions(-) rename server/src/{utils => bin}/healthcheck.ts (69%) rename server/src/{utils/workers.spec.ts => repositories/config.repository.spec.ts} (92%) delete mode 100644 server/src/utils/workers.ts diff --git a/server/bin/immich-healthcheck b/server/bin/immich-healthcheck index 6043e526aa..cf0accb8dd 100755 --- a/server/bin/immich-healthcheck +++ b/server/bin/immich-healthcheck @@ -1,3 +1,3 @@ #!/usr/bin/env bash -node /usr/src/app/dist/utils/healthcheck.js +node /usr/src/app/dist/bin/healthcheck.js diff --git a/server/package.json b/server/package.json index fbbc2c4892..5e0e0a6705 100644 --- a/server/package.json +++ b/server/package.json @@ -18,7 +18,6 @@ "check": "tsc --noEmit", "check:code": "npm run format && npm run lint && npm run check", "check:all": "npm run check:code && npm run test:cov", - "healthcheck": "node ./dist/utils/healthcheck.js", "test": "vitest", "test:watch": "vitest --watch", "test:cov": "vitest --coverage", diff --git a/server/src/utils/healthcheck.ts b/server/src/bin/healthcheck.ts similarity index 69% rename from server/src/utils/healthcheck.ts rename to server/src/bin/healthcheck.ts index 763fce81b4..b38d9d17df 100644 --- a/server/src/utils/healthcheck.ts +++ b/server/src/bin/healthcheck.ts @@ -1,12 +1,14 @@ #!/usr/bin/env node -const port = Number(process.env.IMMICH_PORT) || 3001; -const controller = new AbortController(); +import { ImmichWorker } from 'src/enum'; +import { ConfigRepository } from 'src/repositories/config.repository'; const main = async () => { - if (!process.env.IMMICH_WORKERS_INCLUDE?.includes('api')) { + const { workers, port } = new ConfigRepository().getEnv(); + if (!workers.includes(ImmichWorker.API)) { process.exit(); } + const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 2000); try { const response = await fetch(`http://localhost:${port}/api/server-info/ping`, { diff --git a/server/src/enum.ts b/server/src/enum.ts index d1a76573d1..109e9a90b7 100644 --- a/server/src/enum.ts +++ b/server/src/enum.ts @@ -334,3 +334,8 @@ export enum ImmichEnvironment { TESTING = 'testing', PRODUCTION = 'production', } + +export enum ImmichWorker { + API = 'api', + MICROSERVICES = 'microservices', +} diff --git a/server/src/interfaces/config.interface.ts b/server/src/interfaces/config.interface.ts index fe0c809bf8..3787684b8d 100644 --- a/server/src/interfaces/config.interface.ts +++ b/server/src/interfaces/config.interface.ts @@ -1,4 +1,4 @@ -import { ImmichEnvironment, LogLevel } from 'src/enum'; +import { ImmichEnvironment, ImmichWorker, LogLevel } from 'src/enum'; import { VectorExtension } from 'src/interfaces/database.interface'; export const IConfigRepository = 'IConfigRepository'; @@ -18,6 +18,8 @@ export interface EnvData { ignoreMountCheckErrors: boolean; }; + workers: ImmichWorker[]; + nodeVersion?: string; } diff --git a/server/src/main.ts b/server/src/main.ts index 48ce179e88..11cc44ec10 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -2,20 +2,15 @@ import { CommandFactory } from 'nest-commander'; import { fork } from 'node:child_process'; import { Worker } from 'node:worker_threads'; import { ImmichAdminModule } from 'src/app.module'; -import { LogLevel } from 'src/enum'; -import { getWorkers } from 'src/utils/workers'; -const immichApp = process.argv[2] || process.env.IMMICH_APP; +import { ImmichWorker, LogLevel } from 'src/enum'; +import { ConfigRepository } from 'src/repositories/config.repository'; -if (process.argv[2] === immichApp) { +const immichApp = process.argv[2]; +if (immichApp) { process.argv.splice(2, 1); } -async function bootstrapImmichAdmin() { - process.env.IMMICH_LOG_LEVEL = LogLevel.WARN; - await CommandFactory.run(ImmichAdminModule); -} - -function bootstrapWorker(name: string) { +function bootstrapWorker(name: ImmichWorker) { console.log(`Starting ${name} worker`); const execArgv = process.execArgv.map((arg) => (arg.startsWith('--inspect') ? '--inspect=0.0.0.0:9231' : arg)); @@ -35,26 +30,27 @@ function bootstrapWorker(name: string) { } function bootstrap() { - switch (immichApp) { - case 'immich-admin': { - process.title = 'immich_admin_cli'; - return bootstrapImmichAdmin(); - } - case 'immich': { - if (!process.env.IMMICH_WORKERS_INCLUDE) { - process.env.IMMICH_WORKERS_INCLUDE = 'api'; - } - break; - } - case 'microservices': { - if (!process.env.IMMICH_WORKERS_INCLUDE) { - process.env.IMMICH_WORKERS_INCLUDE = 'microservices'; - } - break; - } + if (immichApp === 'immich-admin') { + process.title = 'immich_admin_cli'; + process.env.IMMICH_LOG_LEVEL = LogLevel.WARN; + return CommandFactory.run(ImmichAdminModule); } + + if (immichApp === 'immich' || immichApp === 'microservices') { + console.error( + `Using "start.sh ${immichApp}" has been deprecated. See https://github.com/immich-app/immich/releases/tag/v1.118.0 for more information.`, + ); + process.exit(1); + } + + if (immichApp) { + console.error(`Unknown command: "${immichApp}"`); + process.exit(1); + } + process.title = 'immich'; - for (const worker of getWorkers()) { + const { workers } = new ConfigRepository().getEnv(); + for (const worker of workers) { bootstrapWorker(worker); } } diff --git a/server/src/utils/workers.spec.ts b/server/src/repositories/config.repository.spec.ts similarity index 92% rename from server/src/utils/workers.spec.ts rename to server/src/repositories/config.repository.spec.ts index 1e4ff5e2d3..2fbd76cefb 100644 --- a/server/src/utils/workers.spec.ts +++ b/server/src/repositories/config.repository.spec.ts @@ -1,4 +1,6 @@ -import { getWorkers } from 'src/utils/workers'; +import { ConfigRepository } from 'src/repositories/config.repository'; + +const getWorkers = () => new ConfigRepository().getEnv().workers; describe('getWorkers', () => { beforeEach(() => { diff --git a/server/src/repositories/config.repository.ts b/server/src/repositories/config.repository.ts index 9c65a5608b..df4e426886 100644 --- a/server/src/repositories/config.repository.ts +++ b/server/src/repositories/config.repository.ts @@ -1,13 +1,30 @@ import { Injectable } from '@nestjs/common'; import { getVectorExtension } from 'src/database.config'; -import { ImmichEnvironment, LogLevel } from 'src/enum'; +import { ImmichEnvironment, ImmichWorker, LogLevel } from 'src/enum'; import { EnvData, IConfigRepository } from 'src/interfaces/config.interface'; +import { setDifference } from 'src/utils/set'; // TODO replace src/config validation with class-validator, here +const WORKER_TYPES = new Set(Object.values(ImmichWorker)); + +const asSet = (value: string | undefined, defaults: ImmichWorker[]) => { + const values = (value || '').replaceAll(/\s/g, '').split(',').filter(Boolean); + return new Set(values.length === 0 ? defaults : (values as ImmichWorker[])); +}; + @Injectable() export class ConfigRepository implements IConfigRepository { getEnv(): EnvData { + const included = asSet(process.env.IMMICH_WORKERS_INCLUDE, [ImmichWorker.API, ImmichWorker.MICROSERVICES]); + const excluded = asSet(process.env.IMMICH_WORKERS_EXCLUDE, []); + const workers = [...setDifference(included, excluded)]; + for (const worker of workers) { + if (!WORKER_TYPES.has(worker)) { + throw new Error(`Invalid worker(s) found: ${workers.join(',')}`); + } + } + return { port: Number(process.env.IMMICH_PORT) || 3001, environment: process.env.IMMICH_ENV as ImmichEnvironment, @@ -20,6 +37,7 @@ export class ConfigRepository implements IConfigRepository { storage: { ignoreMountCheckErrors: process.env.IMMICH_IGNORE_MOUNT_CHECK_ERRORS === 'true', }, + workers, }; } } diff --git a/server/src/utils/workers.ts b/server/src/utils/workers.ts deleted file mode 100644 index 14daa2620f..0000000000 --- a/server/src/utils/workers.ts +++ /dev/null @@ -1,21 +0,0 @@ -const WORKER_TYPES = new Set(['api', 'microservices']); - -export const getWorkers = () => { - let workers = ['api', 'microservices']; - const includedWorkers = process.env.IMMICH_WORKERS_INCLUDE?.replaceAll(/\s/g, ''); - const excludedWorkers = process.env.IMMICH_WORKERS_EXCLUDE?.replaceAll(/\s/g, ''); - - if (includedWorkers) { - workers = includedWorkers.split(','); - } - - if (excludedWorkers) { - workers = workers.filter((worker) => !excludedWorkers.split(',').includes(worker)); - } - - if (workers.some((worker) => !WORKER_TYPES.has(worker))) { - throw new Error(`Invalid worker(s) found: ${workers}`); - } - - return workers; -}; diff --git a/server/test/repositories/config.repository.mock.ts b/server/test/repositories/config.repository.mock.ts index 41bb8208b9..daf002335f 100644 --- a/server/test/repositories/config.repository.mock.ts +++ b/server/test/repositories/config.repository.mock.ts @@ -1,4 +1,4 @@ -import { ImmichEnvironment } from 'src/enum'; +import { ImmichEnvironment, ImmichWorker } from 'src/enum'; import { EnvData, IConfigRepository } from 'src/interfaces/config.interface'; import { DatabaseExtension } from 'src/interfaces/database.interface'; import { Mocked, vitest } from 'vitest'; @@ -15,6 +15,8 @@ const envData: EnvData = { storage: { ignoreMountCheckErrors: false, }, + + workers: [ImmichWorker.API, ImmichWorker.MICROSERVICES], }; export const newConfigRepositoryMock = (): Mocked => {