Restore at setup: the wizard accepts an encrypted backup in place of the config form (#786 sub-issue B) - #933
Merged
Conversation
…d of the config form Implements #909 (#786 sub-issue B): the setup wizard accepts an uploaded encrypted pithead-backup archive + its passphrase as an alternative to the config form, on both a plain first boot and the installation medium's combined install+configure page. - wizard.py: POST /submit-restore writes the uploaded archive + passphrase to the spool (same "container asks, host decides" split as the rest of the wizard); a size cap (64 MiB — config/keys/db, never chains) is enforced both by aiohttp's client_max_size and an explicit check with a clearer message. On the installation medium the disk/wipe fields ride beside the archive through the same _gate_install_request every other submission takes. - pithead: firstboot_consume_restore decrypts and integrity-verifies the archive (the same magic-byte + full-stream check stack_restore uses), stages the extraction through a mktemp copy, validates the embedded config.json via the same fresh-process parse_and_validate_config call every other config path uses, and only on success commits the whole tree onto "/" and touches $spool/applied — landing in the identical accept path a typed submission takes. A rejected archive (bad passphrase, wrong format, failed integrity, unusable config) writes error.txt and touches nothing, falling back to the form exactly like a rejected config. The passphrase is read once and deleted immediately either way. - wizard.mjs: a "Restoring an existing Pithead? Upload its backup instead." toggle above the setup form, and its own small card (archive upload + passphrase) that respects the installer's disk picker. - docs: docs/appliance.md gets a "Recovering from a backup" section (fresh flash -> restore -> done) and a troubleshooting entry; docs/dev/appliance-wizard.md documents the new spool channel and validate-through-a-copy design; docs/dev/testing-strategy.md records the new tier-1 and tier-4 coverage. - tests: tier-1 stage/spool cases in test_wizard.py (accept, missing archive, oversize, installer disk gates) and wizard.test.mjs (toggle, size cap, multipart body, installer gates); tier-1 host-side firstboot_consume_restore cases in tests/stack/run.sh against a genuine backup archive (accept, bad passphrase, missing passphrase, oversize, malformed archive); a new KVM install-phase restore leg in tests/os/run.sh (real backup off a live machine, uploaded instead of the form, wallet + Tor identity proven restored) — unverified until the next battery run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The verifier caught it: firstboot_consume_restore extracted to a temp dir then cp -a'd the tree to /, but only checked config.json landed — an absolute path, a .. component, or a symlink/hardlink member could write outside the restore set. Modern tar refuses these, but the destination is the filesystem root, so audit the member list and fail closed regardless of tar version. Tier-1 cases cover a symlink and an absolute-path member (both refused, live files untouched). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Aug 13, 2026
Backup export from the dashboard: encrypted archive + one-time emergency kit (#786 sub-issue A)
#934
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements issue #909 — completes the disaster-recovery loop that backup export (#908) opens, the Home Assistant restore-during-onboarding pattern.
Mechanism: the setup wizard gains a "restore from backup" path — upload an encrypted
pithead-backup-*.tar.gz.enc+ its passphrase as an alternative to the config form. The host side (firstboot_consume_restore) reusesstack_restore's machinery: magic-byte format check, full-stream integrity verify BEFORE anything is touched, then decrypt/validate the embedded config through a COPY (never mutate live state until accepted) and land it exactly like a typed submission. A rejected archive — wrong passphrase, malformed, oversize (64 MiB cap, enforced client + server), or now unsafe members — falls back to the form with the reason surfaced, never blocking setup. Restore is wizard-time only. The passphrase is read once and deleted immediately, accepted or not; it never reaches argv or logs.Verifier caught one critical gap, fixed (commit 5b4ee02): extraction went to a temp dir then
cp -ato/, checking only that config.json landed — an absolute path, a..component, or a symlink/hardlink member could escape. Now the member list is audited and refused fail-closed before staging, regardless of tar version; tier-1 cases cover a symlink and an absolute-path member (both refused, live files untouched).Coverage: tier-1
firstboot_consume_restorecases (accept, wrong/missing passphrase, oversize, malformed, traversal, empty) — suite 2209/0; a KVM restore-boot leg in tests/os/run.sh (capture backup → upload → boot fresh disk → verify wallet/onion/db restored), proven in the next battery. Documented in docs/appliance.md.Part of the #786 pre-GA set (A #908 backup, B this, D #910 media). Ponytail: reuses stack_restore, no new crypto or abstraction.
🤖 Generated with Claude Code