diff --git a/agent/BACKLOG.md b/agent/BACKLOG.md index 1a26ebc..319c471 100644 --- a/agent/BACKLOG.md +++ b/agent/BACKLOG.md @@ -30,48 +30,6 @@ These are separate queue items from one real install/session pass. Preserve that separation when fixing them: the installer blocker, unclear installer copy, and post-install desktop failures have different verification paths. -### 118. smartd fails where no drive has SMART, so the health icon greets you red - -Bernardo, live ISO 2026-07-14: the Waybar doctor module shows a smartd error. - -**Root cause, reproduced headlessly 2026-07-14** (scratch `runNixOSTest`, a -node whose only config is `services.smartd.enable = true` — the same block -`modules/nixos/default.nix:166` mkDefaults ON for every machine): - - smartd[567]: In the system's table of devices NO devices found to scan - smartd[567]: Unable to monitor any SMART enabled devices. Exiting. - smartd.service: Main process exited, code=exited, status=17/n/a - Status: "No devices to monitor" - -`smartctl --scan` prints **nothing** on such a machine — which is both the -cause and the ready-made gate. The rest of the chain is each part working -correctly: `systemctl --failed` lists `smartd.service`, so nomarchy-doctor's -generic failed-unit check (`nomarchy-doctor.sh:24-28`) reports "failed system -unit(s): smartd.service", and Waybar's `#custom-doctor` goes `@bad`. **Fix -smartd, not the doctor** — the doctor is telling the truth about a unit that -genuinely failed. - -**Scope is wider than the live ISO** (which is why it's here and not filed as -a live-only nit): `services.smartd.enable` mkDefaults true on `nomarchy` as -well as `nomarchy-live`, and QEMU virtio disks expose no SMART — so a plain -VM install boots to a red health icon out of the box, as does a live USB whose -stick has no SMART. Every V2 QEMU run has been showing this. - -Fix direction: **self-gate on the hardware**, the convention the rest of the -distro follows (`ExecCondition`, like the night-light; "each half self-gates -on its hardware"). A condition that runs `smartctl --scan` and skips the unit -when it finds nothing leaves the unit *inactive* rather than *failed*, so real -SMART failures on real drives still surface. Do **not** paper over it with -`SuccessExitStatus = 17` — that also swallows the case where smartd dies on a -machine that does have drives, which is the whole reason the daemon is here. - -Pass = on a machine with no SMART-capable device the unit is inactive (not -failed), `systemctl --failed` is empty and the doctor is green; on a machine -with one, smartd runs as it does today; a permanent `checks.*` covers both -halves (the scratch repro above is most of it — the no-SMART node exists, the -with-SMART node needs a QEMU disk that answers SMART, or the gate script -tested directly against a stubbed `smartctl`). - ### 94. Live ISO/install: no default browser observed **Progress 2026-07-13:** installed path VERIFIED at V1 — the exact HM @@ -177,10 +135,11 @@ distro: "works on a plane" and "8 GiB is absurd to download" are both true, and a second target settles them without compromising either. **Measured facts (2026-07-14), so this starts from numbers, not vibes.** -*(Taken before #121 landed; it removed a duplicate 687 MiB chromium, so the -uncompressed figures below are each ~0.67 GiB lower now — the live desktop is -**13.34 GiB**, not 14.01. The shape of the argument is unchanged. Re-measure -before acting rather than trusting these.)* +*(These stand as measured: #121 would have cut ~0.67 GiB of duplicate chromium +from them, but it was **reverted** — decided against, ROADMAP § one chromium, +not two. If a netinstall ships, revisit it: the duplicate is worth ~195 MiB of +**download**, which is this item's whole currency, even though it is worth +almost nothing on disk or on the ISO.)* > **Read this before using the numbers below.** They are **closure arithmetic**, > and #121 proved the hard way that closure size is neither disk size nor image @@ -200,7 +159,7 @@ before acting rather than trusting these.)* **~13.3 GiB** desktop → roughly **6 GiB** compressed at the same ratio. **So "no pin" alone is NOT the lighter ISO** — this is the trap to avoid. - The desktop's own top weights: libreoffice 1457 MiB, initrd 1369, - linux-firmware 770, chromium 704 (was 1391 before #121), llvm-lib 540, + linux-firmware 770, chromium 1391 (two builds — #121, left in), llvm-lib 540, bibata-cursors 322, mesa 264, mbrola-voices 259, nerd-fonts ~420 combined. Note what that list implies: no single lever gets a desktop ISO under ~4 GB — which is the case for (b) below. diff --git a/agent/HARDWARE-QUEUE.md b/agent/HARDWARE-QUEUE.md index a71d44d..cf48ced 100644 --- a/agent/HARDWARE-QUEUE.md +++ b/agent/HARDWARE-QUEUE.md @@ -429,6 +429,18 @@ the **T14s** (webcam case). double-clicking a text file is #119's problem, so don't file it twice. ## AMD dev box only +- [ ] **#118 smartd still runs where drives DO have SMART** (this commit) — the + half a VM cannot answer: QEMU exposes no SMART, so `checks.smartd-gate` + proves the skip but has to drive the *with-device* path through a stub. + On the dev box (real NVMe), after `nomarchy-rebuild` + reboot: + `systemctl status smartd` is **active/running**, and + `systemctl show -p ExecCondition --value smartd` names + `smartd-any-smart-device`. **Pass** = smartd is running, exactly as + before this commit — i.e. the gate skips nothing on real hardware. + **Fail** = inactive/skipped, which would mean the gate is silently + disabling drive-health monitoring: revert it, don't tune it. + Cheap bonus while you are there: `nomarchy-doctor` reports no failed + units (the red-icon symptom that started #118). - [ ] **AMD runtime bits** — VA-API (`vainfo` → radeonsi), amd-pstate EPP active and PPD switching governors; opt-ins: ROCm (`rocminfo`, a GPU PyTorch/Ollama smoke) and the XDNA NPU driver loading. diff --git a/agent/JOURNAL.md b/agent/JOURNAL.md index 8e98e09..a46a255 100644 --- a/agent/JOURNAL.md +++ b/agent/JOURNAL.md @@ -19,6 +19,37 @@ Template: --- +## 2026-07-14 — #118: smartd self-gates; the doctor was right all along (this commit) +- **Task:** BACKLOG #118 — Bernardo's live ISO showed a red Waybar health icon + reporting smartd. +- **Did:** smartd's DEVICESCAN finds nothing where no drive answers SMART and + exits **17**, so systemd marks it failed, the doctor honestly reports a + failed unit, and Waybar paints `@bad`. Everything downstream was correct — + **smartd was the bug, not the doctor**. Added an `ExecCondition` running + `smartctl --scan` (empty exactly when smartd would find nothing), so the + unit goes **inactive** rather than failed. NOT `SuccessExitStatus = 17`, + which would also swallow exit 17 on a machine that has drives — the reason + the daemon exists. +- **Scope was never live-only:** smartd mkDefaults true everywhere and QEMU + virtio has no SMART, so every VM install has been booting to a health + warning, and every V2 run had been showing it as noise. +- **Verified:** **V2** — `checks.smartd-gate` boots the **real** distro module + (not a restatement of it; `nixpkgs.config` needed `mkForce` to yield to the + test's pkgs) and asserts both halves, since they pull opposite ways: a gate + that never skips leaves the red icon, one that always skips silently kills + drive-health monitoring — the failure nobody notices until a disk dies + quietly. No-SMART node → `ActiveState=inactive`, not failed, absent from + `systemctl --failed` (what the doctor actually reads); and the gate's logic + driven against a scan that *finds* a device → exit 0, because QEMU cannot + answer SMART honestly and pretending otherwise would test nothing. **Proved + to fail** by unwiring the condition. flake check + doctor + hardware-toggles + + live-baseline-apps + option-docs + state-bridges green. +- **Pending:** **V3** — that smartd still RUNS on real drives (dev box, real + NVMe). Queued with an explicit fail-condition: if it skips there, revert the + gate rather than tune it. +- **Next suggestion:** #119 (`text/plain` names vscode — the last of #94's bug + class), or #95 (Ghostty does not open) at the top of NOW. + ## 2026-07-14 — #121 reverted: the fix worked, the gain didn't survive measuring (this commit) - **Task:** Bernardo's call on #121 (shipped 0b464af, reverted here): *"Let's leave it honestly. Sounds too much work for a negligible gain."* diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 99dc9dc..7ae1332 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -404,6 +404,30 @@ Design/decision records and a running log of shipped work (items marked decision rather than a drive-by. Proved to fail: dropping snapshot makes it name the missing entry. **V3 pending** — that the apps *launch* needs real hardware (HARDWARE-QUEUE, Acer M5-481T). +- ✓ **smartd self-gates on the hardware (#118):** Bernardo booted the live ISO + and the Waybar health icon was red. Everything downstream was working + correctly, which is what made it worth writing down: smartd's config is + DEVICESCAN, and where no drive answers SMART it exits **17** ("Unable to + monitor any SMART enabled devices"), systemd marks the unit **failed**, + nomarchy-doctor honestly reports a failed system unit, and Waybar paints + `@bad`. **The doctor was right; smartd was the bug.** Scope was never + live-only — `services.smartd.enable` mkDefaults true on every machine and + QEMU virtio exposes no SMART, so *every VM install booted to a health + warning* and every V2 run had been showing it as noise. + Fixed by the distro's own self-gate convention: an `ExecCondition` running + `smartctl --scan` (which prints nothing exactly when smartd would find + nothing) leaves the unit **inactive** instead of failed. **Deliberately not + `SuccessExitStatus = 17`** — that would also swallow exit 17 from a machine + that *does* have drives, which is the entire reason the daemon ships. + **V2:** `checks.smartd-gate` boots the real distro module and asserts both + halves, because they pull in opposite directions — a gate that never skips + leaves the red icon; a gate that always skips silently disables drive-health + monitoring on real hardware, the failure nobody notices until a disk dies + quietly. So: no-SMART node → `ActiveState=inactive`, not failed, absent from + `systemctl --failed` (what the doctor reads); and the gate's logic driven + against a scan that *finds* a device → exit 0, since QEMU cannot answer SMART + honestly. Proved to fail by unwiring the condition. **V3 pending:** on real + drives smartd still runs (HARDWARE-QUEUE, dev box). - ✗ **One chromium, not two (#121) — investigated, measured, DECIDED AGAINST (Bernardo, 2026-07-14): "too much work for a negligible gain".** The bug is real and the fix worked; the *gain* is what failed to survive measurement. diff --git a/flake.nix b/flake.nix index 9f5d51d..cc67120 100644 --- a/flake.nix +++ b/flake.nix @@ -560,10 +560,6 @@ touch $out ''; - # nomarchy-doctor's contract: an induced failed unit flips the - # sheet to ✖/exit-1 and names the unit; with the failure - # cleared it reports healthy/exit-0. Minimal node (just the - # package) — the disk/flake/snapper checks self-skip in a VM. # Live-ISO baseline apps (ROADMAP § live-ISO baseline apps, #103). # The live session shipped no # browser and no office for as long as it existed — the thing a user @@ -654,6 +650,74 @@ touch $out ''; + # smartd self-gate (BACKLOG #118). Where no drive answers SMART, + # smartd exits 17 and systemd marks the unit FAILED — so the doctor + # honestly reported a failed unit and Waybar went red on every QEMU + # guest and most live USBs. Both halves matter and they pull in + # opposite directions, which is why both are asserted here: a + # gate that never skips leaves the red icon, and a gate that always + # skips silently disables drive-health monitoring on real hardware — + # the failure nobody would notice until a disk died quietly. + smartd-gate = pkgs.testers.runNixOSTest { + name = "nomarchy-smartd-gate"; + nodes = { + # QEMU virtio: no SMART. The machine Bernardo saw. Imports the + # REAL distro module, so this guards the shipped wiring rather + # than a restatement of it — runNixOSTest supplies the node's + # pkgs, so the module's own `nixpkgs.config` has to yield. + nosmart = { lib, pkgs, ... }: { + imports = [ ./modules/nixos/default.nix ]; + nixpkgs.config = lib.mkForce { allowUnfree = true; }; + nomarchy.system.greeter.enable = false; + # For the test's own probing only — the gate calls smartctl by + # store path, so this changes nothing about what is measured. + environment.systemPackages = [ pkgs.smartmontools ]; + }; + }; + testScript = '' + nosmart.wait_for_unit("multi-user.target") + + # The gate itself agrees there is nothing to monitor. + cond = nosmart.succeed( + "systemctl show -p ExecCondition --value smartd.service" + ) + assert "smartd-any-smart-device" in cond, f"gate not wired: {cond}" + nosmart.succeed("smartctl --scan | tee /dev/stderr | (! grep -q .)") + + # Inactive, NOT failed — the whole point. Before #118 this was + # 'failed' with Result=exit-code / status=17. + state = nosmart.succeed( + "systemctl show -p ActiveState --value smartd.service" + ).strip() + assert state == "inactive", f"expected inactive, got {state}" + nosmart.fail("systemctl is-failed --quiet smartd.service") + + # And therefore the thing the user actually sees: the doctor's + # failed-unit check is what reddened the Waybar icon. + failed = nosmart.succeed("systemctl --failed --no-legend --plain") + assert "smartd" not in failed, f"smartd still failed: {failed}" + + # The other half: a machine WITH a SMART device must still run + # smartd. QEMU cannot answer SMART, so drive the gate's logic + # against a scan that finds one, rather than pretend otherwise. + nosmart.succeed( + "mkdir -p /tmp/fake && " + "printf '#!/bin/sh\\necho \"/dev/sda -d scsi # /dev/sda, SCSI device\"\\n' " + "> /tmp/fake/smartctl && chmod +x /tmp/fake/smartctl" + ) + gate = cond.split("argv[]=")[1].split(" ")[0].strip("; ]") + nosmart.succeed( + f"sed 's|/nix/store/[^ ]*/bin/smartctl|/tmp/fake/smartctl|' {gate} > /tmp/gate2" + ) + nosmart.succeed("chmod +x /tmp/gate2") + nosmart.succeed("/tmp/gate2") # exit 0 => smartd would start + ''; + }; + + # nomarchy-doctor's contract: an induced failed unit flips the + # sheet to ✖/exit-1 and names the unit; with the failure + # cleared it reports healthy/exit-0. Minimal node (just the + # package) — the disk/flake/snapper checks self-skip in a VM. doctor = pkgs.testers.runNixOSTest { name = "nomarchy-doctor"; nodes.machine = { ... }: { diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index 2790d14..b61c652 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -168,6 +168,27 @@ in notifications.x11.enable = lib.mkDefault true; notifications.wall.enable = lib.mkDefault true; }; + + # Self-gate on the hardware, like every other conditional bit of the + # distro. smartd's config is DEVICESCAN, and where no drive answers SMART + # it exits **17** ("Unable to monitor any SMART enabled devices") — which + # systemd records as a FAILED unit, nomarchy-doctor faithfully reports as + # a failed system unit, and Waybar renders as a red health icon. That is + # every QEMU guest (virtio exposes no SMART), most live USB sticks, and + # some eMMC — so a plain VM install greeted the user with a health warning + # about a daemon that had nothing to do (Bernardo, live ISO 2026-07-14). + # + # ExecCondition is the right lever, not SuccessExitStatus = 17: a failed + # *condition* leaves the unit **inactive** and unfailed, while exit 17 + # from a machine that DOES have drives still fails loudly — which is the + # entire reason the daemon is here. `smartctl --scan` prints nothing + # exactly when smartd would find nothing, so it is the same question + # asked before the daemon can fail it. + systemd.services.smartd.serviceConfig.ExecCondition = + lib.mkIf config.services.smartd.enable + (lib.mkDefault "${pkgs.writeShellScript "smartd-any-smart-device" '' + [ -n "$(${pkgs.smartmontools}/bin/smartctl --scan)" ] + ''}"); # Core security: enable AppArmor to confine desktop apps and services. security.apparmor.enable = true; security.apparmor.killUnconfinedConfinables = false;