Full restore: a run again after an interrupted restore keeps maintenance mode on when its check refuses; retry text about apps

This commit is contained in:
2026-10-22 01:59:01 +02:00
parent 2eabdf44d5
commit fd0f786f5f
4 changed files with 22 additions and 9 deletions
+3 -2
View File
@@ -101,8 +101,9 @@ mode on yourself before, it stays on: switch it off with `occ maintenance:mode -
If it stops halfway, maintenance mode stays on (the instance may be partly restored). Fix the cause,
then switch maintenance mode off and run the restore again with `--force`, in one line (the message
prints it): `occ maintenance:mode --off && occ backup:restore:full <location> <snapshot> --force`.
Leave out `--install-apps` this time: the apps are installed already, and the instance is then open
only for the seconds until the restore has switched maintenance mode on again.
Leave out `--install-apps` this time, so that the instance is open only for the seconds until the
restore has switched maintenance mode on again; apps the first run could not install are listed at
the end of the restore, install those afterwards.
If occ then says that Nextcloud or one of the apps requires an upgrade, the database is already
the old server's: run `occ upgrade` first, then the same command with `--force`.
+1 -1
View File
@@ -98,7 +98,7 @@ HELP);
$again = 'occ backup:restore:full ' . escapeshellarg((string)$input->getArgument('target')) . ' ' . escapeshellarg((string)$input->getArgument('snapshot'))
. ($input->getOption('skip-missing-tables') ? ' --skip-missing-tables' : '') . ' --force --no-interaction';
$output->writeln(' occ maintenance:mode --off && ' . $again);
$output->writeln('(Without --install-apps: the apps are installed already, and the instance is open again only for the seconds until the restore starts.)');
$output->writeln('(Without --install-apps, so the instance is open only for the seconds until the restore starts again. Apps the first run could not install are listed at the end: install those afterwards.)');
} else {
$output->writeln('If this happened after the restore had started, the instance may be partly restored: fix the cause and run the same command again with --force.');
}
+17 -6
View File
@@ -27,6 +27,9 @@ final class RestoreFullService {
* Config keys that must keep pointing at THIS installation's actual, already-working
* database and data directory (set by occ maintenance:install), never at the old server's.
*/
/** App setting: when a full restore started that has not completed (see restoreFull()). */
private const INTERRUPTED_KEY = 'restore_full_started';
private const CONNECTION_KEYS = [
'dbtype', 'dbhost', 'dbport', 'dbname', 'dbuser', 'dbpassword', 'dbpersistent',
'dbtableprefix', 'dbdriveroptions', 'datadirectory',
@@ -98,23 +101,31 @@ final class RestoreFullService {
// right before running the restore again (see RestoreFull). Already on before: left on.
// On before the checks below, so that a run again after an error leaves the partly
// restored instance open for seconds at most; off again when they refuse (nothing changed).
// A marker in ng_backup's own app settings (which the restore keeps) says a restore started
// and did not complete: then the instance may be partly restored, and a check that refuses
// a run again must not open it.
$appConfig = \OCP\Server::get(\OCP\IAppConfig::class);
$interrupted = $appConfig->getValueInt(\OCA\NgBackup\AppInfo\Application::APP_ID, self::INTERRUPTED_KEY, 0) > 0;
$wasInMaintenance = $this->config->getSystemValueBool('maintenance', false);
$this->config->setSystemValue('maintenance', true);
$reopen = function () use ($wasInMaintenance, $interrupted): void {
if (!$wasInMaintenance && !$interrupted) {
$this->config->setSystemValue('maintenance', false);
}
};
try {
$check = $this->checkCompatibility($target, $snapshotId, $skipMissingTables);
} catch (\Throwable $e) {
if (!$wasInMaintenance) {
$this->config->setSystemValue('maintenance', false);
}
$reopen();
throw $e;
}
if (!$check['ok']) {
if (!$wasInMaintenance) {
$this->config->setSystemValue('maintenance', false);
}
$reopen();
throw new \RuntimeException($this->describeBlockers($check));
}
$appConfig->setValueInt(\OCA\NgBackup\AppInfo\Application::APP_ID, self::INTERRUPTED_KEY, time());
$result = $this->restoreInMaintenance($target, $snapshotId, $skipMissingTables, $check);
$appConfig->deleteKey(\OCA\NgBackup\AppInfo\Application::APP_ID, self::INTERRUPTED_KEY);
if (!$wasInMaintenance) {
$this->config->setSystemValue('maintenance', false);
}
@@ -174,6 +174,7 @@ OUT=$(run_on ngb-frt-restore backup:restore:full offsite "$SNAP" "${RESTORE_OPTS
}
printf '%s\n' "$OUT" | grep -q "maintenance mode while the restore runs" || fail "the restore did not say it runs in maintenance mode"
run_on ngb-frt-restore status 2>&1 | grep -q "maintenance: false" || fail "maintenance mode is still on after the restore"
run_on ngb-frt-restore config:app:get ng_backup restore_full_started >/dev/null 2>&1 && fail "the restore left its 'started' marker behind"
ok "backup:restore:full completed, in maintenance mode and out of it again"
# Everything below must work in a FRESH occ process (a new PHP process per docker exec), since