mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-11-10 18:41:01 +00:00
Merge branch 'feat/backup' into nightly
This commit is contained in:
9 files changed
+39
-5
No files matched your search
@@ -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'])
|
||||
];
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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';
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<div class="row mb-4">
|
||||
<div class="col-sm-9 offset-sm-3">
|
||||
<p class="text-muted">{{ lang.backup.intro }} <a href="/admin/restore">{{ lang.backup.intro_restore_link }}</a></p>
|
||||
{% if backup_demo_mode %}<div class="alert alert-warning">{{ lang.backup.demo_mode }}</div>{% endif %}
|
||||
|
||||
<div id="backup_status">
|
||||
<div class="backup-status-summary mb-2" aria-live="polite"><span class="text-muted">{{ lang.footer.loading }}</span></div>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<span>{{ lang.backup.restore_card }}</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if backup_demo_mode %}<div class="alert alert-warning">{{ lang.backup.demo_mode }}</div>{% endif %}
|
||||
<div class="backup-restore-hints"></div>
|
||||
{# left-aligned with the search of the toolbar below #}
|
||||
<div class="d-flex flex-wrap align-items-center gap-2 mb-3 px-2">
|
||||
|
||||
@@ -81,6 +81,26 @@ class BackupApiControllerTest extends TestCase
|
||||
$this->assertSame(['backup', 'delete', 'job', ['id' => [4, '7', 'x']]], $_SESSION['return'][4]['log']);
|
||||
}
|
||||
|
||||
public function testDemoModeChangesNothing(): void
|
||||
{
|
||||
$this->settings->expects($this->never())->method('save');
|
||||
foreach (['create', 'restore', 'cancel'] as $method) {
|
||||
$this->jobs->expects($this->never())->method($method);
|
||||
}
|
||||
$this->jobs->method('status')->willReturn(['configured' => false]);
|
||||
$api = new BackupApiController($this->settings, $this->jobs, $this->createMock(ExportService::class), $this->restore,
|
||||
$this->domainRestore, new ApiResponse(Settings::SECRETS), true);
|
||||
|
||||
$this->assertFalse($api->editSettings(['target' => 's3', 's3_endpoint' => 'https://s3.example.org']));
|
||||
$this->assertFalse($api->addJob(['type' => 'backup']));
|
||||
$this->assertFalse($api->addRestore(['snapshot' => 'abc']));
|
||||
$this->assertFalse($api->deleteJobs([4]));
|
||||
|
||||
$this->assertSame(array_fill(0, 4, ['danger', 'demo_mode_enabled']),
|
||||
array_map(fn(array $m): array => [$m['type'], $m['msg']], $_SESSION['return']));
|
||||
$this->assertSame(['configured' => false], $api->getStatus());
|
||||
}
|
||||
|
||||
public function testReads(): void
|
||||
{
|
||||
$this->jobs->method('get')->willReturnCallback(fn(int $id): ?Job => ($id === 4) ? self::job(4, 'backup') : null);
|
||||
|
||||
Reference in new issue
Block a user