Files
felhom-controller/REPORT.md
T

56 lines
3.5 KiB
Markdown

# REPORT — controller v0.75.0: gate userdata MkdirAll on a live mountpoint
## Baseline → target
`felhom-controller` `main` v0.74.0 → **v0.75.0**. Controller-only; agent/hub untouched.
## Problem
Two `MkdirAll`-into-`<drive>/userdata` sites fired without checking the drive was mounted (campaign-#2
findings #2/#3): the deploy belt (`ensureUserdataMounts`) and the FileBrowser sync. During a
drive-absent window they logged `mkdir …/userdata: permission denied` + caused transient `Created`
container flapping — and worse, writing into an unmounted mountpoint lands app data on the guest
**rootfs**, shadowed when the drive returns (data-integrity + rootfs-fill hazard).
## Change (commit `0b2605c`)
- `internal/stacks/manager.go``ensureUserdataMounts` skips when the `HDD_PATH` drive root is an
**external** path (`!= sysDataPath`) that is **not a live mountpoint**; the app is held by
`planDriveGates`. New injectable `Manager.isMountPoint` seam (defaults to `system.IsMountPoint`).
The system/local path is never gated.
- `internal/web/handlers.go` — the FileBrowser sync loop skips (and doesn't mount) a path under
`StableParentDir` that isn't a live mountpoint, via the new pure `skipFileBrowserPath` helper.
- `EnsureUserdataDir`/`EnsureUserdataSkeleton`/`planDriveGates` unchanged (gated the callers).
## Tests (green; red-proofed)
`go build ./... && go vet ./... && go test ./...` all green.
- T1 `TestEnsureUserdataMounts_SkipsAbsentExternalDrive` — belt skips when external drive absent.
Red-proof: removing the gate → dir created → FAIL (shown, reverted).
- T2 `TestEnsureUserdataMounts_EnsuresWhenMounted` — belt ensures when drive mounted.
- T3 `TestEnsureUserdataMounts_SystemPathNeverSkipped` — system/local path never gated (must-not-over-gate).
- T4 `TestSkipFileBrowserPath` — only an absent external path is skipped; mounted + system paths kept.
Red-proof: gate→false → absent `usb` not skipped → FAIL (shown, reverted).
## Deploy
Built+pushed `gitea.dooplex.hu/admin/felhom-controller:0.75.0` on 192.168.0.180 (digest
`sha256:f735676e…`; **build-server `git pull` done first** — the v0.74.0 stale-source gotcha),
deployed to guest 9201 via the bootstrap path. `docker inspect`: `image=:0.75.0 running healthy`.
## LIVE acceptance — campaign-#2 C6 re-run, now clean
Disconnected felhom-flash (8 apps) and observed the drive-absent window:
| Check | campaign #2 (pre-fix) | **v0.75.0 (fixed)** |
|---|---|---|
| `mkdir …/userdata: permission denied` | many | **0** |
| "could not ensure userdata skeleton" | present | **0** |
| rootfs shadow dir at unmounted mountpoint | created | **none**`/mnt/felhom-drives/felhom-flash/userdata` does not exist (bare mountpoint) |
| apps during absent window | transient `Created` flapping | held cleanly (down) |
| reconnect | recovered | **recovered: flash remounted /dev/sdc1, 8 apps restarted → 25 running / 0 unhealthy** |
The integrity hazard (app data shadowed onto the rootfs) is closed: the now-unmounted mountpoint is
empty, no `userdata/...` was written to the rootfs.
## Not touched / design note (see CONTEXT.md)
The **boot-time** `mkdir … permission denied` (daemon boot-restore) is a different cause — **docker**
auto-starts drive-backed containers (`restart: unless-stopped`) before the agent mounts the drives, so
docker (not the belt) tries to create the bind source; `planDriveGates` recovers them after mount
convergence. This task ships only the runtime gate; the boot-ordering decision is documented, not
implemented.