Files
felhom.eu/documentation/runbooks/RUNBOOK-open-retained-package.md
T

4.0 KiB
Raw Blame History

RUNBOOK — open a retained escrow package for a household (R-304, design option C, second slice)

When: the hub mailed you recovery_older_package — a household's recovery code opens (422), or may open (424), an OLDER sealed package that the hub keeps as retained custody (R-222), and the household asked for data from that older off-site store. The product has no path to a retained package (R-312: reopening old history is operator-only). This is that path, written down from the drill that proved it end to end (audits/DRILL-retained-key-2026-08-12.md §3.5–3.6; memory note „opening an escrow blob by hand").

Promise: none new. The household's dashboard says „contact support"; this is what support does.

Rules for the whole procedure

  • The household types its recovery code, on its own box. You never ask for it in a mail or chat, and it is never on a command line or in a file outside the box's /mnt/ work folder (deleted at the end).
  • Nothing is printed. A recovered password is verified by its SHA-256 against the hub's record, never shown.
  • Restore into a NEW folder, never over the household's live data. The household moves files afterwards.
  • One package, one session; do it with the household present (phone or screen-share). Write what you did in the household's support log.

1. Find the package (DooPlex, read only)

  1. Snapshot the hub database with its -wal and -shm (a copy of hub.db alone can be hours stale).
  2. In the copy: SELECT id, host_id, length(identity_blob), restic_pw_sha256, superseded_at FROM host_escrow_superseded WHERE host_id='<host>' ORDER BY superseded_at DESC; — the mail names the box and the date; pick that row.
  3. Write the blob out with writefile (never SELECT a BLOB to a terminal): SELECT writefile('/tmp/<host>-<id>.age', identity_blob) FROM host_escrow_superseded WHERE id=<id>;
  4. Note the row's restic_pw_sha256 — the check value for step 3.

2. Bring it to the box

  1. Copy the blob into the household's guest under /mnt/felhom-retained-<date>/ (bind-mounted into the controller container at the same path — restic lives INSIDE the felhom-controller container, and a path under /root is invisible to it).
  2. Copy the drill's pty wrapper (a ~30-line Python pty.fork() that runs age -d -o <out> <blob> and answers the passphrase prompt) to the same folder. age -d will not read a passphrase from stdin; it needs the pty.

3. The household unseals it

  1. The household types the recovery code (the ten words) into the wrapper's prompt on its box. A wrong code fails closed at the scrypt step — no partial output.
  2. The output is the identity bundle. Take restic_repo_password from it into a 0600 file in the same folder.
  3. Check it: sha256sum of the trimmed password must equal the row's restic_pw_sha256 (step 1.4). If not: stop — wrong package or wrong code.

4. Read the old store

  1. Mirror the controller's off-site arguments (controller/internal/backup/offbox.go, offboxBaseArgs): the sftp backend with the box's own known_hosts and ssh_key, and RESTIC_PASSWORD_FILE=/mnt/felhom-retained-<date>/pw. Run it as docker exec felhom-controller restic ….
  2. restic snapshots — the household picks the snapshot (date, app tag).
  3. restic restore <snapshot> --target /mnt/felhom-retained-<date>/restore --include <what they need>.
  4. Show the household the restored files. They move what they want into place through the dashboard's file tools or their share.

5. Clean up — always, also after a failure

  1. shred -u the password file, the bundle and the blob copy; remove /mnt/felhom-retained-<date>/ once the household has its files.
  2. Delete the hub DB copy on DooPlex.
  3. Note in the household's support record: the date, the package id, the snapshot, what was restored. No secret.

Negative control used in the drill: the box's CURRENT password against the old store → Fatal: wrong password or no key found. If you are unsure which store you are reading, run that first.