Files
felhom-agent/REPORT.md
T
admin 3e39dbb4f8 agent v0.37.0: re-assert mounted-but-DISABLED units (live felhom-usb fix)
The skip-if-mounted optimization defeated the actual root cause: felhom-usb is
mounted now but its unit is `disabled`, so a host reboot would not auto-mount
it. ReassertEnrolledMounts now skips ONLY the durable steady state (mounted AND
enabled) via the pure shouldReassertMount; a mounted-but-disabled unit is
re-asserted so enable --now re-creates the wants-symlink. Enabled-state read by
privilege-free Lstat of the multi-user.target.wants symlink (unitEnabled) — no
systemctl is-enabled subprocess, no new sudoers entry.

Tests: TestShouldReassertMount (4 combos), TestUnitEnabled (wants-symlink).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 17:55:48 +02:00

76 lines
4.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# REPORT — felhom-agent v0.37.0 (host-reboot remount by durable-id/UUID)
**TASK — Close out the reboot story (agent half: Task A + Task A2).**
## Task A — host-reboot remount resolves by filesystem UUID, never a cached node
### Diagnose-first (root cause, pinned LIVE on felhom-pve)
On a host reboot the kernel re-enumerates block devices; a drive can move node (felhom-usb
`/dev/sdb``/dev/sdc`). Two things could leave an enrolled drive unmounted after a reboot:
1. A systemd `.mount` unit left **`disabled`** by a prior detach (`Unmount` = stop + disable)
never gets a `multi-user.target.wants` symlink, so it does **not** auto-mount at boot — and
nothing in the agent re-asserted it at startup.
2. Any remount that trusted a remembered `/dev/sdX` node would target the WRONG device after a
letter move.
Confirmed live: felhom-usb's mount unit was `disabled` (`systemctl is-enabled``disabled`,
no wants-symlink) while felhom-flash's was `enabled`. The `What=` line was already correct
(`/dev/disk/by-uuid/<UUID>`), so the fix is to **re-assert the unit by UUID at startup**, not to
rewrite the unit.
### Fix
- `storage.ResolveStorageDevice(durableID)` (`durable_device.go`) — resolves the enrolled
`uuid:<fs-uuid>` storage scheme to its **current** backing `/dev` node by re-scanning
`/dev/disk/by-uuid` (`EvalSymlinks`); rejects any non-`uuid:` scheme (anti-node-binding) and
errors when the UUID is absent so a caller skips a gone drive instead of fail-mounting.
- `storage.parseFelhomMountUnit` (`mountunit.go`) — pure inverse of `renderMountUnit`, gated on a
`Managed by felhom-agent` marker; extracts Name/UUID/Where/Type/Options, ignores foreign units.
- `(*SudoHostOps).ReassertEnrolledMounts(ctx)` (`hostops.go`) — for each enrolled `.mount` unit,
re-resolve by UUID and re-run `EnsureMount` (idempotent `systemctl enable --now`). Skips ONLY the
durable steady state (mounted AND enabled), via the pure `shouldReassertMount`; a
**mounted-but-DISABLED** unit (the exact live felhom-usb state — serves now, but a reboot would not
auto-mount it) is still re-asserted so the wants-symlink is re-created. Enabled-state read with a
privilege-free `os.Lstat` of the `multi-user.target.wants` symlink (`unitEnabled`) — no
`systemctl is-enabled` subprocess / no new sudoers entry. Absent UUIDs skipped (retry next tick).
- `main.go` — runs `ReassertEnrolledMounts` at startup **before** `ReassertGuestBinds`, and again
ahead of the bind re-assert on the periodic 20s tick, so host mounts are live before the guest
binds re-establish.
### Tests (non-hollow, seam = device resolution; Linux)
- `TestResolveStorageDevice_ToleratesDeviceLetterMove` — fake `/dev/disk/by-uuid/<uuid>` symlink
pointed at `sdc` (moved from the enroll-time `sdb`); asserts `ResolveStorageDevice` returns the
CURRENT node `sdc`. **Companion (red-proof):** asserts the cached enroll-time node (`sdb`) differs
from the freshly-resolved node — a node-trusting remount would mount the wrong device.
- `TestResolveStorageDevice_AbsentAndScheme` — absent UUID errors; only `uuid:` is resolvable
(`/dev/sdb1`, `store:`, `byid:`, `""` all rejected).
- `TestParseFelhomMountUnit``renderMountUnit``parseFelhomMountUnit` round-trip + rejects a
non-felhom unit.
- `TestShouldReassertMount` — the four mounted/enabled combinations; pins that mounted-but-disabled
(the live bug) re-asserts and only mounted+enabled is skipped.
- `TestUnitEnabled` — wants-symlink presence ⇒ enabled (privilege-free `Lstat`).
## Task A2 — verdict: enrolling a NEW drive does NOT need an LXC restart
**NO.** Source-verified the enroll path: `/disks/guest-attach``handleDiskGuestAttach`
`GuestBinder.AttachDrive` (intermediary-mount model, "no pct, no reboot") — a live bind under the
single shared parent `/mnt/felhom-drives`, which provides unbounded **named** live slots. It does
NOT take the legacy `RebootGuest` branch. The operator's pre-created-slot-pool idea is unnecessary.
## Gate
`go build ./...`, `go vet ./...`, `go test ./...` all green locally (Windows: the symlink tests
skip — they assert on Linux, the agent's OS). Linux green gate + companion red-proof run on the
build server (192.168.0.180, go1.26.0) — see live-acceptance section.
## Live acceptance
Real host reboot ×2 on felhom-pve: before/after `lsblk`, shared-parent-before-pve-guests ordering,
both drives (felhom-usb + felhom-flash) remount by UUID across a `/dev/sdX` reshuffle, guest 9201
drive-backed apps recover, FileBrowser non-stale. (Captured during the supervised run.)
Secrets (local_api token / leaf fingerprint) handled out-of-band — none committed.