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.
### 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
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
+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
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:
+1
View File
@@ -92,6 +92,7 @@ HELP);
} catch (\Throwable $e) {
$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 occ then says that Nextcloud needs an upgrade (the database is already the old server\'s), run occ upgrade first.');
return 1;
}
$output->writeln(sprintf(
+5
View File
@@ -237,6 +237,11 @@ final class Repository {
if (!$this->backend instanceof IReadsAhead || !$this->backend->canReadAhead()) {
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)));
foreach ($this->streamEntries($snapshotId) as $entry) {
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'];
}
private function restore(ReadAheadBackend $backend, string $prefix = ''): string {
$target = $this->tempDir();
private function restore(ReadAheadBackend $backend, string $prefix = '', ?string $target = null): string {
$target ??= $this->tempDir();
$repo = Repository::openWithKey($backend, $this->keys, null, null);
$repo->restore($this->snapshot, $target, $prefix);
return $target;
@@ -66,6 +66,13 @@ class ReadAheadTest extends TestCase {
$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 {
$backend = new ReadAheadBackend(new LocalBackend($this->root), fail: [1]);