Files
felhom-controller/REPORT.md
T

5.2 KiB

REPORT — most recent implementation

v0.141.0 — N100 polish: initialize-to-usable (F6) + Vissza back-routes (F7) — 2026-07-17

Baseline: v0.140.0 (5be2449) → v0.141.0 (deployed live on demo 9201, healthy). Green: go build ./... && go vet ./... && go test ./... all pass; template/emoji/native-confirm gates pass. Closes felhom.eu/documentation/tests/VALIDATION-n100-baremetal-2026-07-16.md F6 + F7. (F5 lives in felhom.eu scripts/; see that repo's REPORT.)

Fork verdict (Part 1 Phase 1, source-grounded)

F6 is controller-side — NO agent change. The detached mkfs lives agent-side (felhom-agent/internal/localapi/formatjob.go:99 + disks.go:635, crash-safe, mkfs-only), but the format→mount→register ORCHESTRATION is internal/web/storage_handlers.go runStorageInit and ran on the REQUEST context; a disconnect cancelled it after FormatDisk (agent mkfs continues detached, returns errFormatClientGone) → the mount+register leg never ran → device formatted but unusable. The chain must reach SyncFileBrowserMounts (handlers.go:2011, controller-only), so a single detached job can only live controller-side. The agent already exposes crash-safe format + assign → agent v0.90.0 was NOT triggered.

F6 — initialize now ends in a mounted+registered drive, disconnect-safe

  • POST /api/storage/init starts a DETACHED single-flight job (internal/web/storage_init_job.go, the netAddState shape) on context.Background(); runStorageInit gained a nil-safe phase callback (formatting → mounting → registering). The wizard polls GET /api/storage/init/status and renders the 3-step progress (storage_init.html); confirm/refuse verdicts surface through the same poll. Register is the LAST step (marker-last, Scenario B) + idempotent prior steps (AddStoragePath dedups) → a crash leaves at most an unregistered orphan, never a broken/duplicate registration.
  • Deeper half (found on the live leg): a slow mkfs (a 64 GB USB) outruns the agentapi client's 15 s Timeout; the agent runs it DETACHED and records the job, so runStorageInit now POLLS the agent's GET /disks/format/status (new agentapi.Client.FormatStatusawaitAgentFormat) to the terminal outcome on a client timeout, then continues to mount+register (the F6 root-cause's "mkfs continues detached; poll the status").
  • Red-proofs (run-fail-revert): TestStorageInit_DetachedSurvivesClientDisconnect (pre-fix cancelled-ctx chain fails at mount, NOT registered → FAIL; fixed: detached job registers exactly once) + TestStorageInit_PollsAgentFormatStatusOnTimeout (timeout→done registers; timeout→failed surfaces the error).

F7 — Vissza back-routes

The "Vissza" (Back) anchor on /storage/init and /storage/attach now routes to /storage (was /settings); the init success link also points to /storage. Test TestStorageWizardBackAnchors_PointToStorage.

Live validation (F6 Scenario A, on the demo scratch USB)

Method: the REAL controller endpoints (curl against the in-guest controller with the dashboard Host header + a login session + the CSRF token — the exact calls the UI makes; browser tool not used). Scratch device /dev/sdd (serial 20120926571200000, 59.5 GB) serial-checked against the enrolled list FIRST (≠ the enrolled /dev/sdb hdd_1; ≠ OS /dev/sda) and confirmed present in the agent-proven initialize candidates. Flow: init (no confirm) → needs_confirmation (ext4) via poll → init (confirmed) → {started} (returns immediately; the client can leave) → detached job formatting (~27 s mkfs, past the 15 s client timeout → the status-poll fix engaged) → mountingdone at /mnt/felhom-drives/scratch1. Verified: /dev/sdd mounted at the enrolled stable bind /mnt/felhom-drives/scratch1 (/dev/sdd[/felhom-data]); the enrolled hdd_1 untouched.

Security-review acknowledgment (background review of the commit)

Flagged a TOCTOU in runStorageInit (format → resolveEnrollUUID(path)AssignDisk(uuid)). Assessment: pre-existing (unchanged sequence — F6 only moved it off the request context and added the timeout status-poll, which widened the window by the poll interval). The DESTRUCTIVE step (format) is the authorization boundary and is agent-enforced with anti-retarget durable-id binding — the controller cannot make the agent format the wrong device; the subsequent mount is benign and keyed by fs-UUID (a moved /dev node resolves to a non-existent UUID → a mount failure, not data loss or an authz bypass). No new authorization bypass. Left as an Observation (not expanded this batch per the minimal-changes mandate); a future hardening could bind resolve+assign to the format's durable-id.

Operator sidebar (Viktor — CC does NOT do these; recorded verbatim from the spec)

  1. BIOS "State After G3" → "Power On"/"Last State" before final sign-off — also added to the hardware-prep notes in scripts/iso/README.md.
  2. campaign6 autofs orphan — clears at the next host reboot.
  3. tied-CreatedAt flash duplicates (audiobookshelf/komga/romm) — standing action item.

NOT live this task

F5 (guest auto-sizing) is validated by harness only; live at the next from-scratch rehearsal (Viktor's planned delete/re-create run) — see felhom.eu REPORT.