From 0da15d483ca703bb5b3554484d095ed6c6808c1b Mon Sep 17 00:00:00 2001 From: Lance Pioch Date: Fri, 6 Feb 2026 10:32:12 -0500 Subject: [PATCH] Add option to delete S3 backups from storage on server deletion When deleting a server, a checkbox now appears in the confirmation modal if unlocked backups exist, allowing admins to also remove them from storage via DeleteBackupService. Locked backups are always preserved. --- .../Resources/Servers/Pages/EditServer.php | 17 +++++++--- .../Servers/ServerDeletionService.php | 32 ++++++++++++++++++- lang/en/admin/server.php | 1 + 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/app/Filament/Admin/Resources/Servers/Pages/EditServer.php b/app/Filament/Admin/Resources/Servers/Pages/EditServer.php index a17cb979a..1bfa799ce 100644 --- a/app/Filament/Admin/Resources/Servers/Pages/EditServer.php +++ b/app/Filament/Admin/Resources/Servers/Pages/EditServer.php @@ -30,6 +30,7 @@ use App\Traits\Filament\CanCustomizeTabs; use Exception; use Filament\Actions\Action; use Filament\Actions\ActionGroup; +use Filament\Forms\Components\Checkbox; use Filament\Forms\Components\CheckboxList; use Filament\Forms\Components\FileUpload; use Filament\Forms\Components\Hidden; @@ -1106,9 +1107,13 @@ class EditServer extends EditRecord ->modalHeading(trans('filament-actions::delete.single.modal.heading', ['label' => $this->getRecordTitle()])) ->modalSubmitActionLabel(trans('filament-actions::delete.single.label')) ->requiresConfirmation() - ->action(function (Server $server, ServerDeletionService $service) { + ->form(fn (Server $server) => $server->backups()->where('is_locked', false)->exists() ? [ + Checkbox::make('delete_backups') + ->label(trans('admin/server.delete_backups', ['count' => $server->backups()->where('is_locked', false)->count()])), + ] : []) + ->action(function (array $data, Server $server, ServerDeletionService $service) { try { - $service->handle($server); + $service->withDeleteBackups($data['delete_backups'] ?? false)->handle($server); return redirect(ListServers::getUrl(panel: 'admin')); } catch (ConnectionException) { @@ -1132,9 +1137,13 @@ class EditServer extends EditRecord ->modalHeading(trans('filament-actions::force-delete.single.modal.heading', ['label' => $this->getRecordTitle()])) ->modalSubmitActionLabel(trans('filament-actions::force-delete.single.label')) ->requiresConfirmation() - ->action(function (Server $server, ServerDeletionService $service) { + ->form(fn (Server $server) => $server->backups()->where('is_locked', false)->exists() ? [ + Checkbox::make('delete_backups') + ->label(trans('admin/server.delete_backups', ['count' => $server->backups()->where('is_locked', false)->count()])), + ] : []) + ->action(function (array $data, Server $server, ServerDeletionService $service) { try { - $service->withForce()->handle($server); + $service->withForce()->withDeleteBackups($data['delete_backups'] ?? false)->handle($server); return redirect(ListServers::getUrl(panel: 'admin')); } catch (ConnectionException) { diff --git a/app/Services/Servers/ServerDeletionService.php b/app/Services/Servers/ServerDeletionService.php index 7f59d017c..3fe50d82b 100644 --- a/app/Services/Servers/ServerDeletionService.php +++ b/app/Services/Servers/ServerDeletionService.php @@ -5,6 +5,7 @@ namespace App\Services\Servers; use App\Exceptions\DisplayException; use App\Models\Server; use App\Repositories\Daemon\DaemonServerRepository; +use App\Services\Backups\DeleteBackupService; use App\Services\Databases\DatabaseManagementService; use Exception; use Illuminate\Database\ConnectionInterface; @@ -16,13 +17,16 @@ class ServerDeletionService { protected bool $force = false; + protected bool $deleteBackups = false; + /** * ServerDeletionService constructor. */ public function __construct( private ConnectionInterface $connection, private DaemonServerRepository $daemonServerRepository, - private DatabaseManagementService $databaseManagementService + private DatabaseManagementService $databaseManagementService, + private DeleteBackupService $deleteBackupService ) {} /** @@ -35,6 +39,16 @@ class ServerDeletionService return $this; } + /** + * Set if unlocked backups should be deleted from storage when the server is deleted. + */ + public function withDeleteBackups(bool $bool = true): self + { + $this->deleteBackups = $bool; + + return $this; + } + /** * Delete a server from the panel and remove any associated databases from hosts. * @@ -78,6 +92,22 @@ class ServerDeletionService } } + if ($this->deleteBackups) { + foreach ($server->backups()->where('is_locked', false)->get() as $backup) { + try { + $this->deleteBackupService->handle($backup); + } catch (Exception $exception) { + if (!$this->force) { + throw $exception; + } + + $backup->delete(); + + logger()->warning($exception); + } + } + } + $server->allocations()->update([ 'server_id' => null, 'notes' => null, diff --git a/lang/en/admin/server.php b/lang/en/admin/server.php index e0bb2beba..3991e98eb 100644 --- a/lang/en/admin/server.php +++ b/lang/en/admin/server.php @@ -98,6 +98,7 @@ return [ 'delete_db' => 'Are you sure you want to delete :name ?', 'delete_db_heading' => 'Delete Database?', 'backups' => 'Backups', + 'delete_backups' => 'Also delete :count backup(s) from storage', 'egg' => 'Egg', 'mounts' => 'Mounts', 'no_mounts' => 'No Mounts exist for this Node',