diff --git a/server/src/main.ts b/server/src/main.ts index 26c49f397f..ac7f50d6c7 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -3,11 +3,11 @@ import { CommandFactory } from 'nest-commander'; import { ChildProcess, fork } from 'node:child_process'; import { dirname, join } from 'node:path'; import { Worker } from 'node:worker_threads'; -import { PostgresError } from 'postgres'; import { ApiModule, ImmichAdminModule } from 'src/app.module'; -import { ImmichWorker, LogLevel, SystemMetadataKey } from 'src/enum'; +import { ImmichWorker, LogLevel } from 'src/enum'; import { ConfigRepository } from 'src/repositories/config.repository'; import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; +import { MaintenanceService } from 'src/services/maintenance.service'; /** * Manages worker lifecycle @@ -47,22 +47,13 @@ class Workers { * Initialise a short-lived Nest application to build configuration * @returns System configuration */ - private async getConfig(): Promise<{ isMaintenanceMode?: boolean }> { - try { - const app = await NestFactory.create(ApiModule); - const metadataRepo = app.get(SystemMetadataRepository); + private async getConfig(): Promise<{ isMaintenanceMode: boolean }> { + const app = await NestFactory.create(ApiModule); + const metadataRepository = app.get(SystemMetadataRepository); - await app.close(); + await app.close(); - return metadataRepo.get(SystemMetadataKey.MaintenanceMode).then((value) => value ?? {}); - } catch (err) { - // Table doesn't exist (migrations haven't run yet) - if (err instanceof PostgresError && err.code === '42P01') { - return { isMaintenanceMode: false }; - } - - throw err; - } + return await MaintenanceService.getMaintenanceModeWith(metadataRepository); } /** diff --git a/server/src/services/maintenance.service.ts b/server/src/services/maintenance.service.ts index 5608d9d934..81c5178b34 100644 --- a/server/src/services/maintenance.service.ts +++ b/server/src/services/maintenance.service.ts @@ -1,16 +1,33 @@ import { BadRequestException, INestApplication, Injectable } from '@nestjs/common'; +import { PostgresError } from 'postgres'; import { OnEvent } from 'src/decorators'; import { MaintenanceModeResponseDto } from 'src/dtos/maintenance.dto'; import { SystemMetadataKey } from 'src/enum'; +import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; import { BaseService } from 'src/services/base.service'; @Injectable() export class MaintenanceService extends BaseService { nestApplication: INestApplication | undefined; - async getMaintenanceMode(): Promise { - const value = await this.systemMetadataRepository.get(SystemMetadataKey.MaintenanceMode); - return { isMaintenanceMode: false, ...value }; + static getMaintenanceModeWith( + systemMetadataRepository: SystemMetadataRepository, + ): Promise<{ isMaintenanceMode: boolean }> { + return systemMetadataRepository + .get(SystemMetadataKey.MaintenanceMode) + .catch((err) => { + // Table doesn't exist (migrations haven't run yet) + if (err instanceof PostgresError && err.code === '42P01') { + return { isMaintenanceMode: false }; + } + + throw err; + }) + .then((value) => ({ ...value, isMaintenanceMode: false })); + } + + getMaintenanceMode(): Promise { + return MaintenanceService.getMaintenanceModeWith(this.systemMetadataRepository); } private async setMaintenanceMode(isMaintenanceMode: boolean) { @@ -18,7 +35,7 @@ export class MaintenanceService extends BaseService { await this.systemMetadataRepository.set(SystemMetadataKey.MaintenanceMode, state); this.websocketRepository.clientBroadcast('on_server_restart', state); this.websocketRepository.serverSend('AppRestart'); - this.eventRepository.emit('AppRestart'); + await this.eventRepository.emit('AppRestart'); } async startMaintenance(): Promise { @@ -27,7 +44,7 @@ export class MaintenanceService extends BaseService { throw new BadRequestException('Already in maintenance mode'); } - this.setMaintenanceMode(true); + await this.setMaintenanceMode(true); } async endMaintenance(): Promise { @@ -36,16 +53,19 @@ export class MaintenanceService extends BaseService { throw new BadRequestException('Not in maintenance mode'); } - this.setMaintenanceMode(false); + await this.setMaintenanceMode(false); } @OnEvent({ name: 'AppRestart', server: true }) onRestart() { - this.nestApplication + /* eslint-disable unicorn/no-process-exit */ + // we need to specify the exact exit code + void this.nestApplication ?.close() // attempt graceful shutdown .then(() => process.exit(7)); // then signal restart // in some exceptional circumstances, close() may hang setTimeout(() => process.exit(7), 5000); + /* eslint-enable unicorn/no-process-exit */ } } diff --git a/server/src/services/server.service.ts b/server/src/services/server.service.ts index edf229c988..c41b426b77 100644 --- a/server/src/services/server.service.ts +++ b/server/src/services/server.service.ts @@ -17,6 +17,7 @@ import { import { StorageFolder, SystemMetadataKey } from 'src/enum'; import { UserStatsQueryResponse } from 'src/repositories/user.repository'; import { BaseService } from 'src/services/base.service'; +import { MaintenanceService } from 'src/services/maintenance.service'; import { asHumanReadable } from 'src/utils/bytes'; import { mimeTypes } from 'src/utils/mime-types'; import { @@ -118,7 +119,7 @@ export class ServerService extends BaseService { const config = await this.getConfig({ withCache: false }); const isInitialized = await this.userRepository.hasAdmin(); const onboarding = await this.systemMetadataRepository.get(SystemMetadataKey.AdminOnboarding); - const maintenanceMode = await this.systemMetadataRepository.get(SystemMetadataKey.MaintenanceMode); + const { isMaintenanceMode } = await MaintenanceService.getMaintenanceModeWith(this.systemMetadataRepository); return { loginPageMessage: config.server.loginPageMessage, @@ -131,7 +132,7 @@ export class ServerService extends BaseService { publicUsers: config.server.publicUsers, mapDarkStyleUrl: config.map.darkStyle, mapLightStyleUrl: config.map.lightStyle, - maintenanceMode: maintenanceMode?.isMaintenanceMode || false, + maintenanceMode: isMaintenanceMode, }; }