fix(backups): enforce authorization and safe retention

- Gate volume backup retention and S3 controls by update permission
- Preserve backup records when S3 deletion fails
- Share SFTP download streaming with consistent missing-file handling
- Handle schedule creation errors and link service database backups
This commit is contained in:
Andras Bacsai
2026-07-19 23:15:55 +02:00
parent 99a8a96e7f
commit 72a0a57f0e
13 changed files with 433 additions and 112 deletions
+45 -2
View File
@@ -17,6 +17,10 @@ use App\Models\SwarmDocker;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use RuntimeException;
use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Throwable;
function create_standalone_postgresql($environmentId, StandaloneDocker|SwarmDocker $destination, ?array $otherData = null, string $databaseImage = 'postgres:16-alpine'): StandalonePostgresql
{
@@ -194,6 +198,41 @@ function deleteBackupsLocally(string|array|null $filenames, Server $server, bool
$foldersToCheck->each(fn ($folder) => deleteEmptyBackupFolder($folder, $server));
}
function streamBackupFromServer(Server $server, string $filename, string $contentType): StreamedResponse
{
$disk = Storage::build([
'driver' => 'sftp',
'host' => $server->ip,
'port' => (int) $server->port,
'username' => $server->user,
'privateKey' => $server->privateKey->getKeyLocation(),
'root' => '/',
]);
if (! $disk->exists($filename)) {
throw new FileNotFoundException($filename);
}
return new StreamedResponse(function () use ($disk, $filename) {
if (ob_get_level()) {
ob_end_clean();
}
$stream = $disk->readStream($filename);
if ($stream === false || is_null($stream)) {
abort(500, 'Failed to open stream for the requested file.');
}
while (! feof($stream)) {
echo fread($stream, 2048);
flush();
}
fclose($stream);
}, 200, [
'Content-Type' => $contentType,
'Content-Disposition' => 'attachment; filename="'.basename($filename).'"',
]);
}
function deleteBackupsS3(string|array|null $filenames, S3Storage $s3): void
{
if (empty($filenames) || ! $s3) {
@@ -430,8 +469,12 @@ function deleteOldBackupsFromS3($backup): Collection
->all();
if (! empty($filesToDelete)) {
deleteBackupsS3($filesToDelete, $backup->s3);
$processedBackups = $backupsToDelete;
try {
deleteBackupsS3($filesToDelete, $backup->s3);
$processedBackups = $backupsToDelete;
} catch (Throwable $e) {
report($e);
}
}
return $processedBackups;