docs: REPORT — intermediary-mount re-architecture (agent half)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,51 +1,45 @@
|
||||
# REPORT — self-serve decommission + intent-aware re-assert → agent v0.32.0 (2026-06-14)
|
||||
# REPORT — intermediary-mount re-architecture (agent half) (2026-06-15)
|
||||
|
||||
Agent half of TASK B2 (the controller half ships as felhom-controller v0.65.0). Implemented trunk-based
|
||||
on `main` against the locked spike design
|
||||
(`felhom.eu/documentation/audits/SPIKE-decommission-migration-2026-06-14.md`). Built on 192.168.0.180,
|
||||
deployed + restarted on demo host `felhom-pve`.
|
||||
## Type
|
||||
Multi-commit build + live deploy + migration on the demo host (`felhom-pve`, guest 9201). Replaces the
|
||||
per-drive `pct set -mpN` guest bind (reboot-to-activate; bricks when a drive is absent at boot) with a
|
||||
single permanent **parent bind** `/mnt/felhom-drives` plus host-side drive hot-swaps underneath it. Pairs
|
||||
with controller v0.67.5 + the app-catalog repoint. Design: `felhom.eu/documentation/audits/SPIKE-intermediary-mount-2026-06-15.md`.
|
||||
|
||||
> Validate against pushed source, not this report.
|
||||
## Shipped (agent), each built + Linux-gated (`go vet`/`go test ./...` green) + deployed to felhom-pve
|
||||
- **v0.33.0 — C1 net (transitional).** Pre-start PVE hookscript (`internal/guesthook`) creates host-root
|
||||
placeholders for absent bind-mount sources so the guest always boots (fail-closed); decommission now
|
||||
`pct set --delete`s the dead mp (`GuestBinder.DetachBind`). **Live-proven**: reproduced the C1 brick
|
||||
(start exit 255) then the hook heal → clean boot.
|
||||
- **v0.34.0 — intermediary mechanism.** `EnsureSharedParent` (self-bind + `--make-shared` + boot unit),
|
||||
`AttachDrive`/`DetachDrive` (host `mount --bind <drive>/felhom-data` under the parent — propagates into
|
||||
the running guest live, confined, fail-closed), `ReassertGuestBinds` rewritten as a host-side reconcile
|
||||
(no pct/reboot). `/disks` adds `GuestPath` + `BoundUnderParent`. **Live-proven**: the startup reconcile
|
||||
bound felhom-flash under the parent, propagating 3-hop into the controller container live.
|
||||
- **v0.35.0 — guest-reboot re-propagation (load-bearing).** A guest's parent bind is NON-RECURSIVE, so a
|
||||
guest reboot leaves enrolled drives bound on the HOST but invisible in the fresh guest ns. `AttachDrive`
|
||||
now takes `vmid`, checks `GuestSeesMount` (`/proc/<init-pid>/mountinfo`), and FORCE re-binds to re-fire
|
||||
propagation; a 20s periodic reconcile self-heals guest reboots; `BoundUnderParent` reflects guest
|
||||
visibility. **Live-proven**: after a guest reboot the drive auto-re-propagated.
|
||||
- **v0.35.1 — host-boot ordering.** The shared-parent oneshot now `WantedBy=pve-guests.service` (pulled
|
||||
in + `Before`-ordered) instead of the unreliable `multi-user.target`; `EnsureSharedParent` reinstalls
|
||||
on content change. Unit verified reinstalled + enabled + runs (the actual host-reboot ordering is not
|
||||
validated — the production demo host can't be rebooted).
|
||||
|
||||
## Baseline → shipped
|
||||
- `main` was `8e6d00a` = **v0.31.0** → shipped **v0.32.0** (commit `f43697c`).
|
||||
Tests are non-hollow, each with a companion that fails the pre-fix/trivial impl (guesthook selector,
|
||||
decommission `--delete`, `GuestAttach` uses host-side AttachDrive not legacy pct, host-side reconcile,
|
||||
GuestPath/BoundUnderParent reporting). Sudoers extended (`FELHOM_GUESTHOOK`, `FELHOM_INTERMEDIARY`).
|
||||
|
||||
## What shipped
|
||||
- **`POST /disks/decommission`** (`internal/localapi/disks.go` `handleDiskDecommission`, route in
|
||||
`server.go`) — self-serve, NO operator signature, NON-destructive. Mirrors `handleDiskEject` exactly:
|
||||
`withGuest` self-scoping, `scopedFromBody`, and the same **user-data role gate** (`roleForMountPath` must
|
||||
be `RoleUserData`, else 403; fail-safe-to-protected). It records a PERMANENT `IntentDecommissioned`,
|
||||
prunes the `GuestBindStore` entry, and unmounts. It **NEVER** calls any format/mkfs path — the data stays
|
||||
on the drive. A compromised controller gains nothing beyond eject/customer-confirmed-wipe; the hub can't
|
||||
reach the local API at all.
|
||||
- **`ReassertGuestBinds` is now intent-aware** (the load-bearing F9-reconnect correctness fix): the
|
||||
startup re-assert skips any durable-id whose intent ≠ `enrolled`, so a decommissioned- (or ejected-)
|
||||
but-still-present drive is never auto-rebound into the guest on agent restart. Nil intent store → legacy
|
||||
bind-all (matches the watchdog's nil-intent rule). Covers both the self-serve and the operator-signed
|
||||
decommission paths (both land on `IntentDecommissioned`).
|
||||
- **`GuestBindStore.Remove(vmid, durableID)`** — idempotent (absent = no-op), atomic tmp+rename; drops the
|
||||
vmid key when its set empties. Re-enroll re-`Record`s via the existing `recordGuestBind`, so Remove
|
||||
doesn't break re-commission.
|
||||
- `IntentRecorder` extended with `SetDecommissioned` + `Get`. The operator-signed `DecommissionExecutor` +
|
||||
`reconcile.Classify` classification are **untouched** (the absent-drive/DR route).
|
||||
## Live migration (guest 9201) + verification
|
||||
felhom-flash (the drive with all 8 deployed apps) fully migrated to `/mnt/felhom-drives/felhom-flash`;
|
||||
the legacy per-drive `mp2` deleted; **a guest reboot self-converges with no manual intervention** (agent
|
||||
re-propagates the drive, controller startup-recreates the apps onto it). End state: **25 containers
|
||||
healthy** (komga's healthcheck is a pre-existing quirk), `/mnt/felhom-flash` gone from the guest,
|
||||
confinement intact (only `appdata/backups/media/userdata` cross in), data identical. felhom-usb (no
|
||||
deployed apps, stale `decommissioned` intent) left on its legacy bind — out of scope.
|
||||
|
||||
## Tests (`go build && go vet && go test ./...` green)
|
||||
`internal/localapi/decommission_test.go`: role-gate refuses system/backup (403, no unmount); decommission
|
||||
sets intent + removes the bind + unmounts + never formats (all three asserted); **intent-aware re-assert
|
||||
does NOT rebind a decommissioned-but-present drive** (companion: enrolled DOES rebind — and the guard was
|
||||
mutation-proven: removing it makes the test FAIL with the drive re-bound); re-commission re-records;
|
||||
`GuestBindStore.Remove` idempotency + persistence.
|
||||
|
||||
## Deploy / verify
|
||||
- Built v0.32.0 on 192.168.0.180 (`go build -ldflags "-X main.version=0.32.0"`); installed on felhom-pve
|
||||
at `/usr/local/bin/felhom-agent` (prior backed up to `.bak-0.31.0`); `systemctl restart felhom-agent` →
|
||||
**active**, `--version` 0.32.0.
|
||||
- **Live-proven on restart:** the new intent-aware re-assert logged
|
||||
`F9 re-assert: skipping non-enrolled drive (intent-gated) vmid=9201 durable_id=uuid:ce9d1d81-… intent=ejected`
|
||||
— the exact drive that lingered in `guest-binds.json` (B0 spike finding) is now correctly skipped (it
|
||||
would have been re-bound on the pre-fix code). Clean startup, no errors.
|
||||
|
||||
## NOT yet live-validated — awaiting supervised B3
|
||||
The endpoint's full effect via the real UI flow (customer decommission → drive un-enrolled → does not
|
||||
auto-mount/re-bind on physical reconnect, with real data on the drive) is deferred to the operator-present
|
||||
B3 session. The intent-gate itself is already proven live by the restart log above.
|
||||
## Known residuals (documented, not blocking)
|
||||
- Host-reboot ordering: unit fix shipped (v0.35.1) but not validated via an actual host reboot.
|
||||
- Guest-reboot app recreate is state-sampled (controller side): an app cleanly stopped at the exact
|
||||
sample instant may need one manual start (1/8 in one run). The deterministic fix is a guest-boot-id
|
||||
signal (future).
|
||||
|
||||
Reference in New Issue
Block a user