Full restore: maintenance mode on before the checks (off again when they refuse); the printed retry command is complete

This commit is contained in:
2026-10-22 00:53:08 +02:00
parent 7ed53ad0eb
commit 61e2a28b67
3 changed files with 24 additions and 7 deletions
+4 -2
View File
@@ -99,8 +99,10 @@ writes into it; the restore switches it off when it completes. (If you had switc
mode on yourself before, it stays on: switch it off with `occ maintenance:mode --off` when ready.)
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 same command again with `--force`, in one line:
`occ maintenance:mode --off && occ backup:restore:full <location> <snapshot> --install-apps --force`.
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.
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`.
+4 -1
View File
@@ -95,7 +95,10 @@ HELP);
if (\OCP\Server::get(\OCP\IConfig::class)->getSystemValueBool('maintenance', false)) {
$output->writeln('The restore had started: the instance may be partly restored, and maintenance mode stays on so that nobody uses it like that.');
$output->writeln('Fix the cause, then switch maintenance mode off and run the same command again with --force, in one line:');
$output->writeln(' occ maintenance:mode --off && occ backup:restore:full ' . (string)$input->getArgument('target') . ' ' . (string)$input->getArgument('snapshot') . ' --force ...');
$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.)');
} 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.');
}
+16 -4
View File
@@ -89,10 +89,6 @@ final class RestoreFullService {
/** @return array{dbReport:array, dataFiles:int, configKeys:int, dbConsistent:bool, missingApps:list<string>, maintenanceLeftOn:bool} */
public function restoreFull(Target $target, string $snapshotId, bool $skipMissingTables = false): array {
$check = $this->checkCompatibility($target, $snapshotId, $skipMissingTables);
if (!$check['ok']) {
throw new \RuntimeException($this->describeBlockers($check));
}
// Maintenance mode for the whole restore: background jobs (cron.php stops in maintenance
// mode), page visits (AJAX cron) and users must not write into tables or the data
// directory while they are being replaced; a row written in between would collide with a
@@ -100,8 +96,24 @@ final class RestoreFullService {
// error it stays on: the instance may be partly restored, and must not be used like that.
// occ only offers app commands outside maintenance mode, so the admin switches it off
// 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).
$wasInMaintenance = $this->config->getSystemValueBool('maintenance', false);
$this->config->setSystemValue('maintenance', true);
try {
$check = $this->checkCompatibility($target, $snapshotId, $skipMissingTables);
} catch (\Throwable $e) {
if (!$wasInMaintenance) {
$this->config->setSystemValue('maintenance', false);
}
throw $e;
}
if (!$check['ok']) {
if (!$wasInMaintenance) {
$this->config->setSystemValue('maintenance', false);
}
throw new \RuntimeException($this->describeBlockers($check));
}
$result = $this->restoreInMaintenance($target, $snapshotId, $skipMissingTables, $check);
if (!$wasInMaintenance) {
$this->config->setSystemValue('maintenance', false);