Full restore: say to run occ upgrade before rerunning a restore that stopped after the database; remove empty spool folders of a stopped restore

This commit is contained in:
2026-10-19 05:34:54 +02:00
parent 43a71684f9
commit 6121f684e9
5 changed files with 20 additions and 2 deletions
+3
View File
@@ -17,6 +17,9 @@ instead carried by the app description, the git tag, the release, and this file.
cannot be read this way is read as before. Restores from other locations work as before. cannot be read this way is read as before. Restores from other locations work as before.
### Fixed ### Fixed
- After a full restore stopped halfway, running it again with `--force` was refused when the
database was already restored: Nextcloud then first wants `occ upgrade`. The guide and the
error message now say so.
- A full restore of a backup made before 1.0.0-rc.2 with `--skip-missing-tables` failed on - A full restore of a backup made before 1.0.0-rc.2 with `--skip-missing-tables` failed on
PostgreSQL with "relation ... does not exist": the left-out tables were still looked up to set PostgreSQL with "relation ... does not exist": the left-out tables were still looked up to set
their ID counters. It now skips them. A new test restores a backup made by 1.0.0-rc.1 on their ID counters. It now skips them. A new test restores a backup made by 1.0.0-rc.1 on
+2
View File
@@ -95,6 +95,8 @@ out with `--skip-missing-tables`.
**8. Let the restore run.** It restores the database, then the data directory, and takes hours for **8. Let the restore run.** It restores the database, then the data directory, and takes hours for
a large backup. If it stops halfway, fix the cause and run the same command again with `--force`. a large backup. If it stops halfway, fix the cause and run the same command again with `--force`.
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`.
**9. After the restore.** These worked in our tests: **9. After the restore.** These worked in our tests:
+1
View File
@@ -92,6 +92,7 @@ HELP);
} catch (\Throwable $e) { } catch (\Throwable $e) {
$output->writeln('<error>' . $e->getMessage() . '</error>'); $output->writeln('<error>' . $e->getMessage() . '</error>');
$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.'); $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.');
$output->writeln('If occ then says that Nextcloud needs an upgrade (the database is already the old server\'s), run occ upgrade first.');
return 1; return 1;
} }
$output->writeln(sprintf( $output->writeln(sprintf(
+5
View File
@@ -237,6 +237,11 @@ final class Repository {
if (!$this->backend instanceof IReadsAhead || !$this->backend->canReadAhead()) { if (!$this->backend instanceof IReadsAhead || !$this->backend->canReadAhead()) {
return null; return null;
} }
// what a restore that was stopped left behind: empty, its files were unlinked when opened
$stale = glob(rtrim($target, '/') . '/.ngb-restore-spool-*', GLOB_ONLYDIR);
foreach ($stale === false ? [] : $stale as $dir) {
@rmdir($dir);
}
$plan = new ReadAheadPlan(rtrim($target, '/') . '/.ngb-restore-spool-' . bin2hex(random_bytes(4))); $plan = new ReadAheadPlan(rtrim($target, '/') . '/.ngb-restore-spool-' . bin2hex(random_bytes(4)));
foreach ($this->streamEntries($snapshotId) as $entry) { foreach ($this->streamEntries($snapshotId) as $entry) {
if ($prefix !== '' && !str_starts_with($entry['p'], $prefix)) { if ($prefix !== '' && !str_starts_with($entry['p'], $prefix)) {
+9 -2
View File
@@ -41,8 +41,8 @@ class ReadAheadTest extends TestCase {
$this->snapshot = $repo->backupDirectory($this->src)['snapshot']; $this->snapshot = $repo->backupDirectory($this->src)['snapshot'];
} }
private function restore(ReadAheadBackend $backend, string $prefix = ''): string { private function restore(ReadAheadBackend $backend, string $prefix = '', ?string $target = null): string {
$target = $this->tempDir(); $target ??= $this->tempDir();
$repo = Repository::openWithKey($backend, $this->keys, null, null); $repo = Repository::openWithKey($backend, $this->keys, null, null);
$repo->restore($this->snapshot, $target, $prefix); $repo->restore($this->snapshot, $target, $prefix);
return $target; return $target;
@@ -66,6 +66,13 @@ class ReadAheadTest extends TestCase {
$this->assertLessThanOrEqual(count($backend->inner->list('packs')) + 1, count($backend->asked)); $this->assertLessThanOrEqual(count($backend->inner->list('packs')) + 1, count($backend->asked));
} }
public function testTheEmptySpoolOfAStoppedRestoreIsRemoved(): void {
$target = $this->tempDir();
mkdir("$target/.ngb-restore-spool-0badc0de", 0700); // a restore that was killed
$this->assertRestored($this->restore(new ReadAheadBackend(new LocalBackend($this->root)), '', $target));
}
public function testARangeThatCouldNotBeReadAheadIsReadTheUsualWay(): void { public function testARangeThatCouldNotBeReadAheadIsReadTheUsualWay(): void {
$backend = new ReadAheadBackend(new LocalBackend($this->root), fail: [1]); $backend = new ReadAheadBackend(new LocalBackend($this->root), fail: [1]);