Backups: a shipped backup can be read back, retention stops deleting fresh dumps, and remotes are redacted properly (#753) #790
Loading…
Reference in a new issue
No description provided.
Delete branch "build/753-backup-readback"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #753. Owner, 2026-09-23 (B10): fix it before release.
Read the second section first. #753 was the bug asked about, and fixing it turned up two worse ones.
1. #753: a shipped backup couldn't be read back
_run_backup_asyncsetdestination = str(dest_path), then overwrote it withship_to_remote(...), which returns the redacted remote spec. The dump was still on disk, but nothing recorded its name. The one reader that opens that value as a file,POST /api/admin/backup/verify, which the setup wizard's "read it back" step (D87) uses, always answered "No local copy" on an instance with off-host backups.There was a piece #753 didn't mention: when shipping or the audio snapshot failed,
destinationwas never written at all. So a failed row didn't say where its perfectly good dump was either.Fix:
destinationnow only ever holds the local path, written as soon aspg_dumpsucceeds. A newbackup_logs.remote_destinationholds the redacted remote, written only when shipping succeeds.detail.Migration
d0e1f2a3b4c5, revisingb8c9d0e1f2a3; it's the new head:destinationthat doesn't start with/intoremote_destination. That test is exact, not a guess: every local path the task ever wrote is under the resolved backup root, and every remote passedvalidate_remote, which arrived in the same commit as shipping and can't start with/.test_backup_log_migration.pyruns the realupgrade()/downgrade()against seeded rows and pins the head.2. Two worse bugs, found on the way, both fixed here
Retention deleted the dump it had just written, and recorded success.
questboard_*.dump, which also matchesmake backup-now'squestboard_manual_<ts>.dump. Both land in the backup root by default. Becausemsorts after every digit, each manual dump counted as the newest backup.^questboard_\d{8}T\d{6}Z\.dump$and the audio equivalent, and always keeps at least one. I checked the pattern against the scheduled filename format (%Y%m%dT%H%M%SZ), so pruning still works.docs/OPERATIONS.mdnow says manual dumps are never pruned.Remote credentials leaked through redaction.
redact_remoteonly handled the inline form:backend,params:path. A named remote with parameters, e.g.s3-backups,secret_access_key=…:bucket, is accepted by bothvalidate_remoteand rclone, and was passed through verbatim to the log row, the audit log and error text.endpoint="https://…"leaked every parameter after it._redact_textcovers both forms too.Tests
verified: trueend to end.destinationis never opened.Admin.test.jsxcovers the table with both fields, remote only, and local only.Left alone, for a decision later
GET/PUT /api/admin/backup/configstill returns the rawremoteto the admin form, by design, since the form round-trips it. If the remote carries inline credentials, they reach the admin's browser. The alternative is write-only credentials, like the AI keys._redact_textonly scrubs the remote when rclone echoes it verbatim. If rclone prints the parameters split up or re-quoted, they'd get through.successrows remain. Verify reports those honestly as possibly pruned, moved or deleted.🤖 Generated with Claude Code