From f415bab5195a24b8ce5a837a0538770d2ba87029 Mon Sep 17 00:00:00 2001 From: Dillard Blom Date: Thu, 8 Oct 2026 01:11:21 +0200 Subject: [PATCH] Verify, README: address review notes An unreadable blob may still be a truncated pack, so don't rule out damage in the verify message; README: Hetzner needs WebDAV/SMB and external reachability enabled for the sub-account. --- CHANGELOG.md | 3 ++- README.md | 4 +++- lib/Command/Verify.php | 5 +++-- lib/Repository/Repository.php | 8 ++++---- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98d5d67..34f51d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,8 @@ Bugfix release. - Backups: the runs table scrolls horizontally on narrow screens instead of running off the page. - `occ backup:verify --deep`: a blob that could not be read (network or storage error) was reported as "corrupted or tampered". Reads are now retried, and a read that keeps failing is reported - separately as unreadable, with the error; only a blob that was read but fails decryption or its + separately as unreadable, with the error (run the check again; if the same blobs stay unreadable, + their pack may be damaged or truncated). Only a blob that was read but fails decryption or its hash check counts as corrupted. Found on a Hetzner Storage Box over WebDAV, where the blob turned out to be intact. diff --git a/README.md b/README.md index 43619b9..d7691f3 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,9 @@ Contributions welcome via PR — add what you ran into with your own provider. - **Hetzner Storage Box over WebDAV and SMB (sub-accounts, 2026-10-07):** both work end to end (backup, incremental run, restore of a folder, verify), tested from outside the Hetzner network - with about 66 MiB of data. WebDAV: host `https://uXXXXX-subN.your-storagebox.de`, and the `root` + with about 66 MiB of data. In the Hetzner Console, enable WebDAV or SMB support for the + sub-account, and "External reachability" when your server is not in the Hetzner network. + WebDAV: host `https://uXXXXX-subN.your-storagebox.de`, and the `root` must be a folder that already exists (or empty): WebDAV does not create missing parent folders, so a root that doesn't exist yet fails `backup:target:add` with `Sabre\HTTP\ClientHttpException: Conflict`. SMB: share = the sub-account name, port 445 must be reachable from your server (it was diff --git a/lib/Command/Verify.php b/lib/Command/Verify.php index 148f476..37997ab 100644 --- a/lib/Command/Verify.php +++ b/lib/Command/Verify.php @@ -67,8 +67,9 @@ HELP); $output->writeln('' . count($result['failed']) . ' blob(s) failed verification (corrupted or tampered): ' . implode(', ', $result['failed'])); } if ($result['unreadable'] !== []) { - $output->writeln('' . count($result['unreadable']) . ' blob(s) could not be read from the location (storage or network error, ' - . 'not a sign of corruption; run the check again): ' . implode(', ', $result['unreadable'])); + $output->writeln('' . count($result['unreadable']) . ' blob(s) could not be read from the location (often a temporary storage ' + . 'or network error: run the check again; if the same blobs stay unreadable, their pack may be damaged or truncated): ' + . implode(', ', $result['unreadable'])); $output->writeln('Last read error: ' . $result['readError']); } if ($result['missing'] === [] && $result['failed'] === [] && $result['unreadable'] === []) { diff --git a/lib/Repository/Repository.php b/lib/Repository/Repository.php index be0b35d..f45a4e2 100644 --- a/lib/Repository/Repository.php +++ b/lib/Repository/Repository.php @@ -831,10 +831,10 @@ final class Repository { if (!$deep) { return; } - // Reading and checking are kept apart: a read that fails (network, storage) says - // nothing about the stored data, so it is retried and reported as unreadable, - // never as corrupted. Only a blob that was read but does not decrypt or match - // its id counts as failed. + // Reading and checking are kept apart: a read that fails is usually a network or + // storage error, so it is retried and reported as unreadable (a pack that stays + // unreadable may still be truncated). Only a blob that was read but does not + // decrypt or match its id counts as failed: that is proof of damage. [, $offset, $length] = $this->index->get($id); $encrypted = null; for ($attempt = 1; $attempt <= self::VERIFY_READ_ATTEMPTS && $encrypted === null; $attempt++) {