diff --git a/documentation/audits/SPIKE-smart-coverage-2026-07-25.md b/documentation/audits/SPIKE-smart-coverage-2026-07-25.md new file mode 100644 index 0000000..9ef75cb --- /dev/null +++ b/documentation/audits/SPIKE-smart-coverage-2026-07-25.md @@ -0,0 +1,152 @@ +# SPIKE — why the v0.169.0 disk-health card shows "Nincs adat" on real hardware, 2026-07-25 + +**Question:** the new "Lemezek állapota" card (controller v0.169.x) renders **Nincs adat** for both +physical disks on the live demo host (`demo-felhom`, a real N100 — NOT virtualized). Is SMART actually +unavailable on this hardware, or does the agent fail to *read* SMART it could read? If the latter, +where, and what are the fix directions? + +**Verdict up front:** SMART is **fully available** on both disks via the *exact* form the sudoers +allowlist already grants (`smartctl -a -j `) — the system SSD and the USB HDD both report +**PASSED**. The card says "Nincs adat" because the **agent never asks**, for two independent reasons: + +1. **`local` (the system SSD) → UNKNOWN:** its storage is a `dir` on `pve-root`, an **LVM logical + volume on `sda3`**. The agent leaves `backing_device` empty for it, and even if it didn't, + `smartDeviceFor` has **no device-mapper/LVM handling** — so the dir → LV → PV → whole-disk (`/dev/sda`) + chain is never resolved and `enrich` skips the SMART read. +2. **The USB HDD (`/dev/sdb`) → ABSENT:** it is surfaced only via the **`driveTargets.Known` union path** + in `handleDisks`, which sets `backing_device=/dev/sdb` but **never calls `enrich`** (no SMART read at + all) → the target's `Smart.Health` stays `""` → the controller (correctly) omits it → "Nincs adat". + +**Neither disk needs `smartctl -d sat`** — the USB-SATA bridge passes SMART through with plain +`-a -j`. The `-d sat` candidate is **disproven on this hardware**, and the sudoers allowlist does not +grant it anyway (recorded below). **This is findings-only — no fix implemented; the fix gets its own spec.** + +--- + +## Topology (Probe 1 — `lsblk -o NAME,TYPE,TRAN,MOUNTPOINT,MODEL`) + +``` +NAME TYPE TRAN MOUNTPOINT MODEL +sda disk sata AirDisk 512GB SSD +|-sda1 part +|-sda2 part /boot/efi +`-sda3 part (LVM PV) + |-pve-swap lvm [SWAP] + |-pve-root lvm / ← the "local" dir storage + `-pve-data-tpool … lvm ← pve-data thin pool (VM disks 9201) +sdb disk usb /mnt/hdd_1 TOSHIBA MQ04ABF100 ← the USB data drive +``` + +The customer's OS **and** every VM disk live on **`sda`** (SATA SSD, via LVM). The data drive is +**`sdb`** (USB). Both are real, SMART-capable disks. + +## The `/disks` payload (Probe 2 — controller-side curl to the agent localapi) + +``` +local-lvm | type=lvmthin | backing='' | mount='' | smart=UNKNOWN +local | type=local | backing='' | mount='' | smart=UNKNOWN +felhom-pbs | type=pbs | backing='' | mount='' | smart=UNKNOWN +47a3361a-… | type=usb | backing='/dev/sdb' | mount='/mnt/hdd_1' | smart=ABSENT +``` + +(The controller v0.169.1 already excludes `pbs`/`lvmthin` from the card, so only `local` and the USB +render — both "Nincs adat".) + +## Backing-path resolution (Probe 3 — `readlink -f`) + +``` +readlink -f /dev/sdb → /dev/sdb (already a clean whole-disk path; NOT a symlink) +readlink -f /dev/sda → /dev/sda +/dev/disk/by-uuid/47a3361a-… → ../../sdb (the durable_id's symlink; the payload already carries the resolved /dev/sdb) +``` + +→ **The "USB backing is a `by-uuid` symlink the regex can't match" hypothesis does NOT apply here** — +the payload's `backing_device` is already the resolved `/dev/sdb`. + +## SMART actually works on both (Probe 4 — the EXACT allowlisted form) + +``` +# smartctl -a -j /dev/sda (system SSD) +model: AirDisk 512GB SSD +smart_status.passed: True +messages: [] + +# smartctl -a -j /dev/sdb (USB TOSHIBA, behind its USB-SATA bridge) +model: TOSHIBA MQ04ABF100 +smart_status.passed: True +messages: ['Warning: This result is based on an Attribute check.'] +``` + +Both return **PASSED** with the plain `-a -j` form — **no `-d sat` needed**. (The USB result is +attribute-based, which is normal for a HDD behind a bridge; still a valid PASSED verdict.) + +## Sudoers / capability-manifest (Part 5.4 recording) + +- `capability/manifest.go:94` probes with `{"/usr/sbin/smartctl", ["-a","-j","/dev/sda"]}`. +- `/etc/sudoers.d/felhom-agent` grants **only** the `-a -j` form: + ``` + /usr/sbin/smartctl -a -j /dev/sd[a-z]*, + /usr/sbin/smartctl -a -j /dev/nvme[0-9]*n[0-9]*, + /usr/sbin/smartctl -a -j /dev/vd[a-z]*, + /usr/sbin/smartctl -a -j /dev/hd[a-z]*, + ``` + → `/dev/sda` **and** `/dev/sdb` are both permitted (the agent CAN read both today). **`-d sat` is + NOT granted** — a `-d sat` invocation would be denied by sudoers, so any `-d sat` fix needs a + sudoers **and** manifest widening. + +## Per-disk table — why the card says what it says + +| storage | backing (payload) | whole disk | `smartctl -a -j` | agent read path | card | root cause | +|---|---|---|---|---|---|---| +| `local` (dir/`pve-root`) | `''` | `sda` (via `/`→`pve-root`→`sda3`, **unresolved**) | **PASSED** | `enrich`: `catDir` but `backing_device==''` → SMART skipped | Nincs adat (UNKNOWN) | dir-on-LVM backing never resolved; `smartDeviceFor` has no dm/LVM branch | +| USB `47a3361a` | `/dev/sdb` | `sdb` | **PASSED** | `driveTargets.Known` union path — **`enrich` never called** | Nincs adat (ABSENT) | union path sets `backing` but runs no SMART read; `smartDeviceFor('/dev/sdb')` *would* resolve it | +| `felhom-pbs` / `local-lvm` | `''` | — (logical/network) | n/a | not `catDir` | (excluded, v0.169.1) | correct — not physical disks | + +`smartDeviceFor` (source): maps `…/nvme0n1pN → …/nvme0n1` and `…/sdbN → …/sdb`, then +`ValidateSMARTDevice`. A **whole disk** `/dev/sdb` passes through unchanged and validates → it would +resolve fine if `enrich` were ever called on the union path. **There is no branch for `/dev/mapper/*` +or `/dev/dm-*`,** which is why an LVM-backed dir can never resolve. + +--- + +## Fix directions (graded — DO NOT IMPLEMENT; each needs its own spec) + +### A — Resolve dm/LVM → whole disk (reaches the SYSTEM SSD `local`) +Resolve the dir's mount → LV → PV → whole disk (e.g. `lsblk -s -no pkname `, or +`/sys/block/dm-*/slaves`), set `backing_device` accordingly, and add a **dm/LVM branch to +`smartDeviceFor`**. +- **Reach: HIGH** — the system SSD holds the OS *and* all VM data; it is the single most important disk + to monitor. Also covers any other dir-on-LVM storage. +- **Risk: MEDIUM** — an LVM VG can span **multiple** PVs (a dir → N disks, not one); the resolver must + handle the multi-slave/RAID case (probe each, report per-disk) rather than assume one. Must not + mis-resolve stacked dm (thin pools). Needs a focused test over single-PV and multi-PV shapes. + +### B — Enrich the `driveTargets.Known` union path (reaches the USB / felhom data drives) +Run the same SMART read on `handleDisks`' union branch when `backing_device != ""`. +- **Reach: GOOD** — the felhom-enrolled *data* drives (the USB here), which is exactly what a customer + cares about most for a health card. +- **Risk: LOW** — the backing path is already a clean whole disk, `smartDeviceFor` already resolves it, + and sudoers already grants `/dev/sd[a-z]*`. Care points: the union path lives in `localapi` while + `enrich`/`SMART` lives in `storage` (reuse the `o.ops.SMART` seam or a small helper, no duplication), + and don't pay smartctl on *every* `/disks` call — the controller's 60s TTL cache already blunts the + storm, but consider a short agent-side memo if `/disks` is hit hard. **Cheapest win; do this first.** + +### C — `smartctl -d sat` retry (+ sudoers/manifest widening) — REJECT for now +- **Reach: NONE on this hardware** — the USB bridge already returns PASSED with plain `-a -j` + (disproven above). Would only matter for a *future* bridge that yields UNKNOWN with `-a -j`. +- **Risk: HIGH-relative** — requires adding `smartctl -d sat -a -j /dev/sd[a-z]*` (+ nvme/vd/hd) to + **both** `/etc/sudoers.d/felhom-agent` and `capability/manifest.go` (widening the root-command + allowlist — a privilege-surface change), plus a retry loop. **Keep as a documented fallback**, gated + on an actual UNKNOWN-with-`-a -j` disk appearing in the field. + +### D (minor) — `EvalSymlinks` before the partition regex +Not needed here (`backing_device` is already `/dev/sdb`), but cheap insurance for a future payload that +carries a `/dev/disk/by-*` symlink. **Low priority**, fold into A/B if convenient. + +**Recommended order (for the follow-up spec):** **B** (low-risk, covers the data drive) → **A** +(higher value, medium risk, covers the system disk) → skip **C** until a bridge demands it. + +--- + +**STOP.** No agent code, sudoers, or capability-manifest change was made in this spike (read-only +probes only). The fix is a separate task.