0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-03-18 02:31:28 -05:00

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 <alex.tran1502@gmail.com>
Co-authored-by: Zack Pollard <zack@futo.org>
This commit is contained in:
Lorenzo Montanari 2025-03-11 12:30:43 +01:00 committed by GitHub
parent decc878267
commit d7e0f0e70e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 28 additions and 2 deletions

View file

@ -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.

View file

@ -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 = <ManualJobName>[
@ -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');

View file

@ -9918,7 +9918,8 @@
"tag-cleanup",
"user-cleanup",
"memory-cleanup",
"memory-create"
"memory-create",
"backup-database"
],
"type": "string"
},

View file

@ -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",

View file

@ -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 {

View file

@ -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 });

View file

@ -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');
}

View file

@ -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);