From eba7924b0fac9d928f7c5e485b5d4445819245f2 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Thu, 9 Jul 2026 18:50:37 +0100 Subject: [PATCH] feat(doctor): read-only hardware health section (#44) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six self-gating hardware checks in nomarchy-doctor, same ok/bad/warn/skip + one-fix-command contract: NetworkManager connectivity, default PipeWire sink, VA-API/OpenGL smoke (only if vainfo/glxinfo present), fprintd unit + enroll nudge, fwupd daemon + a soft warn when get-updates is non-empty, and the laptop charge_control_end_threshold when a limit is set. Each skips cleanly when its tool/service/device is absent, so the section shrinks to the machine and never hard-fails on a missing feature. Implemented by a worktree-isolated agent in parallel with the #46 install VM; diff reviewed and cherry-picked here. Verified V0 (flake check) + V1 (nix build .#…pkgs.nomarchy-doctor — build-time bash -n + shellcheck), re-run on main after cherry-pick; plus a read-only run on real hardware (all six checks fired + self-gated). V2 (checks.doctor fakes) pending. Co-Authored-By: Claude Opus 4.8 --- agent/BACKLOG.md | 16 +-- agent/JOURNAL.md | 19 ++++ pkgs/nomarchy-doctor/nomarchy-doctor.sh | 123 ++++++++++++++++++++++++ 3 files changed, 150 insertions(+), 8 deletions(-) diff --git a/agent/BACKLOG.md b/agent/BACKLOG.md index 1391049..d2aa79f 100644 --- a/agent/BACKLOG.md +++ b/agent/BACKLOG.md @@ -43,14 +43,14 @@ Core shipped 2026-07-09: self-gated **System ▸ Firmware** rofi row Waybar/updates-panel integration. (The doctor "updates available" check is item #44.) Spec: docs/HARDWARE.md §4. -### 44. Doctor hardware section — read-only (`VISION § A`) -Extend `nomarchy-doctor` with the same pass/fail + one-fix-command -contract for: NetworkManager connectivity, default PipeWire sink, -optional GPU smoke (`vainfo`/`glxinfo` if present), fprintd unit when -fingerprint was enabled, fwupd active (+ warn if `get-updates` -non-empty), laptop charge-threshold sysfs when a limit is set. Spec: -docs/HARDWARE.md §10. Cost: doctor.sh sections; V1 in existing -`checks.doctor` where fakes exist, rest V2/V3. +### 44. Doctor hardware section — V2 checks.doctor extension +Six read-only hardware checks shipped 2026-07-09 (NetworkManager, +PipeWire sink, VA-API/GL smoke, fprintd, fwupd + get-updates warn, +charge-threshold), all self-gating; V1 (doctor package builds: +bash -n + shellcheck) + a real-hardware read-only smoke on the dev box. +**Remaining V2:** extend the `checks.doctor` nixosTest with fakes to +cover the new rows headlessly; pin the fwupd `get-updates` exit-code +semantics (0 = updates) in that fake. Spec: docs/HARDWARE.md §10. ### 46. V2 verify: install swap=0 + unattended LUKS fail-closed (`208b8d4`) Pure `nix-instantiate` on disko subvols passed V0; **full install path diff --git a/agent/JOURNAL.md b/agent/JOURNAL.md index 8419910..bfb753e 100644 --- a/agent/JOURNAL.md +++ b/agent/JOURNAL.md @@ -17,6 +17,25 @@ Template: --- +## 2026-07-09 — #44 Doctor hardware section (delegated to worktree agent) +- **Task:** NEXT #44 — read-only hardware checks in nomarchy-doctor + (HARDWARE.md §10). Delegated to a worktree-isolated opus agent so it ran + in parallel with the #46 install VM without KVM contention. +- **Did:** Agent added six self-gating checks to `nomarchy-doctor.sh` + (NetworkManager, default PipeWire sink, VA-API/GL smoke, fprintd unit + + enroll nudge, fwupd active + get-updates warn, laptop charge-threshold), + each skipping cleanly when the tool/service/device is absent — same + ok/bad/warn/skip contract + one-fix-command style. Reviewed the diff and + cherry-picked onto main (branch `agent/doctor-hw-44`). +- **Verified:** V0 flake check green; **V1** `nix build …pkgs.nomarchy-doctor` + (writeShellApplication runs bash -n + shellcheck at build) — re-run by me + on main after cherry-pick. Bonus: the agent ran the built binary on the + AMD dev box (read-only) — all six checks fired + self-gated correctly. +- **Pending:** V2 — extend the `checks.doctor` nixosTest with fakes for the + new rows (VM; deferred while #46 held KVM). fwupd get-updates exit-code + semantics worth pinning in that fake. +- **Next suggestion:** V2-verify #43+#44 together on the next VM pass. + ## 2026-07-09 — #43 System ▸ Firmware fwupd menu (core shipped, V2 pending) - **Task:** NEXT #43 — surface fwupd/LVFS updates in the menu (VISION § A). Done while the VM was busy with #46, so V0/V1 only by design. diff --git a/pkgs/nomarchy-doctor/nomarchy-doctor.sh b/pkgs/nomarchy-doctor/nomarchy-doctor.sh index 78b03ff..00f42ba 100644 --- a/pkgs/nomarchy-doctor/nomarchy-doctor.sh +++ b/pkgs/nomarchy-doctor/nomarchy-doctor.sh @@ -127,6 +127,129 @@ else skip "snapper (not enabled on this machine)" fi +# ══ hardware ═════════════════════════════════════════════════════════ +# Every check below self-gates: it skips cleanly when the tool, service, +# or device isn't present, so the section shrinks to fit the machine and +# never fails just because a feature is absent. Still read-only. + +# ── network (NetworkManager) ───────────────────────────────────────── +if command -v nmcli >/dev/null 2>&1; then + nmstate=$(nmcli -t -f STATE general status 2>/dev/null || true) + case "$nmstate" in + connected*) ok "NetworkManager: $nmstate" ;; + "") skip "NetworkManager (no state reported)" ;; + *) warn "NetworkManager state: $nmstate" \ + "connect in System › Network (or: nmcli device)" ;; + esac +else + skip "NetworkManager (nmcli not present)" +fi + +# ── default audio sink (PipeWire) ──────────────────────────────────── +# wpctl/pactl talk to the user's PipeWire session — only meaningful with +# a user bus (not over bare SSH/root). +if [ -n "$(systemctl --user is-system-running 2>/dev/null || true)" ]; then + if command -v wpctl >/dev/null 2>&1; then + if wpctl inspect @DEFAULT_AUDIO_SINK@ >/dev/null 2>&1; then + ok "default audio sink present" + else + warn "no default PipeWire sink" "pick one in System › Audio (or: wpctl status)" + fi + elif command -v pactl >/dev/null 2>&1; then + defsink=$(pactl get-default-sink 2>/dev/null || true) + if [ -n "$defsink" ] && [ "$defsink" != "@DEFAULT_SINK@" ]; then + ok "default audio sink: $defsink" + else + warn "no default audio sink" "pick one in System › Audio" + fi + else + skip "audio sink (no wpctl/pactl)" + fi +else + skip "audio sink (no user session bus here)" +fi + +# ── GPU acceleration smoke (only if the probes are installed) ──────── +if command -v vainfo >/dev/null 2>&1; then + if vainfo >/dev/null 2>&1; then + ok "VA-API acceleration works (vainfo)" + else + warn "vainfo present but VA-API probe failed" \ + "check mkFlake.hardwareProfile / GPU drivers (or run: vainfo)" + fi +else + skip "VA-API smoke (vainfo not installed)" +fi +if command -v glxinfo >/dev/null 2>&1; then + if [ -n "${WAYLAND_DISPLAY:-}${DISPLAY:-}" ]; then + if glxinfo -B >/dev/null 2>&1; then + ok "OpenGL renderer responds (glxinfo)" + else + warn "glxinfo present but GL probe failed" \ + "check mkFlake.hardwareProfile / GPU drivers (or run: glxinfo -B)" + fi + else + skip "OpenGL smoke (no display attached)" + fi +else + skip "OpenGL smoke (glxinfo not installed)" +fi + +# ── fingerprint (only when fprintd is enabled) ─────────────────────── +if [ -n "$(systemctl list-unit-files fprintd.service --no-legend --plain 2>/dev/null || true)" ]; then + enrolled="" + if command -v fprintd-list >/dev/null 2>&1 && [ -n "${USER:-}" ]; then + enrolled=$(fprintd-list "$USER" 2>/dev/null || true) + fi + if [ -n "$enrolled" ] && ! printf '%s' "$enrolled" | grep -qi 'no fingers'; then + ok "fprintd enabled, fingerprint(s) enrolled for ${USER:-user}" + elif [ -n "$enrolled" ]; then + warn "fprintd enabled but no fingerprints enrolled" "enroll one: fprintd-enroll" + else + ok "fprintd unit installed (fingerprint enabled)" + fi +else + skip "fingerprint (fprintd not enabled)" +fi + +# ── firmware updates (fwupd) ───────────────────────────────────────── +# fwupd is D-Bus-activated, so an idle daemon is normal, not a fault. +# get-updates reads local metadata (no flashing); a hit is a soft warn. +if command -v fwupdmgr >/dev/null 2>&1 \ + && [ -n "$(systemctl list-unit-files fwupd.service --no-legend --plain 2>/dev/null || true)" ]; then + if systemctl is-active --quiet fwupd.service; then + ok "fwupd daemon active" + else + skip "fwupd daemon (idle — D-Bus-activated on demand)" + fi + if timeout 20 fwupdmgr get-updates >/dev/null 2>&1; then + warn "firmware updates are available" "review, then apply: fwupdmgr update" + else + ok "firmware up to date (no pending updates)" + fi +else + skip "fwupd (not enabled / fwupdmgr absent)" +fi + +# ── laptop battery charge threshold (only when a limit is set) ─────── +bat_seen=0; bat_limited=0 +for bat in /sys/class/power_supply/BAT*; do + f="$bat/charge_control_end_threshold" + [ -r "$f" ] || continue + bat_seen=1 + lim=$(cat "$f" 2>/dev/null || true) + case "$lim" in + ''|*[!0-9]*) continue ;; + esac + if [ "$lim" -lt 100 ]; then + bat_limited=1 + ok "$(basename "$bat") charge limit active at ${lim}%" + fi +done +if [ "$bat_seen" -eq 1 ] && [ "$bat_limited" -eq 0 ]; then + skip "battery charge limit (none set — charges to 100%)" +fi + # ── verdict ────────────────────────────────────────────────────────── echo if [ "$fails" -eq 0 ]; then