Files
felhom-controller/REPORT.md
T

3.5 KiB

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.goensureUserdataMounts 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.