Files
felhom-controller/REPORT.md
T
2026-08-04 13:56:01 +02:00

3.2 KiB

REPORT — controller v0.195.0: prove the offsite key comes back (2026-08-04, R-200 plumbing half)

Deployed: gitea.dooplex.hu/admin/felhom-controller:0.195.0 on guest 9201 (demo-felhom), Up (healthy). Requires agent >= v0.125.0 (declared MinAgent) and hub >= v0.94.0.

What shipped

--recover-offsite-check — a docker exec diagnostic in the shape of --print-reset-code. It reads the customer's recovery code from STDIN, asks the agent to fetch this host's hub-held sealed bundle and open it, and reports whether the recovered offsite repository password matches the one on disk by sha256. Two hashes and a verdict; never a password, never the recovery code, never a blob.

  • internal/backup/offbox_recovery_check.goCheckOffsiteKeyRecoverable, hashes only.
  • internal/backup/offbox_recovery_cli.go — the entry point, R from stdin.
  • internal/agentapi/escrow.goRecoverOffsiteRepoPassword (the only place R crosses outbound).
  • cmd/controller/main.go — the flag, alongside --print-reset-code.

R from stdin and not a flag because a flag value is visible in ps, in shell history, in a container's command line and in any session transcript. It compares and never installs (§8.5): the recovered password is not written to offbox/repo_password, and link 9 — placing it so the existing repository opens — is next session's work, with the drill around it.

THE RESULT

On demo-felhom, 2026-08-04 13:49 CEST:

  on-disk   sha256: c60c8bc737a6b7c6647c7849283f52087f650a885babedb4ef5fdf9a5c9543cb
  recovered sha256: c60c8bc737a6b7c6647c7849283f52087f650a885babedb4ef5fdf9a5c9543cb
  [MATCH] the offsite repository password IS recoverable from the sealed escrow.

Both equal the hash the hub independently stores as restic_pw_sha256 — three sources agree. Confirmed non-write: offbox/repo_password mtime is still 2026-08-03 07:18:02 afterwards.

Five minutes earlier the same path with a deliberately wrong code returned [FAIL] … the recovery code did not open the sealed bundle … nothing was written, exit 1.

Tests

go build ./... && go vet ./... && go test ./... — rc=0; controller_gates.py --fast — all OK. New: offbox_recovery_check_test.go — match/mismatch verdicts, the no-local-password shape, R passed through verbatim, agent-failure-is-not-a-mismatch, and the four exit codes with a check that no printed stream carries a secret.

Red-proof observed: adding InjectOffboxPassword to the check fails TestCheckOffsiteKeyRecoverable_WritesNothing with "the check INSTALLED the recovered password".

Not in this release

No card, no form, no preview, no customer-facing text — building an interface on a chain nobody had walked is how the preceding three weeks went wrong. The customer-facing shape the operator ruled on (yell → recovery-code form → preview → proceed) is R-200's remaining half, now priced against a chain that exists. R-201 stays open: no recovered password has been installed, no repository reopened, no file restored.

Observation

The controller binary is at /usr/local/bin/felhom-controller, not /app/… — a guessed path cost the first diagnostic invocation. Worth a line in the runbook when one is written.