1
0
mirror of https://github.com/immich-app/immich.git synced 2024-11-24 08:52:28 +02:00

refactor(server): worker env (#13160)

This commit is contained in:
Jason Rasmussen 2024-10-03 15:28:36 -04:00 committed by GitHub
parent 892a35acb5
commit db1623f43f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 63 additions and 58 deletions

View File

@ -1,3 +1,3 @@
#!/usr/bin/env bash #!/usr/bin/env bash
node /usr/src/app/dist/utils/healthcheck.js node /usr/src/app/dist/bin/healthcheck.js

View File

@ -18,7 +18,6 @@
"check": "tsc --noEmit", "check": "tsc --noEmit",
"check:code": "npm run format && npm run lint && npm run check", "check:code": "npm run format && npm run lint && npm run check",
"check:all": "npm run check:code && npm run test:cov", "check:all": "npm run check:code && npm run test:cov",
"healthcheck": "node ./dist/utils/healthcheck.js",
"test": "vitest", "test": "vitest",
"test:watch": "vitest --watch", "test:watch": "vitest --watch",
"test:cov": "vitest --coverage", "test:cov": "vitest --coverage",

View File

@ -1,12 +1,14 @@
#!/usr/bin/env node #!/usr/bin/env node
const port = Number(process.env.IMMICH_PORT) || 3001; import { ImmichWorker } from 'src/enum';
const controller = new AbortController(); import { ConfigRepository } from 'src/repositories/config.repository';
const main = async () => { const main = async () => {
if (!process.env.IMMICH_WORKERS_INCLUDE?.includes('api')) { const { workers, port } = new ConfigRepository().getEnv();
if (!workers.includes(ImmichWorker.API)) {
process.exit(); process.exit();
} }
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 2000); const timeout = setTimeout(() => controller.abort(), 2000);
try { try {
const response = await fetch(`http://localhost:${port}/api/server-info/ping`, { const response = await fetch(`http://localhost:${port}/api/server-info/ping`, {

View File

@ -334,3 +334,8 @@ export enum ImmichEnvironment {
TESTING = 'testing', TESTING = 'testing',
PRODUCTION = 'production', PRODUCTION = 'production',
} }
export enum ImmichWorker {
API = 'api',
MICROSERVICES = 'microservices',
}

View File

@ -1,4 +1,4 @@
import { ImmichEnvironment, LogLevel } from 'src/enum'; import { ImmichEnvironment, ImmichWorker, LogLevel } from 'src/enum';
import { VectorExtension } from 'src/interfaces/database.interface'; import { VectorExtension } from 'src/interfaces/database.interface';
export const IConfigRepository = 'IConfigRepository'; export const IConfigRepository = 'IConfigRepository';
@ -18,6 +18,8 @@ export interface EnvData {
ignoreMountCheckErrors: boolean; ignoreMountCheckErrors: boolean;
}; };
workers: ImmichWorker[];
nodeVersion?: string; nodeVersion?: string;
} }

View File

@ -2,20 +2,15 @@ import { CommandFactory } from 'nest-commander';
import { fork } from 'node:child_process'; import { fork } from 'node:child_process';
import { Worker } from 'node:worker_threads'; import { Worker } from 'node:worker_threads';
import { ImmichAdminModule } from 'src/app.module'; import { ImmichAdminModule } from 'src/app.module';
import { LogLevel } from 'src/enum'; import { ImmichWorker, LogLevel } from 'src/enum';
import { getWorkers } from 'src/utils/workers'; import { ConfigRepository } from 'src/repositories/config.repository';
const immichApp = process.argv[2] || process.env.IMMICH_APP;
if (process.argv[2] === immichApp) { const immichApp = process.argv[2];
if (immichApp) {
process.argv.splice(2, 1); process.argv.splice(2, 1);
} }
async function bootstrapImmichAdmin() { function bootstrapWorker(name: ImmichWorker) {
process.env.IMMICH_LOG_LEVEL = LogLevel.WARN;
await CommandFactory.run(ImmichAdminModule);
}
function bootstrapWorker(name: string) {
console.log(`Starting ${name} worker`); console.log(`Starting ${name} worker`);
const execArgv = process.execArgv.map((arg) => (arg.startsWith('--inspect') ? '--inspect=0.0.0.0:9231' : arg)); 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() { function bootstrap() {
switch (immichApp) { if (immichApp === 'immich-admin') {
case 'immich-admin': {
process.title = 'immich_admin_cli'; process.title = 'immich_admin_cli';
return bootstrapImmichAdmin(); process.env.IMMICH_LOG_LEVEL = LogLevel.WARN;
return CommandFactory.run(ImmichAdminModule);
} }
case 'immich': {
if (!process.env.IMMICH_WORKERS_INCLUDE) { if (immichApp === 'immich' || immichApp === 'microservices') {
process.env.IMMICH_WORKERS_INCLUDE = 'api'; console.error(
} `Using "start.sh ${immichApp}" has been deprecated. See https://github.com/immich-app/immich/releases/tag/v1.118.0 for more information.`,
break; );
} process.exit(1);
case 'microservices': {
if (!process.env.IMMICH_WORKERS_INCLUDE) {
process.env.IMMICH_WORKERS_INCLUDE = 'microservices';
}
break;
} }
if (immichApp) {
console.error(`Unknown command: "${immichApp}"`);
process.exit(1);
} }
process.title = 'immich'; process.title = 'immich';
for (const worker of getWorkers()) { const { workers } = new ConfigRepository().getEnv();
for (const worker of workers) {
bootstrapWorker(worker); bootstrapWorker(worker);
} }
} }

View File

@ -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', () => { describe('getWorkers', () => {
beforeEach(() => { beforeEach(() => {

View File

@ -1,13 +1,30 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { getVectorExtension } from 'src/database.config'; 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 { EnvData, IConfigRepository } from 'src/interfaces/config.interface';
import { setDifference } from 'src/utils/set';
// TODO replace src/config validation with class-validator, here // 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() @Injectable()
export class ConfigRepository implements IConfigRepository { export class ConfigRepository implements IConfigRepository {
getEnv(): EnvData { 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 { return {
port: Number(process.env.IMMICH_PORT) || 3001, port: Number(process.env.IMMICH_PORT) || 3001,
environment: process.env.IMMICH_ENV as ImmichEnvironment, environment: process.env.IMMICH_ENV as ImmichEnvironment,
@ -20,6 +37,7 @@ export class ConfigRepository implements IConfigRepository {
storage: { storage: {
ignoreMountCheckErrors: process.env.IMMICH_IGNORE_MOUNT_CHECK_ERRORS === 'true', ignoreMountCheckErrors: process.env.IMMICH_IGNORE_MOUNT_CHECK_ERRORS === 'true',
}, },
workers,
}; };
} }
} }

View File

@ -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;
};

View File

@ -1,4 +1,4 @@
import { ImmichEnvironment } from 'src/enum'; import { ImmichEnvironment, ImmichWorker } from 'src/enum';
import { EnvData, IConfigRepository } from 'src/interfaces/config.interface'; import { EnvData, IConfigRepository } from 'src/interfaces/config.interface';
import { DatabaseExtension } from 'src/interfaces/database.interface'; import { DatabaseExtension } from 'src/interfaces/database.interface';
import { Mocked, vitest } from 'vitest'; import { Mocked, vitest } from 'vitest';
@ -15,6 +15,8 @@ const envData: EnvData = {
storage: { storage: {
ignoreMountCheckErrors: false, ignoreMountCheckErrors: false,
}, },
workers: [ImmichWorker.API, ImmichWorker.MICROSERVICES],
}; };
export const newConfigRepositoryMock = (): Mocked<IConfigRepository> => { export const newConfigRepositoryMock = (): Mocked<IConfigRepository> => {