mirror of
https://github.com/immich-app/immich.git
synced 2025-12-24 01:11:32 +03:00
119 lines
3.5 KiB
TypeScript
119 lines
3.5 KiB
TypeScript
import { BadRequestException, Injectable } from '@nestjs/common';
|
|
import { basename, join } from 'node:path';
|
|
import { StorageCore } from 'src/cores/storage.core';
|
|
import { OnEvent } from 'src/decorators';
|
|
import { MaintenanceAuthDto, MaintenanceIntegrityResponseDto, SetMaintenanceModeDto } from 'src/dtos/maintenance.dto';
|
|
import { MaintenanceAction, StorageFolder, SystemMetadataKey } from 'src/enum';
|
|
import { BaseService } from 'src/services/base.service';
|
|
import { MaintenanceModeState } from 'src/types';
|
|
import { deleteBackup, isValidBackupName, listBackups, uploadBackup } from 'src/utils/backups';
|
|
import {
|
|
createMaintenanceLoginUrl,
|
|
generateMaintenanceSecret,
|
|
integrityCheck,
|
|
signMaintenanceJwt,
|
|
} from 'src/utils/maintenance';
|
|
import { getExternalDomain } from 'src/utils/misc';
|
|
|
|
/**
|
|
* This service is available outside of maintenance mode to manage maintenance mode
|
|
*/
|
|
@Injectable()
|
|
export class MaintenanceService extends BaseService {
|
|
getMaintenanceMode(): Promise<MaintenanceModeState> {
|
|
return this.systemMetadataRepository
|
|
.get(SystemMetadataKey.MaintenanceMode)
|
|
.then((state) => state ?? { isMaintenanceMode: false });
|
|
}
|
|
|
|
integrityCheck(): Promise<MaintenanceIntegrityResponseDto> {
|
|
return integrityCheck(this.storageRepository);
|
|
}
|
|
|
|
async startMaintenance(action: SetMaintenanceModeDto, username: string): Promise<{ jwt: string }> {
|
|
const secret = generateMaintenanceSecret();
|
|
await this.systemMetadataRepository.set(SystemMetadataKey.MaintenanceMode, {
|
|
isMaintenanceMode: true,
|
|
secret,
|
|
action,
|
|
});
|
|
|
|
await this.eventRepository.emit('AppRestart', { isMaintenanceMode: true });
|
|
|
|
return {
|
|
jwt: await signMaintenanceJwt(secret, {
|
|
username,
|
|
}),
|
|
};
|
|
}
|
|
|
|
async startRestoreFlow(): Promise<{ jwt: string }> {
|
|
const adminUser = await this.userRepository.getAdmin();
|
|
if (adminUser) {
|
|
throw new BadRequestException('The server already has an admin');
|
|
}
|
|
|
|
return this.startMaintenance(
|
|
{
|
|
action: MaintenanceAction.RestoreDatabase,
|
|
},
|
|
'admin',
|
|
);
|
|
}
|
|
|
|
@OnEvent({ name: 'AppRestart', server: true })
|
|
onRestart(): void {
|
|
this.appRepository.exitApp();
|
|
}
|
|
|
|
async createLoginUrl(auth: MaintenanceAuthDto, secret?: string): Promise<string> {
|
|
const { server } = await this.getConfig({ withCache: true });
|
|
const baseUrl = getExternalDomain(server);
|
|
|
|
if (!secret) {
|
|
const state = await this.getMaintenanceMode();
|
|
if (!state.isMaintenanceMode) {
|
|
throw new Error('Not in maintenance mode');
|
|
}
|
|
|
|
secret = state.secret;
|
|
}
|
|
|
|
return await createMaintenanceLoginUrl(baseUrl, auth, secret);
|
|
}
|
|
|
|
/**
|
|
* Backups
|
|
*/
|
|
|
|
async listBackups(): Promise<{ backups: string[] }> {
|
|
return { backups: await listBackups(this.backupRepos) };
|
|
}
|
|
|
|
async deleteBackup(filename: string): Promise<void> {
|
|
return deleteBackup(this.backupRepos, basename(filename));
|
|
}
|
|
|
|
async uploadBackup(file: Express.Multer.File): Promise<void> {
|
|
return uploadBackup(this.backupRepos, file);
|
|
}
|
|
|
|
getBackupPath(filename: string): string {
|
|
if (!isValidBackupName(filename)) {
|
|
throw new BadRequestException('Invalid backup name!');
|
|
}
|
|
|
|
return join(StorageCore.getBaseFolder(StorageFolder.Backups), basename(filename));
|
|
}
|
|
|
|
private get backupRepos() {
|
|
return {
|
|
logger: this.logger,
|
|
storage: this.storageRepository,
|
|
config: this.configRepository,
|
|
process: this.processRepository,
|
|
database: this.databaseRepository,
|
|
};
|
|
}
|
|
}
|