From 61e2a28b670ee83102e1ad05dd2f06839bc1364a Mon Sep 17 00:00:00 2001 From: Dillard Blom Date: Thu, 22 Oct 2026 00:53:08 +0200 Subject: [PATCH] Full restore: maintenance mode on before the checks (off again when they refuse); the printed retry command is complete --- docs/DISASTER-RECOVERY.md | 6 ++++-- lib/Command/RestoreFull.php | 5 ++++- lib/Service/RestoreFullService.php | 20 ++++++++++++++++---- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/docs/DISASTER-RECOVERY.md b/docs/DISASTER-RECOVERY.md index 99db900..9b5d179 100644 --- a/docs/DISASTER-RECOVERY.md +++ b/docs/DISASTER-RECOVERY.md @@ -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 --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 --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`. diff --git a/lib/Command/RestoreFull.php b/lib/Command/RestoreFull.php index 3cf0b8c..05d63ed 100644 --- a/lib/Command/RestoreFull.php +++ b/lib/Command/RestoreFull.php @@ -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.'); } diff --git a/lib/Service/RestoreFullService.php b/lib/Service/RestoreFullService.php index cd3a1fb..27e8edd 100644 --- a/lib/Service/RestoreFullService.php +++ b/lib/Service/RestoreFullService.php @@ -89,10 +89,6 @@ final class RestoreFullService { /** @return array{dbReport:array, dataFiles:int, configKeys:int, dbConsistent:bool, missingApps:list, 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);