v0.5.0-rc1: slice 5 Phase A — storage observe/report + watchdog (read-only, live)

Fill the slice-3 storage_targets stub and add the fast-poll storage watchdog.
Read-only this phase; the host-root surface (mounts/SMART/grow/destructive gate)
is Phase B. Hub-owned desired manifest is slice 10, so reconcile against it is
built-but-unfed.

- internal/storage: StorageTarget wire contract, durable_id derivation per type,
  HostReader seam (procfs/sysfs, root-free), Observer (storage_targets from
  ListStorage/NodeStorage + host reads, lvmthin thin-pool fill), and the watchdog
  (third daemon goroutine; debounced out-of-band report on a known target's
  attach/disconnect transition).
- proxmox.Storage: additive parse-only config fields (durable_id sources).
- collector StorageObserver seam; Loop.SetTrigger out-of-band report; daemon runs
  the watchdog as a third goroutine; StorageConfig knobs.
- cross-repo golden kept byte-identical with felhom.eu/hub; bidirectional key-set test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-09 09:59:05 +02:00
parent 1af21a6cac
commit 27b68f043b
22 changed files with 2129 additions and 103 deletions
+26 -1
View File
@@ -15,13 +15,38 @@ Module: `gitea.dooplex.hu/admin/felhom-agent` · binary: `felhom-agent` · Go 1.
## Layout
```
cmd/felhom-agent/ # entry point + --selftest (wiring only; no daemon loop yet)
cmd/felhom-agent/ # entry point + --selftest modes + the daemon (poll loop + reconcile + watchdog)
internal/proxmox/ # the Proxmox interaction layer (API-first + fenced root-CLI)
internal/config/ # JSON config + env overrides (secrets never logged)
internal/log/ # slog setup
internal/authz/ # operator signed-op verifier (SSHSIG); durable nonce store
internal/hub/ # daemon: host-report collector + Bearer client + resilient poll loop
internal/reconcile/ # reconcile engine + reversibility gate + op journal + crash recovery
internal/storage/ # storage-target observer + durable_id + fast-poll watchdog (slice 5)
configs/agent.example.json
```
## The `storage` package — observe + watchdog (slice 5)
Read-only this slice (no hub desired-state until slice 10):
- **Observer** builds the host-report's `storage_targets` from `ListStorage`/`NodeStorage`
joined with non-privileged host reads (`/proc/mounts`, `/dev/disk/by-uuid`,
`/sys/.../rotational`). It reports each target's `durable_id` (the DR-load-bearing
re-attach key: fs-UUID for usb/local-dir, `server:export` for nfs/cifs,
`repo+fingerprint` for pbs, `vg/pool` for lvmthin), state, usage, a rotational
**class hint** (never authoritative — class is hub-owned), and the lvmthin **thin-pool
data fill** (a full pool corrupts every guest on it). SMART is a Phase-B privileged read.
- **Watchdog** is the third daemon goroutine: a fast poll (seconds) over the *known*
target set that detects an `attached↔disconnected` transition and fires a **debounced,
out-of-band** host-report so the hub learns of a USB drop in seconds rather than at the
~15-minute cycle. It mutates nothing (the benign re-mount-by-UUID response lands in
Phase B). The `HostReader` seam keeps it root-free and unit-testable with no real devices.
The reported `StorageTarget` shape is a cross-repo contract duplicated in `felhom.eu/hub`;
`internal/hub/testdata/host-report.golden.json` is byte-identical with the hub's copy and a
bidirectional key-set test guards drift.
## The `proxmox` package — model
Two backends, one fixed routing policy (the fence is structural — `Client` never shells out,