From 04bcf016199d86330c6f5f050668dcf3cce46c12 Mon Sep 17 00:00:00 2001
From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com>
Date: Mon, 13 Jan 2025 16:35:58 +0100
Subject: [PATCH 01/13] feat: DB migration for Backup retention
- rename number_of_backups_locally to database_backup_retention_amount_locally
- add backup retention days to local stored backups
- add s3 retention fields
---
...ds_to_scheduled_database_backups_table.php | 34 +++++++++++++++++++
1 file changed, 34 insertions(+)
create mode 100644 database/migrations/2025_01_13_130238_add_backup_retention_fields_to_scheduled_database_backups_table.php
diff --git a/database/migrations/2025_01_13_130238_add_backup_retention_fields_to_scheduled_database_backups_table.php b/database/migrations/2025_01_13_130238_add_backup_retention_fields_to_scheduled_database_backups_table.php
new file mode 100644
index 000000000..f5371ee7a
--- /dev/null
+++ b/database/migrations/2025_01_13_130238_add_backup_retention_fields_to_scheduled_database_backups_table.php
@@ -0,0 +1,34 @@
+renameColumn('number_of_backups_locally', 'database_backup_retention_amount_locally');
+ $table->integer('database_backup_retention_amount_locally')->default(0)->nullable(false)->change();
+ $table->integer('database_backup_retention_days_locally')->default(0)->nullable(false);
+
+ $table->integer('database_backup_retention_amount_s3')->default(0)->nullable(false);
+ $table->integer('database_backup_retention_days_s3')->default(0)->nullable(false);
+ $table->integer('database_backup_retention_max_storage_s3')->default(0)->nullable(false);
+ });
+ }
+
+ public function down()
+ {
+ Schema::table('scheduled_database_backups', function (Blueprint $table) {
+ $table->renameColumn('database_backup_retention_amount_locally', 'number_of_backups_locally')->nullable(true)->change();
+ $table->dropColumn([
+ 'database_backup_retention_days_locally',
+ 'database_backup_retention_amount_s3',
+ 'database_backup_retention_days_s3',
+ 'database_backup_retention_max_storage_s3',
+ ]);
+ });
+ }
+};
From e037ed738a344974fe92651b1d90d9e33436b8fa Mon Sep 17 00:00:00 2001
From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com>
Date: Mon, 13 Jan 2025 16:36:41 +0100
Subject: [PATCH 02/13] feat: UI for backup retention settings
---
.../project/database/backup-edit.blade.php | 38 ++++++++++++++++++-
1 file changed, 37 insertions(+), 1 deletion(-)
diff --git a/resources/views/livewire/project/database/backup-edit.blade.php b/resources/views/livewire/project/database/backup-edit.blade.php
index 59628636d..1f3ee6913 100644
--- a/resources/views/livewire/project/database/backup-edit.blade.php
+++ b/resources/views/livewire/project/database/backup-edit.blade.php
@@ -72,7 +72,43 @@
-
+
+
+
Settings
@if ($backup->database_type === 'App\Models\StandalonePostgresql' && $backup->database_id !== 0)
-
-
-
- @if (!$backup->dump_all)
-
- @endif
+
+
+
+ @if (!$backup->dump_all)
+
+ @endif
@elseif($backup->database_type === 'App\Models\StandaloneMongodb')
-
+
@elseif($backup->database_type === 'App\Models\StandaloneMysql')
-
-
-
- @if (!$backup->dump_all)
-
- @endif
+
+
+
+ @if (!$backup->dump_all)
+
+ @endif
@elseif($backup->database_type === 'App\Models\StandaloneMariadb')
-
-
-
- @if (!$backup->dump_all)
-
- @endif
+
+
+
+ @if (!$backup->dump_all)
+
+ @endif
@endif
-
+
Backup Retention Settings
-
- These settings control how long backups are kept.
-
- - Setting a value to 0 means unlimited retention.
- - The retention rules work independently and whichever limit is reached first will trigger a cleanup of the older backups.
-
-
+
+ - Setting a value to 0 means unlimited retention
+ - The retention rules work independently - whichever limit is reached first will trigger cleanup
+
-
+
Local Backup Retention
-
-
+
+
@if ($backup->save_s3)
-
-
S3 Storage Retention
-
-
-
-
-
+
+
S3 Storage Retention
+
+
+
+
+
@endif
From c03b629e85a409951a7a8d7b4a4653b6d2e27497 Mon Sep 17 00:00:00 2001
From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com>
Date: Mon, 13 Jan 2025 17:21:03 +0100
Subject: [PATCH 08/13] fix: deletion of single backup
---
.../Project/Database/BackupExecutions.php | 18 +++++++++++-------
1 file changed, 11 insertions(+), 7 deletions(-)
diff --git a/app/Livewire/Project/Database/BackupExecutions.php b/app/Livewire/Project/Database/BackupExecutions.php
index 9ed608ed0..526239eea 100644
--- a/app/Livewire/Project/Database/BackupExecutions.php
+++ b/app/Livewire/Project/Database/BackupExecutions.php
@@ -61,15 +61,19 @@ class BackupExecutions extends Component
? $execution->scheduledDatabaseBackup->database->service->destination->server
: $execution->scheduledDatabaseBackup->database->destination->server;
- deleteBackupsLocally($execution->filename, $server);
+ try {
+ deleteBackupsLocally($execution->filename, $server);
- if ($this->delete_backup_s3 && $execution->scheduledDatabaseBackup->s3) {
- deleteBackupsS3($execution->filename, $server, $execution->scheduledDatabaseBackup->s3);
+ if ($this->delete_backup_s3 && $execution->scheduledDatabaseBackup->s3) {
+ deleteBackupsS3($execution->filename, $execution->scheduledDatabaseBackup->s3);
+ }
+
+ $execution->delete();
+ $this->dispatch('success', 'Backup deleted.');
+ $this->refreshBackupExecutions();
+ } catch (\Exception $e) {
+ $this->dispatch('error', 'Failed to delete backup: '.$e->getMessage());
}
-
- $execution->delete();
- $this->dispatch('success', 'Backup deleted.');
- $this->refreshBackupExecutions();
}
public function download_file($exeuctionId)
From 3dfca4e4bd799f56c09f70bb9d63a8fb24b8e1c9 Mon Sep 17 00:00:00 2001
From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com>
Date: Mon, 13 Jan 2025 17:31:55 +0100
Subject: [PATCH 09/13] fix: backup job deletion - delete all backups from s3
and local
---
app/Livewire/Project/Database/BackupEdit.php | 32 +++++++++++++++----
.../Project/Database/BackupExecutions.php | 8 +++--
2 files changed, 30 insertions(+), 10 deletions(-)
diff --git a/app/Livewire/Project/Database/BackupEdit.php b/app/Livewire/Project/Database/BackupEdit.php
index 19b7987db..853b918b8 100644
--- a/app/Livewire/Project/Database/BackupEdit.php
+++ b/app/Livewire/Project/Database/BackupEdit.php
@@ -123,13 +123,29 @@ class BackupEdit extends Component
}
try {
- if ($this->delete_associated_backups_locally) {
- $filenames = $this->backup->executions->pluck('filename')->filter()->all();
- deleteBackupsLocally($filenames, $this->backup->server);
+ $server = null;
+ if ($this->backup->database instanceof \App\Models\ServiceDatabase) {
+ $server = $this->backup->database->service->destination->server;
+ } elseif ($this->backup->database->destination && $this->backup->database->destination->server) {
+ $server = $this->backup->database->destination->server;
}
- if ($this->delete_associated_backups_s3 && $this->backup->s3) {
- $filenames = $this->backup->executions->pluck('filename')->filter()->all();
- deleteBackupsS3($filenames, $this->backup->s3);
+
+ $filenames = $this->backup->executions()
+ ->whereNotNull('filename')
+ ->where('filename', '!=', '')
+ ->where('scheduled_database_backup_id', $this->backup->id)
+ ->pluck('filename')
+ ->filter()
+ ->all();
+
+ if (! empty($filenames)) {
+ if ($this->delete_associated_backups_locally && $server) {
+ deleteBackupsLocally($filenames, $server);
+ }
+
+ if ($this->delete_associated_backups_s3 && $this->backup->s3) {
+ deleteBackupsS3($filenames, $this->backup->s3);
+ }
}
$this->backup->delete();
@@ -145,7 +161,9 @@ class BackupEdit extends Component
} else {
return redirect()->route('project.database.backup.index', $this->parameters);
}
- } catch (\Throwable $e) {
+ } catch (\Exception $e) {
+ $this->dispatch('error', 'Failed to delete backup: '.$e->getMessage());
+
return handleError($e, $this);
}
}
diff --git a/app/Livewire/Project/Database/BackupExecutions.php b/app/Livewire/Project/Database/BackupExecutions.php
index 526239eea..7eef1a539 100644
--- a/app/Livewire/Project/Database/BackupExecutions.php
+++ b/app/Livewire/Project/Database/BackupExecutions.php
@@ -62,10 +62,12 @@ class BackupExecutions extends Component
: $execution->scheduledDatabaseBackup->database->destination->server;
try {
- deleteBackupsLocally($execution->filename, $server);
+ if ($execution->filename) {
+ deleteBackupsLocally($execution->filename, $server);
- if ($this->delete_backup_s3 && $execution->scheduledDatabaseBackup->s3) {
- deleteBackupsS3($execution->filename, $execution->scheduledDatabaseBackup->s3);
+ if ($this->delete_backup_s3 && $execution->scheduledDatabaseBackup->s3) {
+ deleteBackupsS3($execution->filename, $execution->scheduledDatabaseBackup->s3);
+ }
}
$execution->delete();
From 9eebeb924134ebe06f4d766f57f9fead18d7c14e Mon Sep 17 00:00:00 2001
From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com>
Date: Mon, 13 Jan 2025 18:39:22 +0100
Subject: [PATCH 10/13] fix: use new removeOldBackups function
- use the new removeOldBackups function
- only call removeOldBackups function when the backup is completed and also only if the backup is successful
---
app/Jobs/DatabaseBackupJob.php | 12 +++---------
1 file changed, 3 insertions(+), 9 deletions(-)
diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php
index b9be4aaa6..577c1f11a 100644
--- a/app/Jobs/DatabaseBackupJob.php
+++ b/app/Jobs/DatabaseBackupJob.php
@@ -299,7 +299,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
throw new \Exception('Unsupported database type');
}
$size = $this->calculate_size();
- $this->remove_old_backups();
if ($this->backup->save_s3) {
$this->upload_to_s3();
}
@@ -323,6 +322,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
$this->team?->notify(new BackupFailed($this->backup, $this->database, $this->backup_output, $database));
}
}
+ if ($this->backup_log && $this->backup_log->status === 'success') {
+ removeOldBackups($this->backup);
+ }
} catch (\Throwable $e) {
throw $e;
} finally {
@@ -457,14 +459,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
return instant_remote_process(["du -b $this->backup_location | cut -f1"], $this->server, false);
}
- private function remove_old_backups(): void
- {
- deleteOldBackupsLocally($this->backup);
- if ($this->backup->save_s3) {
- deleteOldBackupsFromS3($this->backup);
- }
- }
-
private function upload_to_s3(): void
{
try {
From 3347eb3a1a911903267f68c8feaae4c4340b4718 Mon Sep 17 00:00:00 2001
From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com>
Date: Mon, 13 Jan 2025 18:46:27 +0100
Subject: [PATCH 11/13] fix: retention functions and folder deletion for local
backups
- fix: Delete folder and parent folder if folders are empty when deleting local backups.
- fix: Do not remove executions from DB until both S3 and local backups have been deleted and successfully processed otherwise backups will never be deleted from s3.
- fix: Server ID could be null
---
bootstrap/helpers/databases.php | 114 ++++++++++++++++++--------------
1 file changed, 63 insertions(+), 51 deletions(-)
diff --git a/bootstrap/helpers/databases.php b/bootstrap/helpers/databases.php
index fbaae637a..90c18dc6d 100644
--- a/bootstrap/helpers/databases.php
+++ b/bootstrap/helpers/databases.php
@@ -12,6 +12,7 @@ use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
+use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Storage;
use Visus\Cuid2\Cuid2;
@@ -183,6 +184,9 @@ function deleteBackupsLocally(string|array|null $filenames, Server $server): voi
}
$quotedFiles = array_map(fn ($file) => "\"$file\"", $filenames);
instant_remote_process(['rm -f '.implode(' ', $quotedFiles)], $server, throwError: false);
+
+ $foldersToCheck = collect($filenames)->map(fn ($file) => dirname($file))->unique();
+ $foldersToCheck->each(fn ($folder) => deleteEmptyBackupFolder($folder, $server));
}
function deleteBackupsS3(string|array|null $filenames, S3Storage $s3): void
@@ -225,10 +229,27 @@ function deleteEmptyBackupFolder($folderPath, Server $server): void
}
}
-function deleteOldBackupsLocally($backup): void
+function removeOldBackups($backup): void
+{
+ try {
+ $processedBackups = deleteOldBackupsLocally($backup);
+
+ if ($backup->save_s3) {
+ $processedBackups = $processedBackups->merge(deleteOldBackupsFromS3($backup));
+ }
+
+ if ($processedBackups->isNotEmpty()) {
+ $backup->executions()->whereIn('id', $processedBackups->pluck('id'))->delete();
+ }
+ } catch (\Exception $e) {
+ throw $e;
+ }
+}
+
+function deleteOldBackupsLocally($backup): Collection
{
if (! $backup || ! $backup->executions) {
- return;
+ return collect();
}
$successfulBackups = $backup->executions()
@@ -237,20 +258,21 @@ function deleteOldBackupsLocally($backup): void
->get();
if ($successfulBackups->isEmpty()) {
- return;
+ return collect();
}
$retentionAmount = $backup->database_backup_retention_amount_locally;
$retentionDays = $backup->database_backup_retention_days_locally;
if ($retentionAmount === 0 && $retentionDays === 0) {
- return;
+ return collect();
}
$backupsToDelete = collect();
if ($retentionAmount > 0) {
- $backupsToDelete = $backupsToDelete->merge($successfulBackups->skip($retentionAmount));
+ $byAmount = $successfulBackups->skip($retentionAmount);
+ $backupsToDelete = $backupsToDelete->merge($byAmount);
}
if ($retentionDays > 0) {
@@ -260,35 +282,36 @@ function deleteOldBackupsLocally($backup): void
}
$backupsToDelete = $backupsToDelete->unique('id');
- $foldersToCheck = collect();
+ $processedBackups = collect();
- $backupsToDelete->chunk(10)->each(function ($chunk) use ($backup, &$foldersToCheck) {
- $executionIds = [];
- $filesToDelete = [];
+ $server = null;
+ if ($backup->database_type === \App\Models\ServiceDatabase::class) {
+ $server = $backup->database->service->server;
+ } else {
+ $server = $backup->database->destination->server;
+ }
- foreach ($chunk as $execution) {
- if ($execution->filename) {
- $filesToDelete[] = $execution->filename;
- $executionIds[] = $execution->id;
- $foldersToCheck->push(dirname($execution->filename));
- }
- }
+ if (! $server) {
+ return collect();
+ }
- if (! empty($filesToDelete)) {
- deleteBackupsLocally($filesToDelete, $backup->server);
- if (! empty($executionIds)) {
- $backup->executions()->whereIn('id', $executionIds)->delete();
- }
- }
- });
+ $filesToDelete = $backupsToDelete
+ ->filter(fn ($execution) => ! empty($execution->filename))
+ ->pluck('filename')
+ ->all();
- $foldersToCheck->unique()->each(fn ($folder) => deleteEmptyBackupFolder($folder, $backup->server));
+ if (! empty($filesToDelete)) {
+ deleteBackupsLocally($filesToDelete, $server);
+ $processedBackups = $backupsToDelete;
+ }
+
+ return $processedBackups;
}
-function deleteOldBackupsFromS3($backup): void
+function deleteOldBackupsFromS3($backup): Collection
{
if (! $backup || ! $backup->executions || ! $backup->s3) {
- return;
+ return collect();
}
$successfulBackups = $backup->executions()
@@ -297,7 +320,7 @@ function deleteOldBackupsFromS3($backup): void
->get();
if ($successfulBackups->isEmpty()) {
- return;
+ return collect();
}
$retentionAmount = $backup->database_backup_retention_amount_s3;
@@ -305,13 +328,14 @@ function deleteOldBackupsFromS3($backup): void
$maxStorageGB = $backup->database_backup_retention_max_storage_s3;
if ($retentionAmount === 0 && $retentionDays === 0 && $maxStorageGB === 0) {
- return;
+ return collect();
}
$backupsToDelete = collect();
if ($retentionAmount > 0) {
- $backupsToDelete = $backupsToDelete->merge($successfulBackups->skip($retentionAmount));
+ $byAmount = $successfulBackups->skip($retentionAmount);
+ $backupsToDelete = $backupsToDelete->merge($byAmount);
}
if ($retentionDays > 0) {
@@ -337,31 +361,19 @@ function deleteOldBackupsFromS3($backup): void
}
$backupsToDelete = $backupsToDelete->unique('id');
- $foldersToCheck = collect();
+ $processedBackups = collect();
- $backupsToDelete->chunk(10)->each(function ($chunk) use ($backup, &$foldersToCheck) {
- $executionIds = [];
- $filesToDelete = [];
+ $filesToDelete = $backupsToDelete
+ ->filter(fn ($execution) => ! empty($execution->filename))
+ ->pluck('filename')
+ ->all();
- foreach ($chunk as $execution) {
- if ($execution->filename) {
- $filesToDelete[] = $execution->filename;
- $executionIds[] = $execution->id;
- $foldersToCheck->push(dirname($execution->filename));
- }
- }
+ if (! empty($filesToDelete)) {
+ deleteBackupsS3($filesToDelete, $backup->s3);
+ $processedBackups = $backupsToDelete;
+ }
- if (! empty($filesToDelete)) {
- deleteBackupsS3($filesToDelete, $backup->s3);
- if (! empty($executionIds)) {
- $backup->executions()
- ->whereIn('id', $executionIds)
- ->update(['s3_backup_deleted_at' => now()]);
- }
- }
- });
-
- $foldersToCheck->unique()->each(fn ($folder) => deleteEmptyBackupFolder($folder, $backup->server));
+ return $processedBackups;
}
function isPublicPortAlreadyUsed(Server $server, int $port, ?string $id = null): bool
From e9f691bf4527d650ea3f500fa63fd8c2ae309516 Mon Sep 17 00:00:00 2001
From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com>
Date: Mon, 13 Jan 2025 19:49:12 +0100
Subject: [PATCH 12/13] fix: storage retention setting
- feat: add storage retention to local storage as well
- fix: UI input for max storage now allows exact decimals so MB input is now also possible
- fix: Database column is now decimal instead of integer
- fix: variable naming of storage check no longer overwrites $backup - renamed it to $backupExecution
---
app/Livewire/Project/Database/BackupEdit.php | 9 +++--
bootstrap/helpers/databases.php | 35 ++++++++++++++++---
...ds_to_scheduled_database_backups_table.php | 4 ++-
.../project/database/backup-edit.blade.php | 15 +++++++-
4 files changed, 54 insertions(+), 9 deletions(-)
diff --git a/app/Livewire/Project/Database/BackupEdit.php b/app/Livewire/Project/Database/BackupEdit.php
index 853b918b8..6ebbf951e 100644
--- a/app/Livewire/Project/Database/BackupEdit.php
+++ b/app/Livewire/Project/Database/BackupEdit.php
@@ -49,14 +49,17 @@ class BackupEdit extends Component
#[Validate(['required', 'integer'])]
public ?int $databaseBackupRetentionDaysLocally = 0;
+ #[Validate(['required', 'numeric', 'min:0'])]
+ public ?float $databaseBackupRetentionMaxStorageLocally = 0;
+
#[Validate(['required', 'integer'])]
public ?int $databaseBackupRetentionAmountS3 = 0;
#[Validate(['required', 'integer'])]
public ?int $databaseBackupRetentionDaysS3 = 0;
- #[Validate(['required', 'integer'])]
- public ?int $databaseBackupRetentionMaxStorageS3 = 0;
+ #[Validate(['required', 'numeric', 'min:0'])]
+ public ?float $databaseBackupRetentionMaxStorageS3 = 0;
#[Validate(['required', 'boolean'])]
public bool $saveS3 = false;
@@ -87,6 +90,7 @@ class BackupEdit extends Component
$this->backup->frequency = $this->frequency;
$this->backup->database_backup_retention_amount_locally = $this->databaseBackupRetentionAmountLocally;
$this->backup->database_backup_retention_days_locally = $this->databaseBackupRetentionDaysLocally;
+ $this->backup->database_backup_retention_max_storage_locally = $this->databaseBackupRetentionMaxStorageLocally;
$this->backup->database_backup_retention_amount_s3 = $this->databaseBackupRetentionAmountS3;
$this->backup->database_backup_retention_days_s3 = $this->databaseBackupRetentionDaysS3;
$this->backup->database_backup_retention_max_storage_s3 = $this->databaseBackupRetentionMaxStorageS3;
@@ -102,6 +106,7 @@ class BackupEdit extends Component
$this->timezone = data_get($this->backup->server(), 'settings.server_timezone', 'Instance timezone');
$this->databaseBackupRetentionAmountLocally = $this->backup->database_backup_retention_amount_locally;
$this->databaseBackupRetentionDaysLocally = $this->backup->database_backup_retention_days_locally;
+ $this->databaseBackupRetentionMaxStorageLocally = $this->backup->database_backup_retention_max_storage_locally;
$this->databaseBackupRetentionAmountS3 = $this->backup->database_backup_retention_amount_s3;
$this->databaseBackupRetentionDaysS3 = $this->backup->database_backup_retention_days_s3;
$this->databaseBackupRetentionMaxStorageS3 = $this->backup->database_backup_retention_max_storage_s3;
diff --git a/bootstrap/helpers/databases.php b/bootstrap/helpers/databases.php
index 90c18dc6d..6a834ee6f 100644
--- a/bootstrap/helpers/databases.php
+++ b/bootstrap/helpers/databases.php
@@ -263,8 +263,9 @@ function deleteOldBackupsLocally($backup): Collection
$retentionAmount = $backup->database_backup_retention_amount_locally;
$retentionDays = $backup->database_backup_retention_days_locally;
+ $maxStorageGB = $backup->database_backup_retention_max_storage_locally;
- if ($retentionAmount === 0 && $retentionDays === 0) {
+ if ($retentionAmount === 0 && $retentionDays === 0 && $maxStorageGB === 0) {
return collect();
}
@@ -281,6 +282,26 @@ function deleteOldBackupsLocally($backup): Collection
$backupsToDelete = $backupsToDelete->merge($oldBackups);
}
+ if ($maxStorageGB > 0) {
+ $maxStorageBytes = $maxStorageGB * pow(1024, 3);
+ $totalSize = 0;
+ $backupsOverLimit = collect();
+
+ $backupsToCheck = $successfulBackups->skip(1);
+
+ foreach ($backupsToCheck as $backupExecution) {
+ $totalSize += (int) $backupExecution->size;
+ if ($totalSize > $maxStorageBytes) {
+ $backupsOverLimit = $successfulBackups->filter(
+ fn ($b) => $b->created_at->utc() <= $backupExecution->created_at->utc()
+ )->skip(1);
+ break;
+ }
+ }
+
+ $backupsToDelete = $backupsToDelete->merge($backupsOverLimit);
+ }
+
$backupsToDelete = $backupsToDelete->unique('id');
$processedBackups = collect();
@@ -345,14 +366,18 @@ function deleteOldBackupsFromS3($backup): Collection
}
if ($maxStorageGB > 0) {
- $maxStorageBytes = $maxStorageGB * 1024 * 1024 * 1024;
+ $maxStorageBytes = $maxStorageGB * pow(1024, 3);
$totalSize = 0;
$backupsOverLimit = collect();
- foreach ($successfulBackups as $backup) {
- $totalSize += (int) $backup->size;
+ $backupsToCheck = $successfulBackups->skip(1);
+
+ foreach ($backupsToCheck as $backupExecution) {
+ $totalSize += (int) $backupExecution->size;
if ($totalSize > $maxStorageBytes) {
- $backupsOverLimit = $successfulBackups->filter(fn ($b) => $b->created_at->utc() <= $backup->created_at->utc());
+ $backupsOverLimit = $successfulBackups->filter(
+ fn ($b) => $b->created_at->utc() <= $backupExecution->created_at->utc()
+ )->skip(1);
break;
}
}
diff --git a/database/migrations/2025_01_13_130238_add_backup_retention_fields_to_scheduled_database_backups_table.php b/database/migrations/2025_01_13_130238_add_backup_retention_fields_to_scheduled_database_backups_table.php
index f5371ee7a..f06bc367e 100644
--- a/database/migrations/2025_01_13_130238_add_backup_retention_fields_to_scheduled_database_backups_table.php
+++ b/database/migrations/2025_01_13_130238_add_backup_retention_fields_to_scheduled_database_backups_table.php
@@ -12,10 +12,11 @@ return new class extends Migration
$table->renameColumn('number_of_backups_locally', 'database_backup_retention_amount_locally');
$table->integer('database_backup_retention_amount_locally')->default(0)->nullable(false)->change();
$table->integer('database_backup_retention_days_locally')->default(0)->nullable(false);
+ $table->decimal('database_backup_retention_max_storage_locally', 17, 7)->default(0)->nullable(false);
$table->integer('database_backup_retention_amount_s3')->default(0)->nullable(false);
$table->integer('database_backup_retention_days_s3')->default(0)->nullable(false);
- $table->integer('database_backup_retention_max_storage_s3')->default(0)->nullable(false);
+ $table->decimal('database_backup_retention_max_storage_s3', 17, 7)->default(0)->nullable(false);
});
}
@@ -25,6 +26,7 @@ return new class extends Migration
$table->renameColumn('database_backup_retention_amount_locally', 'number_of_backups_locally')->nullable(true)->change();
$table->dropColumn([
'database_backup_retention_days_locally',
+ 'database_backup_retention_max_storage_locally',
'database_backup_retention_amount_s3',
'database_backup_retention_days_s3',
'database_backup_retention_max_storage_s3',
diff --git a/resources/views/livewire/project/database/backup-edit.blade.php b/resources/views/livewire/project/database/backup-edit.blade.php
index 4f3ebe8f8..f1f604a7d 100644
--- a/resources/views/livewire/project/database/backup-edit.blade.php
+++ b/resources/views/livewire/project/database/backup-edit.blade.php
@@ -75,6 +75,13 @@
+
@@ -84,7 +91,13 @@