diff --git a/server/src/app.module.ts b/server/src/app.module.ts index 153b525fe5..351f67d56c 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -8,6 +8,8 @@ import { OpenTelemetryModule } from 'nestjs-otel'; import { commands } from 'src/commands'; import { IWorker } from 'src/constants'; import { controllers } from 'src/controllers'; +import { MaintenanceController } from 'src/controllers/maintenance.controller'; +import { ServerController } from 'src/controllers/server.controller'; import { ImmichWorker } from 'src/enum'; import { AuthGuard } from 'src/middleware/auth.guard'; import { ErrorInterceptor } from 'src/middleware/error.interceptor'; @@ -95,6 +97,13 @@ export class ApiModule extends BaseModule {} }) export class MicroservicesModule extends BaseModule {} +@Module({ + imports: [...imports], + controllers: [MaintenanceController, ServerController], + providers: [...common, { provide: IWorker, useValue: ImmichWorker.MAINTENANCE }, SchedulerRegistry], +}) +export class MaintenanceModule extends BaseModule {} + @Module({ imports: [...imports], providers: [...common, ...commands, SchedulerRegistry], diff --git a/server/src/controllers/maintenance.controller.ts b/server/src/controllers/maintenance.controller.ts new file mode 100644 index 0000000000..781f89b89d --- /dev/null +++ b/server/src/controllers/maintenance.controller.ts @@ -0,0 +1,17 @@ +import { Controller, Get, Query } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { ServerPingResponse } from 'src/dtos/server.dto'; +import { Authenticated } from 'src/middleware/auth.guard'; +import { MaintenanceService } from 'src/services/maintenance.service'; + +@ApiTags('Maintenance') +@Controller('maintenance') +export class MaintenanceController { + constructor(private service: MaintenanceService) {} + + @Get('ping') + @Authenticated() + getPing(): ServerPingResponse { + return this.service.ping(); + } +} diff --git a/server/src/enum.ts b/server/src/enum.ts index e7e40eb122..e4f8f03c8a 100644 --- a/server/src/enum.ts +++ b/server/src/enum.ts @@ -389,6 +389,7 @@ export enum ImmichEnvironment { export enum ImmichWorker { API = 'api', MICROSERVICES = 'microservices', + MAINTENANCE = 'maintenance', } export enum ImmichTelemetry { diff --git a/server/src/repositories/config.repository.ts b/server/src/repositories/config.repository.ts index 9a0a24f70f..01670cb1a1 100644 --- a/server/src/repositories/config.repository.ts +++ b/server/src/repositories/config.repository.ts @@ -136,7 +136,11 @@ const getEnv = (): EnvData => { ); } - const includedWorkers = asSet(dto.IMMICH_WORKERS_INCLUDE, [ImmichWorker.API, ImmichWorker.MICROSERVICES]); + const includedWorkers = asSet(dto.IMMICH_WORKERS_INCLUDE, [ + // ImmichWorker.API, + // ImmichWorker.MICROSERVICES, + ImmichWorker.MAINTENANCE, + ]); const excludedWorkers = asSet(dto.IMMICH_WORKERS_EXCLUDE, []); const workers = [...setDifference(includedWorkers, excludedWorkers)]; for (const worker of workers) { diff --git a/server/src/repositories/event.repository.ts b/server/src/repositories/event.repository.ts index 307b8b0ef4..985a579fa9 100644 --- a/server/src/repositories/event.repository.ts +++ b/server/src/repositories/event.repository.ts @@ -109,6 +109,7 @@ export interface ClientEventMap { on_new_release: [ReleaseNotification]; on_notification: [NotificationDto]; on_session_delete: [string]; + shutdown_worker: [ImmichWorker]; } export type EventItem = { diff --git a/server/src/services/index.ts b/server/src/services/index.ts index 88b68d2c13..2883e84d41 100644 --- a/server/src/services/index.ts +++ b/server/src/services/index.ts @@ -13,6 +13,7 @@ import { DownloadService } from 'src/services/download.service'; import { DuplicateService } from 'src/services/duplicate.service'; import { JobService } from 'src/services/job.service'; import { LibraryService } from 'src/services/library.service'; +import { MaintenanceService } from 'src/services/maintenance.service'; import { MapService } from 'src/services/map.service'; import { MediaService } from 'src/services/media.service'; import { MemoryService } from 'src/services/memory.service'; @@ -56,6 +57,7 @@ export const services = [ DuplicateService, JobService, LibraryService, + MaintenanceService, MapService, MediaService, MemoryService, diff --git a/server/src/services/maintenance.service.ts b/server/src/services/maintenance.service.ts new file mode 100644 index 0000000000..411bb4e4b8 --- /dev/null +++ b/server/src/services/maintenance.service.ts @@ -0,0 +1,11 @@ +import { Injectable } from '@nestjs/common'; +import { ServerPingResponse } from 'src/dtos/server.dto'; +import { BaseService } from 'src/services/base.service'; + +@Injectable() +export class MaintenanceService extends BaseService { + ping(): ServerPingResponse { + // this.eventRepository.clientBroadcast(); + return { res: 'Pong!' }; + } +} diff --git a/server/src/workers/maintenance.ts b/server/src/workers/maintenance.ts new file mode 100644 index 0000000000..e194007a1d --- /dev/null +++ b/server/src/workers/maintenance.ts @@ -0,0 +1,51 @@ +import { NestFactory } from '@nestjs/core'; +import { NestExpressApplication } from '@nestjs/platform-express'; +import { json } from 'body-parser'; +import cookieParser from 'cookie-parser'; +import { isMainThread } from 'node:worker_threads'; +import { ApiModule, MaintenanceModule } from 'src/app.module'; +import { excludePaths, serverVersion } from 'src/constants'; +import { ImmichEnvironment } from 'src/enum'; +import { WebSocketAdapter } from 'src/middleware/websocket.adapter'; +import { ConfigRepository } from 'src/repositories/config.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; +import { ApiService } from 'src/services/api.service'; +import { isStartUpError } from 'src/utils/misc'; + +export async function bootstrap() { + const app = await NestFactory.create(MaintenanceModule, { bufferLogs: true }); + const logger = await app.resolve(LoggingRepository); + const configRepository = app.get(ConfigRepository); + + const { environment, host, network } = configRepository.getEnv(); + const isDev = environment === ImmichEnvironment.DEVELOPMENT; + + logger.setContext('Bootstrap'); + app.useLogger(logger); + app.set('trust proxy', ['loopback', ...network.trustedProxies]); + app.set('etag', 'strong'); + app.use(cookieParser()); + app.use(json({ limit: '10mb' })); + app.useWebSocketAdapter(new WebSocketAdapter(app)); + + if (isDev) { + app.enableCors(); + } + + app.setGlobalPrefix('api', { exclude: excludePaths }); + + app.use(app.get(ApiService).ssr(excludePaths)); + + await (host ? app.listen(2283, host) : app.listen(2283)); + + logger.log(`Immich Maintenance is listening on ${await app.getUrl()} [v${serverVersion}] [${environment}] `); +} + +if (!isMainThread) { + bootstrap().catch((error) => { + if (!isStartUpError(error)) { + console.error(error); + } + throw error; + }); +}