Proves a customer ConfigJSON logging.level=debug deep-merges over the template
default into the generated controller.yaml — the integration leg the remote
debug-mode toggle depends on. Merge target (logging.level: info) verified live
on the demo controller.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
Adds a form-level debug-mode checkbox to the customer config editor so an
operator can flip the controller's Logging.Level=debug (/debug menu + verbose
log) remotely, without SSH. Form field (not raw-JSON injection) because
handleConfigUpdate rebuilds ConfigJSON from the form on every save; the
config-version bump makes the controller re-pull + self-restart next cycle.
- buildConfigJSON: debug_mode checked -> "logging":{"level":"debug"};
unchecked -> logging key omitted.
- config_form.html: "Hibakeresési mód (fejlesztői)" section + render state.
- configs_debug_test.go: form->JSON both ways; full-path survival test
(debug lands, offsite descriptor unchanged, foreign-key red-proof); render
state; red-proof exercised.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
Golden 0.110.0 published (sha 1593597d98…f010f3d4, 611696157B, anon-verified);
drill VM launch reconstructed (operator-authorized deviation), booted first
try, reverted to virgin. Four operator values ready. Phase D = Viktor's one
UI save (artifact manifest) + the floor bump; Phase E qualified (demo already
0.110.0). READY-FOR-PETI checklist in the record.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
Agent 0.79.0 published from live felhom-pve bytes (sha 35a1625476…), all
GL-1 gates green (404→201→anon-200, live-bytes sha, token-leak 0). Golden
bake (Phase C) blocked: the canonical qemu drill-VM launch command was
never saved and reconstructing it would improvise the bake (runbook-
forbidden). No publish-time signing needed (integrity root = sha; operator
signature is a runtime agent_update concern). Needs operator input to resume.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
Unattended live soak of the build-complete offsite arc. F4 re-issue + restore
round-trip proved live (9/9 byte-identical, immich db-dump exact). Crash
recovery surfaced a HIGH resilience gap: crash mid-prune leaves a stale restic
exclusive lock the controller can't self-heal → subsequent runs fail until
manual unlock. Demo restored to start-state. Track B (quota) + hub-orchestrated
A blocked by session-gating → Peti runbook.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
Sibling checker over the controller report's offsite object: quota-fill
warn/crit + the silently-stuck staleness detector (escrowed-only,
red-proofed; nil-safe on pre-v0.109 reports; same-second tie-guard).
SetOffsiteFrozen flips ONLY readonly on the exactly-1 labelled sub-account
(SSH preserved); Freeze/Unfreeze buttons — manual only, never automatic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
Additive host_escrow migration; SaveHostEscrow/HostEscrow gain the hash
(NULL-safe for legacy rows); GetEscrowStatusForCustomer joins hosts;
the report ACK gains escrow:{identity_blob_present,restic_pw_sha256,
created_at} (omitted without a row). Contract test mirrors the agent's
v0.79.0 emit struct.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
F4: ReissueCredentials — explicit operator recovery for consumed-password
dead-ends; resets the labelled resource's password (exactly-1 guard,
red-proofed), stores a fresh one-time secret, bumps ConfigVersion.
New hetznerapi.ResetBoxPassword for the dedicated path.
F2: host-key scan retry-with-backoff (~60s ladder, red-proofed) — first
save survives fresh-subaccount DNS lag.
F5: config form disables submits + shows an in-flight notice (the re-click
bait that caused live F1).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
Full hub->Hetzner->controller loop validated on demo-felhom against the new
dedicated-project pool box; 2 snapshots on the new box, key-only. F1/F3 fixed
mid-run (hub v0.38.1, controller v0.106.1); F2/F4/F5/F6 recorded open.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
First supervised live run: the ~25s spinner-less offsite save invited a
re-click; the abandoned first request's r.Context() was canceled between
CreateSubaccount and SaveOneTimeSecret, stranding sub-account 268985 with a
password lost forever (consume 404s permanently).
applyOffsite now provisions on context.WithoutCancel + 3-minute absolute
timeout: once the create starts, create->wait->store runs to completion.
Regression test with a ctx-honoring fake that cancels the request context
mid-create; red-proofed against the raw-ctx pre-fix shape (reproduces the
exact live error).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
Runbook Phase 0b. Values come from the out-of-band felhom-system/storagebox
secret; optional:true so the pod starts without it (hub degrades to offsite
disabled). HETZNER_LOCATION explicit fsn1. NOT synced yet — gated on the
secret holding the DEDICATED-project scoped token (the current one fails the
scope check: it sees the old project's ep0 box, not the new pool box).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
Descriptor.HostFingerprint (SHA256, non-secret), captured at provision via an
x/crypto/ssh keyscan (SSHHostKeyScanner — dials :23, grabs the host key from the
handshake, no ssh binary). Fail-closed: nil scanner or scan failure → error (don't
serve a descriptor the controller can't verify). Pairs with controller v0.106.0
which re-scans + refuses on mismatch (no blind TOFU).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
Full cycle on the demo: enable→stage→real escrow-create (K+restic-pw under a
fresh R, uploaded to hub, staged wiped, K byte-unchanged)→atomicity proof (run
skipped before confirm, proceeded after — gate opens exactly on confirm). DR
money shot: restic password recovered from the PRODUCTION hub blob (via
identity-consume with R) is byte-identical to the live pw (sha 24c44f49) and
decrypted the real box repo; inject→restore endpoints reproduced a 45MB db-dump
byte-perfect. Demo left escrowed (un-paused); R held out-of-band; no secret in
logs; repo_password unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
Money shot (Probe 3): a password recovered from an age-under-R escrow of the
real repo password DECRYPTED the real Storage-Box repo (listed immich +
audiobookshelf snapshots) — working key, not just byte-match. Probe 1:
round-trip byte-perfect + wrong-R fails closed (0 plaintext). Probe 2:
controller→agent POST-body transport already proven (withGuest) — stage
endpoint is additive. Probe 4: WriteOffboxSecrets honors a pre-placed password
(DR re-injection), but config endpoint carries no password → explicit inject
step needed. All probes throwaway/discarded; live password + repo untouched.
Impl blueprint for the fork-4 spec inside.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
Real endpoints, fresh box sub-account. Discovery proof: immich (undeployed,
USB-only unit — the v0.103.0 mis-resolution case) → ok/1 snapshot on the USB
path; 45MB db-dump restore sha256-matched. No-silent-success on the live box:
0/N → error + backup_failed alert; partial → ok + LastWarning. Persists across
restart. Forks 1+2 confirmed (direct; sub-account /home/<name>, ssh-copy-id -s
-f, console key field still broken F3); fork-4 custody surface recorded, escrow
design OPEN (repo pw + key not independently recoverable). Kept as standing
demo-offsite.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
Validated the controller offbox restic-SFTP tier direct from the residential
demo box (IPv4-only egress) end-to-end through the real endpoints: reach on
:23 (PASS), chown-immune restic init (works where PBS-direct failed),
config→run→restore green, sub-account isolation confirmed. Residential upload
~4.3 MB/s. Concentrator NOT needed. KEY BLOCKER F1: offbox reads recovery unit
from the app data-drive namespace root, not the actual backup-drive location →
silently backs up 0 apps (ok status) on multi-drive setups. Spike only,
synthetic data, torn down.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
Wired the 1TB BX11 Storage Box to ep0 as a durable key-auth SSHFS mount
(/mnt/pbs-storagebox, reboot-proven). Findings: direct PBS datastore UNFIT
(box rejects chown -> ChunkStore::create fails); loopback-ext4-on-SSHFS FIT
with caveats (GC 7s/verify 12s/restore 11s all green, no GC hang). Raw mount
~327MB/s write, ~69MB/s read. Spike only, synthetic data, torn down.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
Replaces the static Option-1/2 Setup Command blocks with an interactive,
client-side builder: mode (required radio), cores/memory (required for byo,
optional for appliance), vmid, node, acl-storages (quote-wrapped),
operator-pubkey-file, preserve-state-from, and --dry-run/--preflight-only/
--skip-provision/--allow-new-leaf checkboxes. genFlags()/genUpdate() assemble
a live-updating download-then-run command (never curl|bash) + a local-run
variant, enforcing the script's own rules client-side (mode required; byo
requires caps → shows a warning + no runnable command; appliance hides the
caps requirement; allow-new-leaf shows its leaf-regen warning). Emits ONLY
real host-install v1.12.0 flags; the dangerous/operator-only set
(--force/--rotate-recovery/--enable-oob/--remove-golden/--uninstall/
--adopt-pool/--rescope-acl) is never offered. Graceful static fallback:
the server-rendered Option-1/2 commands keep --customer-id + a --mode
placeholder when JS is off. No framework/CDN/network; ScriptVersion (const,
in sync with SCRIPT_VERSION) drives the header. Render/structure test covers
the control ids, version, fallback, and the excluded-flag absence.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
The Setup Command panel rendered the per-customer retrieval passphrase (the
secret that fetches the WHOLE config) in cleartext twice — as #retrieval-pw
text and baked into the Option-3 curl's X-Retrieval-Password header — which
contradicts the panel's own "never on the command line" guidance. Now:
the retrieval password is MASKED by default (bullet run) with Reveal/Hide +
copy-secret controls (value lives in data-secret — the existing reveal
model); the Option-3 debug command carries a <YOUR-RETRIEVAL-PASSWORD>
placeholder, never the secret. Render test asserts the secret is not baked
into any command + is masked by default; red-proof (bake it back) FAILS.
(A zero-secret-in-DOM reveal-on-demand fetch is a noted follow-up, not this task.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
F3: the populated-host leaf guard now fires only when a FELHOM guest exists
(felhom_guests, not `pct list`) — a fresh byo install on a host running only
the OWNER's guests proceeds without --allow-new-leaf; the pin protection for
real Felhom guests is unchanged (guard still fires on a Felhom guest).
F6: byo preflight REFUSES (never mutates) when a foreign resolver is bound
to :53, with remediation — Felhom needs the guest reachable by name and will
not stop/mask the owner's DNS (the dnsmasq analog of the break-glass rule).
Viktor's ruling: refuse+instruct only, no stop/mask path. ss failure degrades
to a warn. Appliance mode untouched (the agent's lan_resolver owns :53).
F1: uninstall now purges the agent config's .bak* siblings (one GL-6 residue
held a live hub api_key), not just agent.json; WIPED statement updated.
Harness +GL8-F3/F6/F1 static + GL8-F1b behavioural + GL8-F6 live (byo
preflight refuses on felhom-pve's leftover dnsmasq — the real F6 scenario).
Red-proofs RP-F3/F6/F1 each fail their invariant. 36/36 on felhom-pve;
shellcheck clean; GL-2 Scenario-A + GL-4 Scenario-D regression green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6