Skip to content

Backup export from the dashboard: encrypted archive + one-time emergency kit (#786 sub-issue A) - #934

Merged
VijitSingh97 merged 2 commits into
develop-v2from
feat/908-backup-export
Aug 14, 2026
Merged

Backup export from the dashboard: encrypted archive + one-time emergency kit (#786 sub-issue A)#934
VijitSingh97 merged 2 commits into
develop-v2from
feat/908-backup-export

Conversation

@VijitSingh97

Copy link
Copy Markdown
Collaborator

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 backup control 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 existing stack_backup encrypted-only as a child process (passphrase crosses via the PITHEAD_BACKUP_PASSPHRASE env var stack_backup already accepts — never argv, never a file), moves the archive onto the already-shared results/ 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-encrypt stays CLI-only.

One-time handoff: results/ is read-only to the container (it can only ask, via requests/), 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 — so control_run_pending now 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. New POST /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 test green (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

VijitSingh97 and others added 2 commits August 13, 2026 12:07
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>
@VijitSingh97
VijitSingh97 merged commit aa62211 into develop-v2 Aug 14, 2026
16 checks passed
@VijitSingh97
VijitSingh97 deleted the feat/908-backup-export branch August 14, 2026 20:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant