v0.84.0: ReassertNetworkMounts — NAS automount survives guest reboots (RCA fix 1)

Storage §8 decision table (stop + enable --now on idle triggers; active mounts untouched),
daemon leg at startup with per-running-guest visibility verify, guest-hook post-start leg
(root, direct systemctl, non-fatal). Red-proofs: always-rearm table FAIL; unwired hook FAIL.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-11 20:46:59 +02:00
parent 0df72ea643
commit 474b858c0b
12 changed files with 715 additions and 27 deletions
+32
View File
@@ -1,3 +1,35 @@
## v0.84.0 — ReassertNetworkMounts: NAS automount survives guest reboots (RCA fix 1) (2026-07-11)
Agent half of the RCA fix pair (controller v0.117.0). Source:
`felhom.eu/documentation/audits/AUDIT-nas-cwa-rca-2026-07-11.md` — a fresh guest namespace inherits
REAL submounts (ext4/nfs4) but NOT an idle autofs trigger, so after any guest reboot an idle NAS
share silently degrades to a local stub inside the guest. The heal (re-create the automount → the
fresh trigger-mount event propagates live into running guests) was live-proven in the RCA
remediation; this release makes it automatic.
- **`internal/storage/netreassert.go`** `SudoHostOps.ReassertNetworkAutomounts`: per configured
network mount, the §8 decision table — real nfs/nfs4/cifs mounted → skip (inherited); `autofs`
trigger at the path → **stop + enable --now the `.automount`** (existing FELHOM_NETMOUNT sudoers
verbs; there is NO restart grant); neither → skip (removed/orphan states owned by add/remove).
Idempotent; per-share errors never stop the pass. Unit enumeration factored into
`networkUnitEntries()` (shared with `ListNetworkMounts`, behavior unchanged).
- **`internal/localapi/netreassert.go`** `Server.ReassertNetworkMounts`: the daemon leg — runs the
host-global pass once, then best-effort verifies each RUNNING guest actually sees each share path
(`GuestSeesMount`; the RCA's masking lesson). Type-asserted capability (the lean
`NetworkStorageOps` interface and its fakes stay unchanged — the main.go
`ReassertEnrolledMounts` pattern). Wired at agent startup after `ReassertGuestBinds`;
deliberately NOT in the 20 s ticker (an idle trigger is healthy and must not be churned).
- **`internal/guesthook/netreassert.go`** + `PhasePostStart`: the hook leg — PVE runs the hookscript
as root, so `guest-hook <vmid> post-start` re-arms triggers with DIRECT systemctl and verifies
via `GuestSeesPath` (hook-process mirror of GuestSeesMount). Non-fatal by contract (stderr → PVE
task log; exit 0 always); 30 s bound. The installed wrapper snippet already forwards all phases —
no snippet re-install needed.
- Tests + red-proofs: §8 table (red-proof: always-rearm shape → FAIL "nfs → rearmed, want
skip-active" — the live-mount churn the table prevents); rearm emits EXACTLY stop+enable-now on
the right unit; active mount → ZERO systemctl calls; idempotent double-pass; hook wiring
(red-proof: PhasePostStart case removed → FAIL "got []"); daemon leg verifies running guests
only; invisible-share verify is WARN-only (non-fatal).
## v0.83.0 — observability pass: always-DEBUG capture ring + GET /debug/logs + heartbeat log-pull + gap-fill sweep (2026-07-11)
Agent half of the cross-repo observability task (controller v0.116.0 + hub v0.46.0). Motivating