diff --git a/data/web/admin/system.php b/data/web/admin/system.php index ed6f64bed..4af2e4b4b 100644 --- a/data/web/admin/system.php +++ b/data/web/admin/system.php @@ -126,6 +126,7 @@ $template_data = [ 'mbox_templates' => $mbox_templates, 'backup_timezone' => getenv('TZ') ?: 'UTC', 'backup_hostname' => getenv('MAILCOW_HOSTNAME') ?: '', + 'backup_demo_mode' => $DEMO_MODE, 'lang_admin' => json_encode($lang['admin']), 'lang_datatables' => json_encode($lang['datatables']) ]; diff --git a/data/web/inc/lib/mailcow/Backup/BackupModule.php b/data/web/inc/lib/mailcow/Backup/BackupModule.php index c7fc3eedd..034baf846 100644 --- a/data/web/inc/lib/mailcow/Backup/BackupModule.php +++ b/data/web/inc/lib/mailcow/Backup/BackupModule.php @@ -39,7 +39,8 @@ use Redis; * process_get_return(BackupModule::fromGlobals()->api->getSettings()); * echo json_encode(BackupModule::create($pdo, Context::system(), $config, $redis)->api->export($domain, $page)); // backup.php * - * $config: timezone (TZ of mailcow, for the schedule), alias_disable_external_domains ($ALIAS_DISABLE_EXTERNAL_DOMAINS). + * $config: timezone (TZ of mailcow, for the schedule), alias_disable_external_domains ($ALIAS_DISABLE_EXTERNAL_DOMAINS), + * demo_mode ($DEMO_MODE: the API changes nothing). * $redis: for DKIM keys, rate limits and the domain map (export and restore). */ class BackupModule @@ -73,15 +74,17 @@ class BackupModule $restore = new RestoreService($restoreRepo, $jobRepo, $groups, $sogo, $dovecot, $legacy, (bool)($config['alias_disable_external_domains'] ?? false)); $domainRestore = new DomainRestoreService($restoreRepo, $jobRepo, $groups, $legacy); return new self($settings, $jobs, $export, $restore, $domainRestore, - new BackupApiController($settings, $jobs, $export, $restore, $domainRestore, new ApiResponse(Settings::SECRETS))); + new BackupApiController($settings, $jobs, $export, $restore, $domainRestore, new ApiResponse(Settings::SECRETS), + (bool)($config['demo_mode'] ?? false))); } - /** Legacy entry point for procedural callers: reads $pdo, $redis, $_SESSION, TZ and $ALIAS_DISABLE_EXTERNAL_DOMAINS */ + /** Legacy entry point for procedural callers: reads $pdo, $redis, $_SESSION, TZ, $ALIAS_DISABLE_EXTERNAL_DOMAINS and $DEMO_MODE */ public static function fromGlobals(?Context $context = null): self { return self::create($GLOBALS['pdo'], $context ?? Context::fromSession($_SESSION ?? []), [ 'timezone' => getenv('TZ') ?: 'UTC', 'alias_disable_external_domains' => ($GLOBALS['ALIAS_DISABLE_EXTERNAL_DOMAINS'] ?? false) === true, - ], ($GLOBALS['redis'] ?? null) instanceof Redis ? $GLOBALS['redis'] : null); + 'demo_mode' => ($GLOBALS['DEMO_MODE'] ?? false) === true, + ],($GLOBALS['redis'] ?? null) instanceof Redis ? $GLOBALS['redis'] : null); } } diff --git a/data/web/inc/lib/mailcow/Backup/Controller/BackupApiController.php b/data/web/inc/lib/mailcow/Backup/Controller/BackupApiController.php index 38cd4b764..48619c3c8 100644 --- a/data/web/inc/lib/mailcow/Backup/Controller/BackupApiController.php +++ b/data/web/inc/lib/mailcow/Backup/Controller/BackupApiController.php @@ -23,7 +23,8 @@ use PDOException; class BackupApiController { public function __construct(private SettingsService $settings, private JobService $jobs, private ExportService $export, - private RestoreService $restore, private DomainRestoreService $domainRestore, private ApiResponse $response) + private RestoreService $restore, private DomainRestoreService $domainRestore, private ApiResponse $response, + private bool $demoMode = false) { } @@ -147,11 +148,15 @@ class BackupApiController /** * Runs one write call: $work gets the log entry and returns the result for process_*_return(). * A UserError becomes a danger message, a database error stops the call with one `mysql_error` message. + * In demo mode no write call runs. */ private function handle(string $action, string $object, mixed $data, callable $work): bool { $log = ['backup', $action, $object, $data]; try { + if ($this->demoMode) { + throw new UserError('demo_mode_enabled'); + } $result = $work($log); } catch (UserError $e) { $result = $this->response->error($e, $log); diff --git a/data/web/inc/restore.inc.php b/data/web/inc/restore.inc.php index e35214495..3f87ec97f 100644 --- a/data/web/inc/restore.inc.php +++ b/data/web/inc/restore.inc.php @@ -13,6 +13,7 @@ $template_data = [ 'lang_datatables' => json_encode($lang['datatables']), 'backup_configured' => \Mailcow\Backup\BackupModule::fromGlobals()->settings->load()->configured(), 'backup_timezone' => getenv('TZ') ?: 'UTC', + 'backup_demo_mode' => $DEMO_MODE, ]; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; diff --git a/data/web/lang/lang.de-de.json b/data/web/lang/lang.de-de.json index eee94f5d3..02301ec11 100644 --- a/data/web/lang/lang.de-de.json +++ b/data/web/lang/lang.de-de.json @@ -607,6 +607,7 @@ "created_from_backup": "Aus dem Backup angelegt", "data_added": "Hinzugefügt", "deleted_domain_children": "Die Domain wurde gelöscht: Nur die ganze Domain lässt sich wiederherstellen.", + "demo_mode": "Demo Mode ist aktiviert: Backups, Wiederherstellungen und Änderungen an den Einstellungen sind gesperrt", "details": "Details", "domains": "Domains", "duration": "Dauer", diff --git a/data/web/lang/lang.en-gb.json b/data/web/lang/lang.en-gb.json index 6ffb36569..f8433cf5b 100644 --- a/data/web/lang/lang.en-gb.json +++ b/data/web/lang/lang.en-gb.json @@ -607,6 +607,7 @@ "created_from_backup": "Created from the backup", "data_added": "Added", "deleted_domain_children": "The domain was deleted: only the whole domain can be restored.", + "demo_mode": "Demo Mode is enabled: backups, restores and changes to the settings are disabled", "details": "Details", "domains": "Domains", "duration": "Duration", diff --git a/data/web/templates/admin/tab-config-backup.twig b/data/web/templates/admin/tab-config-backup.twig index 34f723bc3..3d1c8df69 100644 --- a/data/web/templates/admin/tab-config-backup.twig +++ b/data/web/templates/admin/tab-config-backup.twig @@ -13,6 +13,7 @@
{{ lang.backup.intro }} {{ lang.backup.intro_restore_link }}
+ {% if backup_demo_mode %}