9b03f98d0d
- removed snapshots go to a trash on the location and their data is only freed after the deletion delay, which is chosen when the key is created (default 7 days, 1-365) and cannot be changed afterwards; any admin can bring them back (occ backup:trash, backup:trash:restore, web UI) - up to three passphrase slots open the same master key (like LUKS), so one person leaving or dying does not lock the company out; slot changes are written to every location, announced to all admins and require a new recovery kit confirmation, while backups keep running - occ backup:key:slots, backup:key:slot:add|replace|remove; key init takes --label and --delete-delay-days - web UI: deletion delay and first slot at key creation, slot management, trash per location with restore; all behind password confirmation - README: key, slots (safe / CTO / head of IT), and protection against ransomware: object lock in compliance mode (e.g. 90 days), credentials without delete rights, provider snapshots, off-site copy
72 lines
2.5 KiB
PHP
72 lines
2.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
// SPDX-FileCopyrightText: 2026 Dillard Blom
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
namespace OCA\NgBackup\SetupCheck;
|
|
|
|
use OCA\NgBackup\Db\Run;
|
|
use OCA\NgBackup\Db\RunMapper;
|
|
use OCA\NgBackup\Service\AlertService;
|
|
use OCA\NgBackup\Service\BackupService;
|
|
use OCA\NgBackup\Service\KeyService;
|
|
use OCA\NgBackup\Service\TargetService;
|
|
use OCP\IL10N;
|
|
use OCP\IURLGenerator;
|
|
use OCP\SetupCheck\ISetupCheck;
|
|
use OCP\SetupCheck\SetupResult;
|
|
|
|
/** Shows the backup state in the admin overview (Security & setup warnings). */
|
|
class BackupStatusCheck implements ISetupCheck {
|
|
public function __construct(
|
|
private IL10N $l,
|
|
private IURLGenerator $url,
|
|
private KeyService $keys,
|
|
private TargetService $targets,
|
|
private RunMapper $runs,
|
|
) {
|
|
}
|
|
|
|
public function getCategory(): string {
|
|
return 'system';
|
|
}
|
|
|
|
public function getName(): string {
|
|
return $this->l->t('NG Backup');
|
|
}
|
|
|
|
public function run(): SetupResult {
|
|
$link = $this->url->linkToRouteAbsolute('settings.AdminSettings.index', ['section' => 'ng_backup']);
|
|
if (!$this->keys->isInitialized() || !$this->keys->everConfirmed()) {
|
|
return SetupResult::error($this->l->t('Backups are not set up: create the backup key and confirm the recovery kit.'), $link);
|
|
}
|
|
if ($this->keys->recoveryKitConfirmation() === null) {
|
|
return SetupResult::error($this->l->t('The backup passphrase was changed: download and confirm the new recovery kit.'), $link);
|
|
}
|
|
$targets = $this->targets->list();
|
|
if ($targets === []) {
|
|
return SetupResult::warning($this->l->t('No backup location is configured.'), $link);
|
|
}
|
|
$problems = [];
|
|
foreach ($targets as $t) {
|
|
$last = $this->runs->lastFinished($t->getId(), BackupService::KIND_FULL);
|
|
if ($last === null) {
|
|
$problems[] = $this->l->t('%s: no successful backup yet', [$t->getName()]);
|
|
} elseif (time() - (int)$last->getFinishedAt() > AlertService::STALE_AFTER) {
|
|
$problems[] = $this->l->t('%1$s: last successful backup %2$s', [$t->getName(), date('Y-m-d H:i', (int)$last->getFinishedAt())]);
|
|
}
|
|
}
|
|
foreach ($this->runs->findRecent(10) as $r) {
|
|
if ($r->getStatus() === Run::FAILED && $r->getStartedAt() > time() - AlertService::STALE_AFTER) {
|
|
$problems[] = $this->l->t('A %1$s run failed: %2$s', [$r->getKind(), mb_strimwidth((string)$r->getError(), 0, 120, '…')]);
|
|
break;
|
|
}
|
|
}
|
|
return $problems === []
|
|
? SetupResult::success($this->l->t('Recent backups to all locations succeeded.'))
|
|
: SetupResult::warning(implode("\n", $problems), $link);
|
|
}
|
|
}
|