fix(backups): pin S3 per volume execution and harden deletes

Store s3_storage_id on scheduled volume backup executions so retention
and recovery use the S3 that received the upload. Extract
DeleteScheduledVolumeBackup for UI and resource deletion, and isolate
database backup retention failures so cleanup errors do not fail the job.
This commit is contained in:
Andras Bacsai committed 2026-07-16 14:30:58 +02:00
1 parent 8cfa41a7d9
commit ac36a59088
14 files changed
+556 -77

No files matched your search

+16 -6
View File
@@ -63,7 +63,9 @@ class VolumeBackupJob implements ShouldBeEncrypted, ShouldQueue
throw new \RuntimeException('The storage backup resource, team, or server no longer exists.');
}
$this->execution = $this->backup->executions()->create();
$this->execution = $this->backup->executions()->create([
's3_storage_id' => $this->backup->save_s3 ? $this->backup->s3_storage_id : null,
]);
BackupCreated::dispatch($team->id);
$backupDirectory = backup_dir().'/volumes/'.str($team->name)->slug().'-'.$team->id.'/'.$target->uuid;
@@ -352,6 +354,7 @@ class VolumeBackupJob implements ShouldBeEncrypted, ShouldQueue
if ($this->backup->save_s3 && $this->backup->s3) {
$s3Executions = $this->backup->executions()
->with('s3')
->where('status', 'success')
->where('s3_uploaded', true)
->where('s3_storage_deleted', false)
@@ -363,11 +366,18 @@ class VolumeBackupJob implements ShouldBeEncrypted, ShouldQueue
$this->backup->retention_max_storage_s3,
);
$filenames = $s3Executions->pluck('filename')->filter()->all();
if ($filenames !== []) {
deleteBackupsS3($filenames, $this->backup->s3);
$this->backup->executions()->whereKey($s3Executions->pluck('id')->all())
->update(['s3_storage_deleted' => true]);
foreach ($s3Executions->groupBy('s3_storage_id') as $executions) {
$s3 = $executions->first()->s3;
if (! $s3) {
throw new \RuntimeException('The S3 storage used by an existing backup is unavailable.');
}
$filenames = $executions->pluck('filename')->filter()->all();
if ($filenames !== []) {
deleteBackupsS3($filenames, $s3);
$this->backup->executions()->whereKey($executions->pluck('id')->all())
->update(['s3_storage_deleted' => true]);
}
}
}