diff --git a/CHANGELOG.md b/CHANGELOG.md index b8b0102..5496a03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/docs/DISASTER-RECOVERY.md b/docs/DISASTER-RECOVERY.md index fd7bdf1..d1f48a1 100644 --- a/docs/DISASTER-RECOVERY.md +++ b/docs/DISASTER-RECOVERY.md @@ -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: diff --git a/lib/Command/RestoreFull.php b/lib/Command/RestoreFull.php index 1516391..9774e7a 100644 --- a/lib/Command/RestoreFull.php +++ b/lib/Command/RestoreFull.php @@ -92,6 +92,7 @@ HELP); } catch (\Throwable $e) { $output->writeln('' . $e->getMessage() . ''); $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( diff --git a/lib/Repository/Repository.php b/lib/Repository/Repository.php index 284a300..3c836b0 100644 --- a/lib/Repository/Repository.php +++ b/lib/Repository/Repository.php @@ -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)) { diff --git a/tests/Unit/Repository/ReadAheadTest.php b/tests/Unit/Repository/ReadAheadTest.php index 276639f..7d97709 100644 --- a/tests/Unit/Repository/ReadAheadTest.php +++ b/tests/Unit/Repository/ReadAheadTest.php @@ -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]);