mirror of
https://github.com/immich-app/immich
synced 2026-08-22 13:13:05 +00:00
feat: boot a stripped down maintenance api if enabled
This commit is contained in:
parent
78f949afaa
commit
42ac9cf922
10 changed files with 168 additions and 9 deletions
|
|
@ -14325,6 +14325,9 @@
|
|||
"loginPageMessage": {
|
||||
"type": "string"
|
||||
},
|
||||
"maintenanceMode": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"mapDarkStyleUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -14349,6 +14352,7 @@
|
|||
"isInitialized",
|
||||
"isOnboarded",
|
||||
"loginPageMessage",
|
||||
"maintenanceMode",
|
||||
"mapDarkStyleUrl",
|
||||
"mapLightStyleUrl",
|
||||
"oauthButtonText",
|
||||
|
|
|
|||
|
|
@ -1159,6 +1159,7 @@ export type ServerConfigDto = {
|
|||
isInitialized: boolean;
|
||||
isOnboarded: boolean;
|
||||
loginPageMessage: string;
|
||||
maintenanceMode: boolean;
|
||||
mapDarkStyleUrl: string;
|
||||
mapLightStyleUrl: string;
|
||||
oauthButtonText: string;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { KyselyModule } from 'nestjs-kysely';
|
|||
import { OpenTelemetryModule } from 'nestjs-otel';
|
||||
import { commandsAndQuestions } from 'src/commands';
|
||||
import { IWorker } from 'src/constants';
|
||||
import { controllers } from 'src/controllers';
|
||||
import { controllers, maintenanceControllers } from 'src/controllers';
|
||||
import { ImmichWorker } from 'src/enum';
|
||||
import { AuthGuard } from 'src/middleware/auth.guard';
|
||||
import { ErrorInterceptor } from 'src/middleware/error.interceptor';
|
||||
|
|
@ -91,6 +91,13 @@ class BaseModule implements OnModuleInit, OnModuleDestroy {
|
|||
})
|
||||
export class ApiModule extends BaseModule {}
|
||||
|
||||
@Module({
|
||||
imports: [...imports, ScheduleModule.forRoot()],
|
||||
controllers: [...maintenanceControllers],
|
||||
providers: [...common, ...middleware, { provide: IWorker, useValue: ImmichWorker.Maintenance }],
|
||||
})
|
||||
export class MaintenanceModule extends BaseModule {}
|
||||
|
||||
@Module({
|
||||
imports: [...imports],
|
||||
providers: [...common, { provide: IWorker, useValue: ImmichWorker.Microservices }, SchedulerRegistry],
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import { OAuthController } from 'src/controllers/oauth.controller';
|
|||
import { PartnerController } from 'src/controllers/partner.controller';
|
||||
import { PersonController } from 'src/controllers/person.controller';
|
||||
import { SearchController } from 'src/controllers/search.controller';
|
||||
import { ServerController } from 'src/controllers/server.controller';
|
||||
import { MaintenanceServerController, ServerController } from 'src/controllers/server.controller';
|
||||
import { SessionController } from 'src/controllers/session.controller';
|
||||
import { SharedLinkController } from 'src/controllers/shared-link.controller';
|
||||
import { StackController } from 'src/controllers/stack.controller';
|
||||
|
|
@ -69,3 +69,5 @@ export const controllers = [
|
|||
UserController,
|
||||
ViewController,
|
||||
];
|
||||
|
||||
export const maintenanceControllers = [MaintenanceServerController];
|
||||
|
|
|
|||
|
|
@ -115,3 +115,36 @@ export class ServerController {
|
|||
return this.systemMetadataService.getVersionCheckState();
|
||||
}
|
||||
}
|
||||
|
||||
@Controller('server')
|
||||
export class MaintenanceServerController {
|
||||
constructor(
|
||||
private service: ServerService,
|
||||
private versionService: VersionService,
|
||||
) {}
|
||||
|
||||
@Get('ping')
|
||||
pingServer(): ServerPingResponse {
|
||||
return this.service.ping();
|
||||
}
|
||||
|
||||
@Get('version')
|
||||
getServerVersion(): ServerVersionResponseDto {
|
||||
return this.versionService.getVersion();
|
||||
}
|
||||
|
||||
@Get('features')
|
||||
getServerFeatures(): Promise<ServerFeaturesDto> {
|
||||
return this.service.getFeatures();
|
||||
}
|
||||
|
||||
@Get('config')
|
||||
getServerConfig(): Promise<ServerConfigDto> {
|
||||
return this.service.getSystemConfig();
|
||||
}
|
||||
|
||||
@Get('media-types')
|
||||
getSupportedMediaTypes(): ServerMediaTypesResponseDto {
|
||||
return this.service.getSupportedMediaTypes();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ export class ServerConfigDto {
|
|||
publicUsers!: boolean;
|
||||
mapDarkStyleUrl!: string;
|
||||
mapLightStyleUrl!: string;
|
||||
maintenanceMode!: boolean;
|
||||
}
|
||||
|
||||
export class ServerFeaturesDto {
|
||||
|
|
|
|||
|
|
@ -457,6 +457,7 @@ export enum ImmichEnvironment {
|
|||
|
||||
export enum ImmichWorker {
|
||||
Api = 'api',
|
||||
Maintenance = 'maintenance',
|
||||
Microservices = 'microservices',
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
import { NestFactory } from '@nestjs/core';
|
||||
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 { ImmichAdminModule } from 'src/app.module';
|
||||
import { ApiModule, ImmichAdminModule } from 'src/app.module';
|
||||
import { ImmichWorker, LogLevel } from 'src/enum';
|
||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
|
||||
import { getConfig } from 'src/utils/config';
|
||||
|
||||
/**
|
||||
* Manages worker lifecycle
|
||||
|
|
@ -22,17 +26,45 @@ class Workers {
|
|||
/**
|
||||
* Boot all enabled workers
|
||||
*/
|
||||
bootstrap() {
|
||||
async bootstrap() {
|
||||
const {
|
||||
maintenance: { enabled: maintenanceMode },
|
||||
} = await this.getConfig();
|
||||
const { workers } = new ConfigRepository().getEnv();
|
||||
|
||||
// todo: filter for API if in maintenance
|
||||
// todo: swap API for maintenance API
|
||||
|
||||
for (const worker of workers) {
|
||||
this.startWorker(worker);
|
||||
if (maintenanceMode) {
|
||||
this.startWorker(ImmichWorker.Maintenance);
|
||||
} else {
|
||||
for (const worker of workers) {
|
||||
this.startWorker(worker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise a short-lived Nest application to build configuration
|
||||
* @returns System configuration
|
||||
*/
|
||||
private async getConfig() {
|
||||
const app = await NestFactory.create(ApiModule);
|
||||
const logger = await app.resolve(LoggingRepository);
|
||||
const configRepo = app.get(ConfigRepository);
|
||||
const metadataRepo = app.get(SystemMetadataRepository);
|
||||
|
||||
const systemConfig = await getConfig(
|
||||
{
|
||||
configRepo,
|
||||
metadataRepo,
|
||||
logger,
|
||||
},
|
||||
{ withCache: false },
|
||||
);
|
||||
|
||||
await app.close();
|
||||
|
||||
return systemConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start an individual worker
|
||||
* @param name Worker
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@ export class ServerService extends BaseService {
|
|||
publicUsers: config.server.publicUsers,
|
||||
mapDarkStyleUrl: config.map.darkStyle,
|
||||
mapLightStyleUrl: config.map.lightStyle,
|
||||
maintenanceMode: config.maintenance.enabled,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
77
server/src/workers/maintenance.ts
Normal file
77
server/src/workers/maintenance.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { NestFactory } from '@nestjs/core';
|
||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
import { json } from 'body-parser';
|
||||
import compression from 'compression';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import { existsSync } from 'node:fs';
|
||||
import sirv from 'sirv';
|
||||
import { MaintenanceModule } from 'src/app.module';
|
||||
import { excludePaths, serverVersion } from 'src/constants';
|
||||
import { WebSocketAdapter } from 'src/middleware/websocket.adapter';
|
||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
import { bootstrapTelemetry } from 'src/repositories/telemetry.repository';
|
||||
import { ApiService } from 'src/services/api.service';
|
||||
import { SystemConfigService } from 'src/services/system-config.service';
|
||||
import { isStartUpError, useSwagger } from 'src/utils/misc';
|
||||
async function bootstrap() {
|
||||
process.title = 'immich-api';
|
||||
|
||||
const { telemetry, network } = new ConfigRepository().getEnv();
|
||||
if (telemetry.metrics.size > 0) {
|
||||
bootstrapTelemetry(telemetry.apiPort);
|
||||
}
|
||||
|
||||
const app = await NestFactory.create<NestExpressApplication>(MaintenanceModule, { bufferLogs: true });
|
||||
const logger = await app.resolve(LoggingRepository);
|
||||
const configRepository = app.get(ConfigRepository);
|
||||
app.get(SystemConfigService).nestApplication = app;
|
||||
|
||||
const { environment, host, port, resourcePaths } = configRepository.getEnv();
|
||||
|
||||
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' }));
|
||||
if (configRepository.isDev()) {
|
||||
app.enableCors();
|
||||
}
|
||||
app.useWebSocketAdapter(new WebSocketAdapter(app));
|
||||
useSwagger(app, { write: configRepository.isDev() });
|
||||
|
||||
app.setGlobalPrefix('api', { exclude: excludePaths });
|
||||
if (existsSync(resourcePaths.web.root)) {
|
||||
// copied from https://github.com/sveltejs/kit/blob/679b5989fe62e3964b9a73b712d7b41831aa1f07/packages/adapter-node/src/handler.js#L46
|
||||
// provides serving of precompressed assets and caching of immutable assets
|
||||
app.use(
|
||||
sirv(resourcePaths.web.root, {
|
||||
etag: true,
|
||||
gzip: true,
|
||||
brotli: true,
|
||||
extensions: [],
|
||||
setHeaders: (res, pathname) => {
|
||||
if (pathname.startsWith(`/_app/immutable`) && res.statusCode === 200) {
|
||||
res.setHeader('cache-control', 'public,max-age=31536000,immutable');
|
||||
}
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
app.use(app.get(ApiService).ssr(excludePaths));
|
||||
app.use(compression());
|
||||
|
||||
const server = await (host ? app.listen(port, host) : app.listen(port));
|
||||
server.requestTimeout = 24 * 60 * 60 * 1000;
|
||||
|
||||
logger.log(`Immich Server is listening on ${await app.getUrl()} [v${serverVersion}] [${environment}] `);
|
||||
}
|
||||
|
||||
bootstrap().catch((error) => {
|
||||
if (!isStartUpError(error)) {
|
||||
console.error(error);
|
||||
}
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue