Backup export from the dashboard: encrypted archive + one-time emergency kit (#786 sub-issue A) - #934
Merged
Merged
Conversation
New fixed `backup` verb on the #33 host control channel: the host mints its own passphrase (reusing generate_node_password), runs the existing stack_backup machinery encrypted-only as a child process, and lands the archive on the spool's already-shared results/ mount. The kit (passphrase + archive name + contents + created-at) rides back through the normal result path once, then the passphrase is redacted host-side on a bounded TTL — the container can only ask, so it can never delete or ack the file itself. Dashboard gets a Backup card (Configuration tab) that triggers the verb, polls for the result, and shows a one-time "save this now" reveal with a downloadable kit file and the archive link. Docs cover the export story; restore stays explicitly out of scope, filed separately. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The one-time kit self-redacts after a blocking TTL, but a runner killed during that sleep (a reboot racing the window) would leave a wallet-grade passphrase in results/ in plaintext on /data forever — the gap the implementer flagged. control_run_pending now sweeps at the top of every drain (a fresh runner after that reboot runs it), nulling the passphrase in any kit older than the TTL that still carries one, while leaving an in-window kit and non-kit results alone. Tier-1 covers all three. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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 #908 — the first of the #786 pre-GA parity set, and the only irreversible loss on a shell-less box today (a dead appliance takes wallet settings, Tor identity and the dashboard DB with it; nothing exported state at all).
Mechanism: a new fixed
backupcontrol verb on the #33 host runner (beside preview/commit/upgrade/worker-apply/worker-upgrade/restart/apply). It mints a passphrase host-side (generate_node_password), runs the existingstack_backupencrypted-only as a child process (passphrase crosses via thePITHEAD_BACKUP_PASSPHRASEenv varstack_backupalready accepts — never argv, never a file), moves the archive onto the already-sharedresults/leg (no new mount), and returns the emergency kit — passphrase + archive name + contents + timestamp — through the normal result path exactly once. A passphrase-less request is refused outright;--no-encryptstays CLI-only.One-time handoff:
results/is read-only to the container (it can only ask, viarequests/), so the container can't ack-and-delete. The kit is readable for a bounded, blocking TTL (default 20s — generous next to the dashboard's poll window, short next to the runner's other blocking verbs), then the passphrase is nulled in place, read or not. Review-added backstop: a runner killed during that sleep (a reboot racing the window) would strand the passphrase in plaintext on /data — socontrol_run_pendingnow sweeps at the top of every drain and redacts any kit past its TTL that still carries a passphrase (the fresh runner after that reboot cleans it up).Dashboard: a backup panel (
backupview.mjs) — trigger → disruption-notice confirm → poll → one-time kit reveal (passphrase + a downloadable kit .txt + the archive download link) with an explicit "save it now, it cannot be recovered" moment, Home Assistant's plain tone. NewPOST /api/control/backup+GET /api/control/backup-download(host-authored filename, no header-injection surface).Coverage: 7 pytest + 8 frontend + 26 shell assertions (verb glue, mid-TTL visibility + redaction, the stranded-kit backstop, failure/throttle/schema — a passphrase can't even be smuggled as a request field).
make testgreen (dashboard 1770, frontend 320, stack 2208, selftest 154); patch coverage 100% on the server changes. Restore lands separately (#933); documented as such in docs/appliance.md. The agent's own security + verifier passes came back clean.Part of the #786 pre-GA set (A this, B #933 restore, D #932 media). Ponytail: reuses stack_backup + the existing spool + configview's poll helper; no new crypto, mount, or abstraction.
🤖 Generated with Claude Code