From 3c41597262793a91a970a249f37e252574d9933d Mon Sep 17 00:00:00 2001 From: Sebastian Almberg <83243306+Sebbeben@users.noreply.github.com> Date: Fri, 20 Feb 2026 23:41:52 +0100 Subject: [PATCH] Add Docker backup support: download button, SQLite restore fix, decouple from auto-update - Decouple backup creation/restore UI from can_auto_update so Docker and other non-git installations can use backup features - Add backup download endpoint for saving backups externally - Fix SQLite restore to use configured DATABASE_URL path instead of hardcoded var/app.db (affects Docker and custom SQLite paths) - Show Docker-specific warning about var/backups/ not being persisted - Pass is_docker flag to template via InstallationTypeDetector --- src/Controller/UpdateManagerController.php | 24 +++++++++++++++++++ src/Services/System/BackupManager.php | 13 +++++----- .../admin/update_manager/index.html.twig | 19 ++++++++++++--- translations/messages.en.xlf | 12 ++++++++++ 4 files changed, 59 insertions(+), 9 deletions(-) diff --git a/src/Controller/UpdateManagerController.php b/src/Controller/UpdateManagerController.php index 0afc7905..480e86a0 100644 --- a/src/Controller/UpdateManagerController.php +++ b/src/Controller/UpdateManagerController.php @@ -24,14 +24,17 @@ declare(strict_types=1); namespace App\Controller; use App\Services\System\BackupManager; +use App\Services\System\InstallationTypeDetector; use App\Services\System\UpdateChecker; use App\Services\System\UpdateExecutor; use Shivas\VersioningBundle\Service\VersionManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\Routing\Attribute\Route; @@ -49,6 +52,7 @@ class UpdateManagerController extends AbstractController private readonly UpdateExecutor $updateExecutor, private readonly VersionManagerInterface $versionManager, private readonly BackupManager $backupManager, + private readonly InstallationTypeDetector $installationTypeDetector, #[Autowire(env: 'bool:DISABLE_WEB_UPDATES')] private readonly bool $webUpdatesDisabled = false, #[Autowire(env: 'bool:DISABLE_BACKUP_RESTORE')] @@ -101,6 +105,7 @@ class UpdateManagerController extends AbstractController 'backups' => $this->backupManager->getBackups(), 'web_updates_disabled' => $this->webUpdatesDisabled, 'backup_restore_disabled' => $this->backupRestoreDisabled, + 'is_docker' => $this->installationTypeDetector->isDocker(), ]); } @@ -388,6 +393,25 @@ class UpdateManagerController extends AbstractController return $this->redirectToRoute('admin_update_manager'); } + /** + * Download a backup file. + */ + #[Route('/backup/download/{filename}', name: 'admin_update_manager_backup_download', methods: ['GET'])] + public function downloadBackup(string $filename): BinaryFileResponse + { + $this->denyAccessUnlessGranted('@system.manage_updates'); + + $details = $this->backupManager->getBackupDetails($filename); + if (!$details) { + throw $this->createNotFoundException('Backup not found'); + } + + $response = new BinaryFileResponse($details['path']); + $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $details['file']); + + return $response; + } + /** * Restore from a backup. */ diff --git a/src/Services/System/BackupManager.php b/src/Services/System/BackupManager.php index 4946bc24..621b58d7 100644 --- a/src/Services/System/BackupManager.php +++ b/src/Services/System/BackupManager.php @@ -327,14 +327,14 @@ readonly class BackupManager */ private function restoreDatabaseFromBackup(string $tempDir): void { + // Get database connection params from Doctrine + $connection = $this->entityManager->getConnection(); + $params = $connection->getParams(); + $platform = $connection->getDatabasePlatform(); + // Check for SQL dump (MySQL/PostgreSQL) $sqlFile = $tempDir . '/database.sql'; if (file_exists($sqlFile)) { - // Import SQL using mysql/psql command directly - // First, get database connection params from Doctrine - $connection = $this->entityManager->getConnection(); - $params = $connection->getParams(); - $platform = $connection->getDatabasePlatform(); if ($platform instanceof AbstractMySQLPlatform) { // Use mysql command to import - need to use shell to handle input redirection @@ -403,7 +403,8 @@ readonly class BackupManager // Check for SQLite database file $sqliteFile = $tempDir . '/var/app.db'; if (file_exists($sqliteFile)) { - $targetDb = $this->projectDir . '/var/app.db'; + // Use the actual configured SQLite path from Doctrine, not a hardcoded path + $targetDb = $params['path'] ?? $this->projectDir . '/var/app.db'; $this->filesystem->copy($sqliteFile, $targetDb, true); return; } diff --git a/templates/admin/update_manager/index.html.twig b/templates/admin/update_manager/index.html.twig index 8afa4472..4b6ff237 100644 --- a/templates/admin/update_manager/index.html.twig +++ b/templates/admin/update_manager/index.html.twig @@ -377,17 +377,23 @@
|
- {% if status.can_auto_update and validation.valid and not backup_restore_disabled %}
+ {% if is_granted('@system.manage_updates') %}
+
+
+
+ {% endif %}
+ {% if not backup_restore_disabled and is_granted('@system.manage_updates') %}
|