refactor: scope util to database-backups instead of backups

This commit is contained in:
izzy
2025-12-03 12:30:07 +00:00
parent adc2d5d1e5
commit 4e2187acf9
4 changed files with 44 additions and 33 deletions

View File

@@ -26,8 +26,14 @@ import { type BaseService as _BaseService } from 'src/services/base.service';
import { type DatabaseBackupService as _DatabaseBackupService } from 'src/services/database-backup.service'; import { type DatabaseBackupService as _DatabaseBackupService } from 'src/services/database-backup.service';
import { type ServerService as _ServerService } from 'src/services/server.service'; import { type ServerService as _ServerService } from 'src/services/server.service';
import { MaintenanceModeState } from 'src/types'; import { MaintenanceModeState } from 'src/types';
import { deleteBackups, downloadBackup, listBackups, restoreBackup, uploadBackup } from 'src/utils/backups';
import { getConfig } from 'src/utils/config'; import { getConfig } from 'src/utils/config';
import {
deleteDatabaseBackup,
downloadDatabaseBackup,
listDatabaseBackups,
restoreDatabaseBackup,
uploadDatabaseBackup,
} from 'src/utils/database-backups';
import { ImmichFileResponse } from 'src/utils/file'; import { ImmichFileResponse } from 'src/utils/file';
import { createMaintenanceLoginUrl, detectPriorInstall } from 'src/utils/maintenance'; import { createMaintenanceLoginUrl, detectPriorInstall } from 'src/utils/maintenance';
import { getExternalDomain } from 'src/utils/misc'; import { getExternalDomain } from 'src/utils/misc';
@@ -171,28 +177,28 @@ export class MaintenanceWorkerService {
* {@link _DatabaseBackupService.listBackups} * {@link _DatabaseBackupService.listBackups}
*/ */
async listBackups(): Promise<{ backups: string[] }> { async listBackups(): Promise<{ backups: string[] }> {
return { backups: await listBackups(this.backupRepos) }; return { backups: await listDatabaseBackups(this.backupRepos) };
} }
/** /**
* {@link _DatabaseBackupService.deleteBackup} * {@link _DatabaseBackupService.deleteBackup}
*/ */
async deleteBackup(files: string[]): Promise<void> { async deleteBackup(files: string[]): Promise<void> {
return deleteBackups(this.backupRepos, files); return deleteDatabaseBackup(this.backupRepos, files);
} }
/** /**
* {@link _DatabaseBackupService.uploadBackup} * {@link _DatabaseBackupService.uploadBackup}
*/ */
async uploadBackup(file: Express.Multer.File): Promise<void> { async uploadBackup(file: Express.Multer.File): Promise<void> {
return uploadBackup(this.backupRepos, file); return uploadDatabaseBackup(this.backupRepos, file);
} }
/** /**
* {@link _DatabaseBackupService.downloadBackup} * {@link _DatabaseBackupService.downloadBackup}
*/ */
downloadBackup(fileName: string): ImmichFileResponse { downloadBackup(fileName: string): ImmichFileResponse {
return downloadBackup(fileName); return downloadDatabaseBackup(fileName);
} }
private get backupRepos() { private get backupRepos() {
@@ -339,7 +345,7 @@ export class MaintenanceWorkerService {
progress: 0, progress: 0,
}); });
await restoreBackup(this.backupRepos, filename, (task, progress) => await restoreDatabaseBackup(this.backupRepos, filename, (task, progress) =>
this.setStatus({ this.setStatus({
active: true, active: true,
action: MaintenanceAction.RestoreDatabase, action: MaintenanceAction.RestoreDatabase,

View File

@@ -6,11 +6,11 @@ import { DatabaseLock, ImmichWorker, JobName, JobStatus, QueueName, StorageFolde
import { ArgOf } from 'src/repositories/event.repository'; import { ArgOf } from 'src/repositories/event.repository';
import { BaseService } from 'src/services/base.service'; import { BaseService } from 'src/services/base.service';
import { import {
createBackup, createDatabaseBackup,
isFailedBackupName, isFailedDatabaseBackupName,
isValidRoutineBackupName, isValidDatabaseRoutineBackupName,
UnsupportedPostgresError, UnsupportedPostgresError,
} from 'src/utils/backups'; } from 'src/utils/database-backups';
import { handlePromiseError } from 'src/utils/misc'; import { handlePromiseError } from 'src/utils/misc';
@Injectable() @Injectable()
@@ -57,10 +57,10 @@ export class BackupService extends BaseService {
const backupsFolder = StorageCore.getBaseFolder(StorageFolder.Backups); const backupsFolder = StorageCore.getBaseFolder(StorageFolder.Backups);
const files = await this.storageRepository.readdir(backupsFolder); const files = await this.storageRepository.readdir(backupsFolder);
const backups = files const backups = files
.filter((fn) => isValidRoutineBackupName(fn)) .filter((fn) => isValidDatabaseRoutineBackupName(fn))
.toSorted() .toSorted()
.toReversed(); .toReversed();
const failedBackups = files.filter((fn) => isFailedBackupName(fn)); const failedBackups = files.filter((fn) => isFailedDatabaseBackupName(fn));
const toDelete = backups.slice(config.keepLastAmount); const toDelete = backups.slice(config.keepLastAmount);
toDelete.push(...failedBackups); toDelete.push(...failedBackups);
@@ -74,7 +74,7 @@ export class BackupService extends BaseService {
@OnJob({ name: JobName.DatabaseBackup, queue: QueueName.BackupDatabase }) @OnJob({ name: JobName.DatabaseBackup, queue: QueueName.BackupDatabase })
async handleBackupDatabase(): Promise<JobStatus> { async handleBackupDatabase(): Promise<JobStatus> {
try { try {
await createBackup(this.backupRepos); await createDatabaseBackup(this.backupRepos);
} catch (error) { } catch (error) {
if (error instanceof UnsupportedPostgresError) { if (error instanceof UnsupportedPostgresError) {
return JobStatus.Failed; return JobStatus.Failed;

View File

@@ -1,6 +1,11 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { BaseService } from 'src/services/base.service'; import { BaseService } from 'src/services/base.service';
import { deleteBackups, downloadBackup, listBackups, uploadBackup } from 'src/utils/backups'; import {
deleteDatabaseBackup,
downloadDatabaseBackup,
listDatabaseBackups,
uploadDatabaseBackup,
} from 'src/utils/database-backups';
import { ImmichFileResponse } from 'src/utils/file'; import { ImmichFileResponse } from 'src/utils/file';
/** /**
@@ -9,19 +14,19 @@ import { ImmichFileResponse } from 'src/utils/file';
@Injectable() @Injectable()
export class DatabaseBackupService extends BaseService { export class DatabaseBackupService extends BaseService {
async listBackups(): Promise<{ backups: string[] }> { async listBackups(): Promise<{ backups: string[] }> {
return { backups: await listBackups(this.backupRepos) }; return { backups: await listDatabaseBackups(this.backupRepos) };
} }
deleteBackup(files: string[]): Promise<void> { deleteBackup(files: string[]): Promise<void> {
return deleteBackups(this.backupRepos, files); return deleteDatabaseBackup(this.backupRepos, files);
} }
async uploadBackup(file: Express.Multer.File): Promise<void> { async uploadBackup(file: Express.Multer.File): Promise<void> {
return uploadBackup(this.backupRepos, file); return uploadDatabaseBackup(this.backupRepos, file);
} }
downloadBackup(fileName: string): ImmichFileResponse { downloadBackup(fileName: string): ImmichFileResponse {
return downloadBackup(fileName); return downloadDatabaseBackup(fileName);
} }
private get backupRepos() { private get backupRepos() {

View File

@@ -14,18 +14,18 @@ import { LoggingRepository } from 'src/repositories/logging.repository';
import { ProcessRepository } from 'src/repositories/process.repository'; import { ProcessRepository } from 'src/repositories/process.repository';
import { StorageRepository } from 'src/repositories/storage.repository'; import { StorageRepository } from 'src/repositories/storage.repository';
export function isValidBackupName(filename: string) { export function isValidDatabaseBackupName(filename: string) {
return filename.match(/^[\d\w-.]+\.sql(?:\.gz)?$/); return filename.match(/^[\d\w-.]+\.sql(?:\.gz)?$/);
} }
export function isValidRoutineBackupName(filename: string) { export function isValidDatabaseRoutineBackupName(filename: string) {
const oldBackupStyle = filename.match(/^immich-db-backup-\d+\.sql\.gz$/); const oldBackupStyle = filename.match(/^immich-db-backup-\d+\.sql\.gz$/);
//immich-db-backup-20250729T114018-v1.136.0-pg14.17.sql.gz //immich-db-backup-20250729T114018-v1.136.0-pg14.17.sql.gz
const newBackupStyle = filename.match(/^immich-db-backup-\d{8}T\d{6}-v.*-pg.*\.sql\.gz$/); const newBackupStyle = filename.match(/^immich-db-backup-\d{8}T\d{6}-v.*-pg.*\.sql\.gz$/);
return oldBackupStyle || newBackupStyle; return oldBackupStyle || newBackupStyle;
} }
export function isFailedBackupName(filename: string) { export function isFailedDatabaseBackupName(filename: string) {
return filename.match(/^immich-db-backup-.*\.sql\.gz\.tmp$/); return filename.match(/^immich-db-backup-.*\.sql\.gz\.tmp$/);
} }
@@ -137,7 +137,7 @@ export async function buildPostgresLaunchArguments(
}; };
} }
export async function createBackup( export async function createDatabaseBackup(
{ logger, storage, process: processRepository, ...pgRepos }: BackupRepos, { logger, storage, process: processRepository, ...pgRepos }: BackupRepos,
filenamePrefix: string = '', filenamePrefix: string = '',
): Promise<void> { ): Promise<void> {
@@ -179,7 +179,7 @@ export async function createBackup(
logger.log(`Database Backup Success`); logger.log(`Database Backup Success`);
} }
export async function restoreBackup( export async function restoreDatabaseBackup(
{ logger, storage, process: processRepository, ...pgRepos }: BackupRepos, { logger, storage, process: processRepository, ...pgRepos }: BackupRepos,
filename: string, filename: string,
progressCb?: (action: 'backup' | 'restore', progress: number) => void, progressCb?: (action: 'backup' | 'restore', progress: number) => void,
@@ -188,7 +188,7 @@ export async function restoreBackup(
let complete = false; let complete = false;
try { try {
if (!isValidBackupName(filename)) { if (!isValidDatabaseBackupName(filename)) {
throw new Error('Invalid backup file format!'); throw new Error('Invalid backup file format!');
} }
@@ -202,7 +202,7 @@ export async function restoreBackup(
progressCb?.('backup', 0.05); progressCb?.('backup', 0.05);
await createBackup({ logger, storage, process: processRepository, ...pgRepos }, 'restore-point-'); await createDatabaseBackup({ logger, storage, process: processRepository, ...pgRepos }, 'restore-point-');
logger.log(`Database Restore Starting. Database Version: ${databaseMajorVersion}`); logger.log(`Database Restore Starting. Database Version: ${databaseMajorVersion}`);
@@ -266,32 +266,32 @@ export async function restoreBackup(
logger.log(`Database Restore Success`); logger.log(`Database Restore Success`);
} }
export async function deleteBackups({ storage }: Pick<BackupRepos, 'storage'>, files: string[]): Promise<void> { export async function deleteDatabaseBackup({ storage }: Pick<BackupRepos, 'storage'>, files: string[]): Promise<void> {
const backupsFolder = StorageCore.getBaseFolder(StorageFolder.Backups); const backupsFolder = StorageCore.getBaseFolder(StorageFolder.Backups);
if (files.some((filename) => !isValidBackupName(filename))) { if (files.some((filename) => !isValidDatabaseBackupName(filename))) {
throw new BadRequestException('Invalid backup name!'); throw new BadRequestException('Invalid backup name!');
} }
await Promise.all(files.map((filename) => storage.unlink(path.join(backupsFolder, filename)))); await Promise.all(files.map((filename) => storage.unlink(path.join(backupsFolder, filename))));
} }
export async function listBackups({ storage }: Pick<BackupRepos, 'storage'>): Promise<string[]> { export async function listDatabaseBackups({ storage }: Pick<BackupRepos, 'storage'>): Promise<string[]> {
const backupsFolder = StorageCore.getBaseFolder(StorageFolder.Backups); const backupsFolder = StorageCore.getBaseFolder(StorageFolder.Backups);
const files = await storage.readdir(backupsFolder); const files = await storage.readdir(backupsFolder);
return files return files
.filter((fn) => isValidBackupName(fn)) .filter((fn) => isValidDatabaseBackupName(fn))
.toSorted((a, b) => (a.startsWith('uploaded-') === b.startsWith('uploaded-') ? a.localeCompare(b) : 1)) .toSorted((a, b) => (a.startsWith('uploaded-') === b.startsWith('uploaded-') ? a.localeCompare(b) : 1))
.toReversed(); .toReversed();
} }
export async function uploadBackup( export async function uploadDatabaseBackup(
{ storage }: Pick<BackupRepos, 'storage'>, { storage }: Pick<BackupRepos, 'storage'>,
file: Express.Multer.File, file: Express.Multer.File,
): Promise<void> { ): Promise<void> {
const backupsFolder = StorageCore.getBaseFolder(StorageFolder.Backups); const backupsFolder = StorageCore.getBaseFolder(StorageFolder.Backups);
const fn = basename(file.originalname); const fn = basename(file.originalname);
if (!isValidBackupName(fn)) { if (!isValidDatabaseBackupName(fn)) {
throw new BadRequestException('Invalid backup name!'); throw new BadRequestException('Invalid backup name!');
} }
@@ -299,8 +299,8 @@ export async function uploadBackup(
await storage.createOrOverwriteFile(path, file.buffer); await storage.createOrOverwriteFile(path, file.buffer);
} }
export function downloadBackup(fileName: string) { export function downloadDatabaseBackup(fileName: string) {
if (!isValidBackupName(fileName)) { if (!isValidDatabaseBackupName(fileName)) {
throw new BadRequestException('Invalid backup name!'); throw new BadRequestException('Invalid backup name!');
} }