111 lines
8.2 KiB
Markdown
111 lines
8.2 KiB
Markdown
# REPORT — v0.87.0: SystemDisks device-mapper walk (IA finding 2, MEDIUM)
|
||
|
||
**Date:** 2026-07-13 · **Version:** felhom-agent v0.87.0 (from v0.86.0, baseline `c20814e` confirmed) · Agent-only classification fix; MinAgent/controller coupling: none. **Safety direction:** the outcome made impossible is a root-backing disk classified candidate; the fixed failure is the opposite over-protection (legacy-boot → everything system → dead wizard).
|
||
|
||
## §3 spike (live, BEFORE code — both transcripts)
|
||
|
||
- **Drill host** (root@192.168.0.152, the exact IA-failure shape): legacy boot — `/` from
|
||
`/dev/mapper/pve-root` → `readlink -f` → **/dev/dm-1** → `/sys/block/dm-1/slaves` = **sda3** →
|
||
`/sys/class/block/sda3/partition` exists → parent **sda**. NO /boot/efi mount (sda2 vfat
|
||
exists, unmounted — classic legacy shape). Bonus fact baked into the design: a PHYSICAL disk's
|
||
`slaves/` dir EXISTS and is EMPTY (`ls /sys/block/sda/slaves | wc -l` = 0) — so `hasDir` alone
|
||
is not resolution; the walk requires non-empty slaves to ground a virtual device.
|
||
- **felhom-pve** (regression baseline): `/` → dm-1 → slaves sda3 → sda AND `/boot/efi` →
|
||
/dev/sda2 → sda — ESP and walk agree on the same disk ⇒ post-fix output must be identical.
|
||
Pre-upgrade API captures saved (scratchpad `pve-disks-pre-0870.json`, `pve-cands-pre-0870.json`,
|
||
`drill-pre-0870.txt`): felhom-pve roles (felhom-usb/felhom-flash/raw-sdb user-data, local/lvm
|
||
system, pbs/offsite backup), candidates EMPTY on both hosts pre-fix.
|
||
|
||
No surprises → proceeded.
|
||
|
||
## Implementation (commit on main)
|
||
|
||
- `internal/storage/role.go`: `SystemDisks` now grounds every system mountpoint via
|
||
**`physicalDisksOf`** — symlink canonicalization → `wholeDiskOf` fast path (raw disks/
|
||
partitions, unchanged) → **`walkSlaves`**: recursive `/sys/block/<name>/slaves` resolution for
|
||
virtual devices (dm-*, md*), partitions resolved by the existing regexes (`sda3 → /dev/sda`),
|
||
cycle/depth guard (visited set, cap 32). Per-branch conservatism (operator ruling): ANY
|
||
unresolvable slave — dangling entry, unrecognizable name (e.g. `md0p1`), virtual with empty
|
||
slaves — fails the WHOLE walk → `ok=false` → the UNCHANGED all-system fail-safe. Non-`/dev`
|
||
sources (ZFS dataset, NFS, overlay) are unwalkable → fail-safe (same as pre-fix).
|
||
- `internal/storage/hostread.go`: **`HostReader.BlockSlaves(name) ([]string, bool)`** — the one
|
||
new seam method (root-free; sysfs world-readable); `ProcHostReader` reads
|
||
`<sysBlockDir>/<name>/slaves`; all four test fakes mirror it (storage `fakeHostReader` gains a
|
||
`slaves` map; localapi `fakeHostReader`/`uuidHostReader`/`f2HostReader` return nil,false).
|
||
- **Caller audit:** `handleDisks`, `roleForMountPath`, `deviceRole` (localapi/disks.go) and
|
||
`gatherClaimFacts` (storage/claim.go) — all consume `SystemDisks` through
|
||
`isSystemBacked`/`classifyClaim` fail-safes; NONE relies on all-system as a feature (finding:
|
||
none needed changes). Format/mkfs paths, data-bearing guards, wizard UI untouched.
|
||
- **Behavior deltas:** legacy-boot LVM → resolves, wizard lives; EFI(+LVM) → byte-identical;
|
||
md-raid root → BOTH members system. New PROTECTIVE delta: an ESP-resolving host whose root
|
||
topology cannot be grounded is now all-system (pre-fix trusted the ESP alone) — per the
|
||
ruling's conservatism; direction-safe.
|
||
|
||
## Tests + red-proofs (fake sysfs via the HostReader seam)
|
||
|
||
`role_walk_test.go` — fixtures mirror the §3 transcripts: legacy-LVM (dm→part→disk), md-raid
|
||
(md→2 disks, BOTH asserted system), EFI+raw, EFI+LVM (felhom-pve), nested dm-on-md;
|
||
dangling-slave (with a load-bearing RESOLVABLE sibling branch so a skip-mutation yields a
|
||
plausible partial set); cycle; virtual-with-empty-slaves. **Signature test
|
||
`TestSystemDisks_WalkTopologies`** asserts the root-backing physical disk(s) are ALWAYS in the
|
||
system set + the scratch disk never is + role-level consequences — never weaken.
|
||
|
||
Red-proofs (mutate → fail → revert, verbatim):
|
||
- **A (pre-fix resolver, no walk):** `TestSystemDisks_LegacyBoot_WizardLives` FAILS —
|
||
"legacy-boot topology must resolve (the all-system fail-safe is the ERROR case…)" — the exact
|
||
dead-wizard live shape; the signature table fails on legacy/md/nested too.
|
||
- **B (walk returns the dm node):** `SIGNATURE VIOLATION: root-backing disk /dev/sda missing
|
||
from system set map[/dev/mapper/pve-root:true]` (+ md0 / nested variants naming both disks).
|
||
- **D (per-branch conservatism removed):** `TestSystemDisks_DanglingSlave_FailSafe` FAILS —
|
||
scratch classified candidate while the walk is incomplete (asserted through the REAL
|
||
`sysKnown=false` → `isSystemBacked`/`RoleForRawDevice` path, not a lookalike).
|
||
|
||
Full suite: `go test ./...` green (all packages; recovery-code flake did not fire).
|
||
|
||
## §13 deploy + regression (DONE) + the IA replay (agent half)
|
||
|
||
- Built on 180 (`sha256 2447d4a3…dc7a`); deployed **drill host first** (0.86.0 → 0.87.0,
|
||
`.bak-0.86.0` kept, service active, clean journal), then **felhom-pve** (0.86.0 → 0.87.0,
|
||
`.bak-0.86.0` kept, capabilities self-check **62/62 ok, degraded=0, inactive=0**).
|
||
- **13.3 regression (felhom-pve, the §3 pre-captures vs post):** `/disks` roles BYTE-IDENTICAL
|
||
(all 7 targets: felhom-usb/felhom-flash/raw-sdb user-data, local/local-lvm system,
|
||
felhom-pbs/felhom-offsite backup) and `/disks/candidates` identical (empty pre and post).
|
||
- **The exact IA-failure replay (drill, API level — the endpoint the wizard UI invokes):**
|
||
hot-added a 5 GB scsi disk to qm 300 (`local-lvm:vm-300-disk-1`, appears as /dev/sdb in the
|
||
drill PVE) → `GET /disks/candidates` now returns
|
||
`initialize: [{device:/dev/sdb, 5 GiB, QEMU HARDDISK, data_bearing:false}]` — pre-fix the
|
||
same call returned empty (drill-pre-0870.txt). The root disk sda is NOT offered (safety
|
||
direction). **The scratch disk is left ATTACHED for the supervised UI leg.**
|
||
- Published to Gitea: `AGENT_VERSION=0.87.0`, `AGENT_SHA256=2447d4a345115db0b380779f251750f3d8c48ba9e89aeaac162fd8c74ce8dc7a`
|
||
(creds from the 180 docker-config, out-of-band). felhom.eu disposition note:
|
||
`documentation/audits/DISPOSITION-ia-finding2-systemdisks-2026-07-13.md` (commit `74fa61c`).
|
||
|
||
## §13.2 wizard live leg — COMPLETE (operator session + GO; finished same day)
|
||
|
||
- Wizard **OFFERED** the hot-added 5 GB disk (Üres — formázható) on the legacy-boot drill box —
|
||
the exact pre-fix dead end. Operator enrolled + formatted through the REAL UI flow
|
||
(mount name enroll-test, ext4, default): "A meghajtó sikeresen inicializálva és regisztrálva:
|
||
/mnt/felhom-drives/enroll-test"; storage page showed the drive Active/Default, role
|
||
Felhasználói adat, uuid durable-id. (First attempt hit the controller-side CSRF bug on
|
||
claimed-box wizard pages → fixed as controller v0.126.3, unrelated to the agent.)
|
||
- **Teardown (leave as found):** decommission attempted through the UI → correctly REFUSED by
|
||
the controller's M1 guard (last usable drive; the refusal surfaced badly → controller
|
||
v0.126.4 fixed the 502-through-Cloudflare + native-alert classes). Teardown completed at
|
||
pipeline level: agent `/disks/decommission` (guest token) → ok (logical retire per design;
|
||
the raw mount/unit intentionally stays for re-enroll — removed manually as the "physical
|
||
removal" step), controller registry entry removed (0 storage paths — the pre-enroll state),
|
||
mount unit stopped + file removed, `qm disk unlink 300 --idlist scsi1 --force` (LV
|
||
vm-300-disk-1 destroyed). Verified: /proc/mounts clean, candidates empty, drill gate back ON.
|
||
- **Day-0 manifest**: vouched by the operator — agent **0.87.0**, sha `2447d4a3…dc7a`
|
||
(hub Configuration screenshot; golden 0.120.0 + MinAgent 0.81.0 unchanged).
|
||
|
||
## Observations
|
||
|
||
- No `SystemDisks` caller relied on the all-system behavior as a feature (audited: disks.go ×3,
|
||
claim.go ×1) — the fix is purely widening resolution.
|
||
- md-raid fixture lesson: partitions of an md array (`md0p1`) are NOT covered by the partition
|
||
regexes — such a slave fails the walk → all-system (documented in code; acceptable, exotic).
|
||
- A physical disk's `slaves/` dir exists and is empty (live-probed) — any future sysfs-walking
|
||
code must treat "dir present" ≠ "virtual device".
|
||
- Pre-existing `gofmt -l` hits (capability/probe.go, escrow/*.go) — not touched, not mine.
|