From d7e0f0e70eb80b2018ec2113d175ef4ca994941d Mon Sep 17 00:00:00 2001 From: Lorenzo Montanari Date: Tue, 11 Mar 2025 12:30:43 +0100 Subject: [PATCH] feat(web): exposed a job to manually trigger database backup procedures (#16622) * feat(web): exposed a new job to create a manual database backup * chore(server): added a new test case * chore(server): moved job to backup db into the create job popup * remove irrelevant change * openapi * chore: formatting * docs: trigger backup documentation --------- Co-authored-by: Lorenzo Montanari <13736036+l0ll098@users.noreply.github.com> Co-authored-by: Alex Tran Co-authored-by: Zack Pollard --- docs/docs/administration/backup-and-restore.md | 7 +++++++ mobile/openapi/lib/model/manual_job_name.dart | 3 +++ open-api/immich-openapi-specs.json | 3 ++- open-api/typescript-sdk/src/fetch-client.ts | 3 ++- server/src/enum.ts | 1 + server/src/services/job.service.spec.ts | 8 ++++++++ server/src/services/job.service.ts | 4 ++++ web/src/routes/admin/jobs-status/+page.svelte | 1 + 8 files changed, 28 insertions(+), 2 deletions(-) diff --git a/docs/docs/administration/backup-and-restore.md b/docs/docs/administration/backup-and-restore.md index 1b5cdba19a..817a7dca6d 100644 --- a/docs/docs/administration/backup-and-restore.md +++ b/docs/docs/administration/backup-and-restore.md @@ -30,6 +30,13 @@ As mentioned above, you should make your own backup of these together with the a You can adjust the schedule and amount of kept backups in the [admin settings](http://my.immich.app/admin/system-settings?isOpen=backup). By default, Immich will keep the last 14 backups and create a new backup every day at 2:00 AM. +#### Trigger Backup + +You are able to trigger a backup in the [admin job status page](http://my.immich.app/admin/jobs-status). +Visit the page, open the "Create job" modal from the top right, select "Backup Database" and click "Confirm". +A job will run and trigger a backup, you can verify this worked correctly by checking the logs or the backup folder. +This backup will count towards the last X backups that will be kept based on your settings. + #### Restoring We hope to make restoring simpler in future versions, for now you can find the backups in the `UPLOAD_LOCATION/backups` folder on your host. diff --git a/mobile/openapi/lib/model/manual_job_name.dart b/mobile/openapi/lib/model/manual_job_name.dart index 71c60d8e64..311215ad9e 100644 --- a/mobile/openapi/lib/model/manual_job_name.dart +++ b/mobile/openapi/lib/model/manual_job_name.dart @@ -28,6 +28,7 @@ class ManualJobName { static const userCleanup = ManualJobName._(r'user-cleanup'); static const memoryCleanup = ManualJobName._(r'memory-cleanup'); static const memoryCreate = ManualJobName._(r'memory-create'); + static const backupDatabase = ManualJobName._(r'backup-database'); /// List of all possible values in this [enum][ManualJobName]. static const values = [ @@ -36,6 +37,7 @@ class ManualJobName { userCleanup, memoryCleanup, memoryCreate, + backupDatabase, ]; static ManualJobName? fromJson(dynamic value) => ManualJobNameTypeTransformer().decode(value); @@ -79,6 +81,7 @@ class ManualJobNameTypeTransformer { case r'user-cleanup': return ManualJobName.userCleanup; case r'memory-cleanup': return ManualJobName.memoryCleanup; case r'memory-create': return ManualJobName.memoryCreate; + case r'backup-database': return ManualJobName.backupDatabase; default: if (!allowNull) { throw ArgumentError('Unknown enum value to decode: $data'); diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index d503e565df..49c6c6ef30 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -9918,7 +9918,8 @@ "tag-cleanup", "user-cleanup", "memory-cleanup", - "memory-create" + "memory-create", + "backup-database" ], "type": "string" }, diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index 85f80eec99..4c058371f4 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -3580,7 +3580,8 @@ export enum ManualJobName { TagCleanup = "tag-cleanup", UserCleanup = "user-cleanup", MemoryCleanup = "memory-cleanup", - MemoryCreate = "memory-create" + MemoryCreate = "memory-create", + BackupDatabase = "backup-database" } export enum JobName { ThumbnailGeneration = "thumbnailGeneration", diff --git a/server/src/enum.ts b/server/src/enum.ts index 55e435a70b..6ebd1906f7 100644 --- a/server/src/enum.ts +++ b/server/src/enum.ts @@ -237,6 +237,7 @@ export enum ManualJobName { USER_CLEANUP = 'user-cleanup', MEMORY_CLEANUP = 'memory-cleanup', MEMORY_CREATE = 'memory-create', + BACKUP_DATABASE = 'backup-database', } export enum AssetPathType { diff --git a/server/src/services/job.service.spec.ts b/server/src/services/job.service.spec.ts index 42010d598c..134a86b69f 100644 --- a/server/src/services/job.service.spec.ts +++ b/server/src/services/job.service.spec.ts @@ -195,6 +195,14 @@ describe(JobService.name, () => { expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.QUEUE_FACIAL_RECOGNITION, data: { force: false } }); }); + it('should handle a start backup database command', async () => { + mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); + + await sut.handleCommand(QueueName.BACKUP_DATABASE, { command: JobCommand.START, force: false }); + + expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.BACKUP_DATABASE, data: { force: false } }); + }); + it('should throw a bad request when an invalid queue is used', async () => { mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); diff --git a/server/src/services/job.service.ts b/server/src/services/job.service.ts index 342aec7a7a..2f180edd40 100644 --- a/server/src/services/job.service.ts +++ b/server/src/services/job.service.ts @@ -39,6 +39,10 @@ const asJobItem = (dto: JobCreateDto): JobItem => { return { name: JobName.MEMORIES_CREATE }; } + case ManualJobName.BACKUP_DATABASE: { + return { name: JobName.BACKUP_DATABASE }; + } + default: { throw new BadRequestException('Invalid job name'); } diff --git a/web/src/routes/admin/jobs-status/+page.svelte b/web/src/routes/admin/jobs-status/+page.svelte index a3e3d6eb04..21381081e0 100644 --- a/web/src/routes/admin/jobs-status/+page.svelte +++ b/web/src/routes/admin/jobs-status/+page.svelte @@ -46,6 +46,7 @@ { title: $t('admin.user_cleanup_job'), value: ManualJobName.UserCleanup }, { title: $t('admin.memory_cleanup_job'), value: ManualJobName.MemoryCleanup }, { title: $t('admin.memory_generate_job'), value: ManualJobName.MemoryCreate }, + { title: $t('admin.backup_database'), value: ManualJobName.BackupDatabase }, ].map(({ value, title }) => ({ id: value, label: title, value })); const handleCancel = () => (isOpen = false);