Review fixes: a missed passphrase change follows at the next backup, a key from before 1.1.0 counts as used, schedules edited in their own time zone
CI / unit (8.2) (push) Has been cancelled
CI / unit (8.3) (push) Has been cancelled
CI / unit (8.4) (push) Has been cancelled
CI / psalm (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / integration (mariadb, 32) (push) Has been cancelled
CI / integration (mariadb, 33) (push) Has been cancelled
CI / integration (mariadb, 34) (push) Has been cancelled
CI / integration (mariadb, 35) (push) Has been cancelled
CI / integration (pgsql, 32) (push) Has been cancelled
CI / integration (pgsql, 33) (push) Has been cancelled
CI / integration (pgsql, 34) (push) Has been cancelled
CI / integration (pgsql, 35) (push) Has been cancelled
CI / integration (sqlite, 32) (push) Has been cancelled
CI / integration (sqlite, 33) (push) Has been cancelled
CI / integration (sqlite, 34) (push) Has been cancelled
CI / integration (sqlite, 35) (push) Has been cancelled
CI / integration-targets (push) Has been cancelled
CI / integration-user (32) (push) Has been cancelled
CI / integration-user (33) (push) Has been cancelled
CI / integration-user (34) (push) Has been cancelled
CI / integration-user (35) (push) Has been cancelled
CI / integration-full-restore (push) Has been cancelled

- A location that a passphrase change could not reach gets it at its next backup
- A key from before 1.1.0 is never replaced as an unused one
- An unreadable settings file on a location is told apart from a missing one
- A schedule is shown and saved in the zone it was saved in
- Saving an unchanged schedule keeps its moment; --timezone needs a schedule
- Schedule coverage uses the time the backup began reading its data
- Tests: catch-up scenario, upgrade test restores the new file, autumn clock change
This commit is contained in:
2026-11-08 16:57:55 +01:00
parent 373d7e0424
commit 99f1655d04
16 changed files with 162 additions and 55 deletions
+29
View File
@@ -19,6 +19,8 @@
# unused-key-no-kit the same without a kit: target:add with --replace-unused-key and a passphrase
# of the old server; a wrong one keeps the unused key
# used-key a key that protected a location is never replaced, also after the location was removed
# slot-catch-up a passphrase added while a location was unreachable reaches it at its next
# backup; a new server then gets the key from it with that passphrase
set -euo pipefail
IMAGE="nextcloud:${1:-35-apache}"
APP=$(cd "$(dirname "$0")/../.." && pwd)
@@ -269,4 +271,31 @@ expect "$OUT" "never replaced" "used-key key:import"
[ "$(key_of ngb-rs-new)" = "$OWN" ] || fail "used-key: the key changed"
ok "used-key: never replaced, by kit or passphrase, also after its location was removed"
# slot-catch-up: a passphrase change that missed a location reaches it at the next backup
start ngb-rs-new
docker exec ngb-rs-new sh -c "printf '%s\n' 'catch up passphrase of the cto' > /tmp/pass2 && chmod 644 /tmp/pass2 && rm -rf /mnt/shared/catchup && mkdir -p /mnt/shared/catchup && chown www-data /mnt/shared/catchup"
run_on ngb-rs-new backup:key:init --passphrase-file=/tmp/pass --label=Safe >/dev/null || fail "slot-catch-up: key init"
CODE=$(run_on ngb-rs-new backup:key:kit | php -r 'echo json_decode(stream_get_contents(STDIN), true)["confirmation_code"];')
run_on ngb-rs-new backup:key:confirm -u admin --code="$CODE" --confirm="Yes, I confirm" >/dev/null || fail "slot-catch-up: confirm"
# shellcheck disable=SC2046
run_on ngb-rs-new backup:target:add offsite $(location catchup) >/dev/null || fail "slot-catch-up: target add"
run_on ngb-rs-new backup:run offsite >/dev/null || fail "slot-catch-up: first backup"
BEFORE=$(docker exec ngb-rs-new md5sum /mnt/shared/catchup/repo/config | cut -d' ' -f1)
docker exec ngb-rs-new chmod 000 /mnt/shared/catchup/repo
OUT=$(must_fail ngb-rs-new "slot-catch-up: slot add with the location unreachable" backup:key:slot:add --label=CTO --passphrase-file=/tmp/pass2)
expect "$OUT" "tried again at their next backup" "slot-catch-up: says the change follows"
docker exec ngb-rs-new chmod 755 /mnt/shared/catchup/repo
[ "$(docker exec ngb-rs-new md5sum /mnt/shared/catchup/repo/config | cut -d' ' -f1)" = "$BEFORE" ] || fail "slot-catch-up: the location changed while unreachable"
CODE=$(run_on ngb-rs-new backup:key:kit | php -r 'echo json_decode(stream_get_contents(STDIN), true)["confirmation_code"];')
run_on ngb-rs-new backup:key:confirm -u admin --code="$CODE" --confirm="Yes, I confirm" >/dev/null || fail "slot-catch-up: confirm the new kit"
run_on ngb-rs-new backup:run offsite >/dev/null || fail "slot-catch-up: backup after the location came back"
[ "$(docker exec ngb-rs-new md5sum /mnt/shared/catchup/repo/config | cut -d' ' -f1)" != "$BEFORE" ] || fail "slot-catch-up: the backup did not bring the new passphrase to the location"
CATCHFP=$(key_of ngb-rs-new)
start ngb-rs-other
docker exec ngb-rs-other sh -c "printf '%s\n' 'catch up passphrase of the cto' > /tmp/pass2 && chmod 644 /tmp/pass2"
# shellcheck disable=SC2046
OUT=$(run_on ngb-rs-other backup:target:add offsite $(location catchup) --passphrase-file=/tmp/pass2 2>&1) || { printf '%s\n' "$OUT"; fail "slot-catch-up: a new server with the new passphrase"; }
expect "$OUT" "fingerprint $CATCHFP" "slot-catch-up: the new server"
ok "slot-catch-up: a passphrase that missed a location reaches it at the next backup and opens it on a new server"
echo "RECOVERY SCENARIOS PASS"
+10 -4
View File
@@ -77,8 +77,8 @@ VER=$(occ app:list --output=json | php -r '$a = json_decode(stream_get_contents(
[ -z "$NEWVER" ] || [ "$VER" = "$NEWVER" ] || fail "after the upgrade the app has version '$VER'"
ok "occ upgrade to $VER"
LIST=$(occ backup:target:list)
printf '%s\n' "$LIST" | grep offsite | grep -q "03:15" || { printf '%s\n' "$LIST"; fail "the location did not keep 03:15 as its schedule"; }
SCHED=$(occ backup:target:schedule offsite)
printf '%s\n' "$SCHED" | grep -q "^offsite: 03:15 " || { printf '%s\n' "$SCHED"; fail "the location did not keep 03:15 as its schedule"; }
occ backup:list offsite | grep -q "$OLDSNAP" || fail "the old restore point is not listed"
occ backup:verify offsite "$OLDSNAP" >/dev/null || fail "the old restore point does not verify"
occ backup:status >/dev/null || fail "backup:status"
@@ -91,9 +91,15 @@ ok "after the upgrade: schedule kept, old restore point listed, verified, a file
docker exec -u www-data $N sh -c 'head -c 2000000 /dev/urandom > data/admin/files/Docs/after.bin'
occ files:scan admin >/dev/null
occ backup:run offsite >/dev/null || fail "backup after the upgrade"
[ "$(occ backup:list offsite | grep -o '[0-9a-f]\{32\}' | sort -u | wc -l)" -ge 2 ] || fail "no new restore point after the upgrade"
NEWSNAP=$(occ backup:list offsite | grep -o '[0-9a-f]\{32\}' | grep -v "$OLDSNAP" | head -1)
[ -n "$NEWSNAP" ] || fail "no new restore point after the upgrade"
SUM2=$(docker exec $N sha256sum /var/www/html/data/admin/files/Docs/after.bin | cut -d' ' -f1)
docker exec $N sh -c 'rm -rf /tmp/rst && mkdir /tmp/rst && chown www-data /tmp/rst'
occ backup:restore offsite "$NEWSNAP" data/admin/files/Docs/after.bin --to-directory=/tmp/rst >/dev/null || fail "restore from the new restore point"
GOT=$(docker exec $N sh -c 'find /tmp/rst -name after.bin -exec sha256sum {} \;' | cut -d' ' -f1)
[ "$GOT" = "$SUM2" ] || fail "the file restored from the new restore point differs"
docker exec $N test -f /mnt/shared/loc/repo/settings || fail "the location got no settings file"
OUT=$(occ backup:key:import /tmp/kit.json --passphrase-file=/tmp/pass --replace-unused-key 2>&1) && fail "a used key from before the upgrade was offered for replacement"
printf '%s\n' "$OUT" | grep -q "never replaced" || { printf '%s\n' "$OUT"; fail "the refusal does not say the key is never replaced"; }
ok "a new backup runs, the location got its settings, the key counts as used"
ok "a new backup runs and restores, the location got its settings, the key counts as used"
echo "UPGRADE $OLDVER -> ${VER:-working tree} PASS"