From 47eb0bf96722d51abf53af258d20898d4de69674 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Sun, 12 Jul 2026 07:52:11 +0200 Subject: [PATCH] =?UTF-8?q?agent=20v0.85.0:=20boot/recovery=20plane=20(F12?= =?UTF-8?q?/F11/F10/F9/F2/F1)=20+=20appliance=20self-heal=20=E2=80=94=20CH?= =?UTF-8?q?ANGELOG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_017CDMFpFx84pfviCTVuGGhf --- CHANGELOG.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a8a6a2..1083e7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,62 @@ +## v0.85.0 — the boot/recovery plane: F12 ordering-cycle fix + F11/F10/F9/F2/F1 + appliance self-heal (2026-07-12) + +Fixes the findings CAMPAIGN-3 (`felhom.eu/documentation/audits/CAMPAIGN-3-2026-07-11.md`) raised +around the NAS automount lifecycle — the data plane held, the reboot/recovery plane did not. + +- **F12 (CRITICAL) — boot ordering cycle.** `internal/storage/netmount.go`: BOTH rendered units drop + `After=/Wants=network-online.target`. The `.mount` keeps `_netdev` (the correct, sufficient network + ordering — systemd classes it remote-fs); the `.automount` gets NO network relation (a trigger + needs none, and it must stay orderable before local-fs without dragging the network into the + transaction). The literal ordering had closed the cycle + networking→local-fs→automount→network-online→networking, which systemd broke by deleting an + arbitrary job — one boot lost networking entirely (host dark 7 h), the next lost the automount. + **Installed-unit migration:** `MigrateNetworkUnits` — a general template-drift reconcile + (SHA-256 content compare of each marker-owned unit vs a fresh render of its reconstructed spec; + rewrite + one batched `daemon-reload`; idempotent). Runs at daemon startup before the reassert + sweep and at the head of `EnsureNetworkMount`, so pre-0.85 units carrying the cycle are repaired, + not just future adds. +- **F11 (HIGH) — read the right unit.** `internal/storage/netreassert.go` `netReassertClassify`: the + re-arm decision is driven ONLY by the host `/proc/mounts` fstype at the mountpoint. Active + nfs4/cifs → skip-active (inherited by fresh namespaces); anything else → re-arm. The `.automount` + unit's own state is never consulted (an armed trigger always reports active — the mis-skip trap). +- **F10 (CRITICAL) — re-arm for real.** A `.mount`/`.automount` left `failed`/start-limit-hit (the + campaign's unexport→idle-timeout→access×5) is `reset-failed` FIRST (new sudoers verb) — without it + the `enable --now` is refused by the start limit and the share stays dead across every boot. The + failed-state read is unprivileged (`systemctl is-failed`, seam-injected). +- **F9 (HIGH) — say what you did.** The pass enumerates by marker-owned unit files on disk (not + enablement/runtime state) and logs an INFO verdict line for EVERY share + (reasserted / reset-failed+rearmed / skip-active / skip-foreign / error) — an empty-looking sweep + over N shares is now impossible. +- **F2/F1 (LOW) — zero residue.** `RemoveNetworkMount` (and, via the same path, every verify-fail + rollback) reset-failed's the pair before removing the files (no `not-found failed` residue) and + `rmdir`s the now-empty mountpoint (F1 — the campaign's 10 stub-shaped leftovers). rmdir ONLY: a + non-empty dir is left in place with a WARN (fail-safe; never `rm -rf`). +- **The hook can never take a guest down (F10/rc255).** `cmd/felhom-agent/main.go` `runHookPhase`: + every guest-hook phase runs recover-wrapped under a hard timeout and returns cleanly (a panic → + logged to the PVE task log, swallowed; an overrun → abandoned). The installed wrapper snippet no + longer `exec`s — it runs the binary as a child, `|| true`, and `exit 0` (the shell belt). +- **Appliance-mode node self-heal (Part 6).** New `internal/selfheal` package: a minimal + check/remedy registry gated on `deployment_mode`. One heal ships — host networking recovery + (F12-class defense in depth): Healthy ⇔ networking.service active AND a default route; the remedy + (`systemctl start networking.service`, new sudoers verb, ≤3 attempts, 10/30/60 s backoff, terminal + give-up logged) runs ONLY on `deployment_mode:"appliance"`. A byo host runs the check + WARNs; the + remedy is structurally unreachable (the Manager gates before any exec). Absent/unknown mode → byo + (fail-safe). Give-up is log-only (no natural HostReport field; the report schema is out of scope) — + the ERROR lands in the always-DEBUG applog ring for a hub bundle-pull. +- **Config:** top-level `deployment_mode` (`internal/config`, `+FELHOM_AGENT_DEPLOYMENT_MODE` overlay, + `IsAppliance()` fail-safe-to-byo). NOT overloaded onto `Privileged.Mode`. +- **Sudoers (loud, per the no-widening rule):** two narrow additions — + `systemctl reset-failed -- mnt-felhom*` (F10) and `rmdir /mnt/felhom-drives/*` (F1, fail-safe: + rmdir refuses a non-empty dir) in FELHOM_NETMOUNT; `systemctl start networking.service` as the new + FELHOM_SELFHEAL alias (appliance self-heal; the grant alone cannot harm — starting networking is + what boot should have done; the remedy is ALSO code-gated on appliance). Capability manifest gains + the three representative probes. +- Tests: F12 render (no network-online, `_netdev` present) + reconcile (drift rewritten once, + idempotent, batched reload, round-trip) + foreign-unit ignore; reassert fstype table with the + automount-state-ignored red-proof, reset-failed+rearm, per-unit verdict count; hook rc-0 under + panic/timeout; zero-residue (reset-failed + rmdir, never rm -rf); selfheal state machine + + byo-never-invokes + absent⇒byo. All green. + ## 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: