diff --git a/.gitea/workflows/bump.yml b/.gitea/workflows/bump.yml index 5448626..7927375 100644 --- a/.gitea/workflows/bump.yml +++ b/.gitea/workflows/bump.yml @@ -36,6 +36,8 @@ jobs: timeout-minutes: 90 env: NIX_CONFIG: | + max-jobs = 1 + cores = 2 experimental-features = nix-command flakes sandbox = false NIX_SSL_CERT_FILE: /etc/ssl/certs/ca-certificates.crt @@ -68,9 +70,12 @@ jobs: fi id: update - - name: Gate — nix flake check (eval only) + - name: Gate — evaluate every output (eval tier, memory-bounded) + # Per-output processes, not one big `nix flake check --no-build`: + # the single-process walk peaks ~6 GB RSS and OOMs inside the + # runner's 2 GB container cap (details in tools/ci-eval.sh). if: steps.update.outputs.changed == '1' - run: nix flake check --no-build + run: bash tools/ci-eval.sh - name: Gate — V1 build (toplevel + home-manager) if: steps.update.outputs.changed == '1' diff --git a/.gitea/workflows/check.yml b/.gitea/workflows/check.yml index 17a39cb..1e4d8bc 100644 --- a/.gitea/workflows/check.yml +++ b/.gitea/workflows/check.yml @@ -57,11 +57,15 @@ jobs: curl -L "https://releases.nixos.org/nix/nix-${NIX_VERSION}/install" | sh -s -- --no-daemon echo "$HOME/.nix-profile/bin" >> "$GITHUB_PATH" - - name: nix flake check (eval only) - # Full module-system evaluation of every output — both nixos + - name: Evaluate every output (eval tier, memory-bounded) + # Same coverage as `nix flake check --no-build` — both nixos # configs, the home config, the checks.* derivations (instantiated, - # not run) and the downstream template through lib.mkFlake. - run: nix flake check --no-build + # not run) and the downstream template through lib.mkFlake — but + # ONE output per nix process. A single process walking everything + # peaks at ~6.0 GB RSS (measured 2026-07-12): it OOM'd the 4 GB + # VPS itself, and can never fit this runner's 2 GB container cap. + # Per-output processes cap at the largest single output (~1 GB). + run: bash tools/ci-eval.sh - name: Python syntax (all tracked scripts) # Every tracked *.py — theme-sync, the installer composers diff --git a/README.md b/README.md index 3507ee2..5ee176b 100644 --- a/README.md +++ b/README.md @@ -194,7 +194,10 @@ nomarchy-what-changed # plain-language last rebuild (system + desktop; --summar `~/.nomarchy` is **your machine flake** (`system.nix` / `home.nix`). Newer Nomarchy code arrives when `flake.lock` updates the `nomarchy` input (`nomarchy-pull`), not by `git pull` of that directory (many installs have -no remote there — only local commits from auto-commit). +no remote there — only local commits from auto-commit). With auto-commit +on (menu: **System › Auto-commit**), all three commands above also sweep +any pending hand edits into a commit before they switch, so +`git -C ~/.nomarchy log` mirrors your generations. | When | Run | |---|---| @@ -242,6 +245,7 @@ two tables below are split along exactly that line. | `nomarchy.rofi.enable` | `true` | Themed rofi launcher + `nomarchy-menu` dispatcher | | `nomarchy.swaync.enable` | `true` | swaync notifications, themed | | `nomarchy.batteryNotify.enable` | `true` | Low-battery toasts at the bar's thresholds — 25% low, 10% critical (stays up until dismissed); silent no-op on machines without a battery | +| `nomarchy.dockAudio.enable` | `true` | Default audio output follows dock/monitor sinks (HDMI/DP/USB) on hotplug, with a toast; EasyEffects follows along; unplug falls back to built-in | | `nomarchy.firstBootWelcome.enable` | `true` | One dismissible “you're set” toast on the first session (SUPER+M / SUPER+T / SUPER+? + network pointer); marker is `settings.firstBootShown` in the flake checkout | | `nomarchy.idle.enable` | `true` | hyprlock + hypridle (idle lock 5 min, display off 10, suspend 15 min — battery-only) | | `nomarchy.yazi.enable` | `true` | yazi TUI file manager, themed + curated plugins | @@ -293,6 +297,7 @@ option, e.g. `services.easyeffects.enable = lib.mkForce false;` — see | `nomarchy.hardware.amd.rocm.enable` | `false` | Opt-in: ROCm HIP/OpenCL GPU compute (multi-GB); pair with `.gfxOverride` (e.g. `"11.0.0"`) for an unlisted iGPU | | `nomarchy.hardware.fingerprint.enable` | `false` | fprintd for a detected fingerprint reader (installer-set); enroll with `fprintd-enroll` | | `nomarchy.hardware.fingerprint.pam` | `false` | Opt-in: use the fingerprint for login + sudo (PAM) | +| `nomarchy.hardware.fingerprint.parallel` | `true` | With PAM on: password *or* fingerprint at the same prompt (whichever comes first); `false` = stock sequential pam_fprintd. Password alone always stays sufficient | | `nomarchy.hardware.npu.enable` | `false` | Opt-in/experimental: load the on-die NPU driver (`amdxdna`/`intel_vpu`); userspace runtime is BYO | | `nomarchy.hardware.latestKernel` | `false` | Opt-in: ship `linuxPackages_latest` instead of the default kernel — for very new hardware whose drivers landed recently | | `nomarchy.hardware.camera.hideIrSensor` | `false` | Hide a dual-sensor webcam's IR node from PipeWire's **v4l2** path so apps only ever see the colour camera (the "second, dark Integrated Camera"); installer-set on a paired RGB+IR webcam. `/dev/video*` stays open, so Howdy-style face unlock still works; `.irMatch` overrides the IR-name regex. Does **not** hide IR from libcamera / portal / Flatpak pickers — see [HARDWARE.md §7](docs/HARDWARE.md) | diff --git a/agent/BACKLOG.md b/agent/BACKLOG.md index f66fe46..80e4462 100644 --- a/agent/BACKLOG.md +++ b/agent/BACKLOG.md @@ -93,18 +93,11 @@ high-ROI, etc.) live in the journal + ROADMAP — not here.* fixture. Control-center / MOTD already mention these; the gap is the silent first *graphical* session for people who never open those. -- **Fingerprint *or* password, in parallel** (2026-07-11, Bernardo) — - `fingerprint.pam` gives pam_fprintd's sequential UX: sudo waits on - the reader, password only after failure/timeout. Bernardo wants - either factor accepted at the same prompt (type the password *or* - touch the sensor, whichever comes first). Stock PAM can't express - this; needs a parallel-conversation module (e.g. `pam-any`, not in - nixpkgs — would be a new package + `fingerprint.pam = "any"` mode). - Cost: medium (package + PAM wiring + careful lockout testing on real - hardware). Security note: keep sudo fallback sane if fprintd hangs. - _(#80–#83 + #85–#88 shipped 2026-07-11. Theme A day-2 + neon-glass finish - shipped — VISION ✓. Dock/hibernate V3 → HARDWARE-QUEUE.)_ + shipped — VISION ✓. Dock/hibernate V3 → HARDWARE-QUEUE. Parallel + fingerprint-or-password shipped 2026-07-12 (Bernardo promoted it live; + `fingerprint.parallel`, pam-fprint-grosshack) — reader V3 → + HARDWARE-QUEUE.)_ ### v1.0 pointer diff --git a/agent/HARDWARE-QUEUE.md b/agent/HARDWARE-QUEUE.md index e203648..ffd59bf 100644 --- a/agent/HARDWARE-QUEUE.md +++ b/agent/HARDWARE-QUEUE.md @@ -19,13 +19,59 @@ the **T14s** (webcam case). — should suspend as usual. Optional: with a docked display-profile (`eDP-1` disable), confirm clamshell layout still looks right after lid close. Pass = no surprise suspend when docked. -- [ ] **#87 dock audio sink follow** — V2 asserts WirePlumber - `90-nomarchy-dock-audio` rules (HDMI/USB priority). On a laptop - with HDMI or USB-dock audio: play sound on speakers, plug external - audio → default sink should move to HDMI/dock (`wpctl status` shows - `*` on the external sink); unplug → `*` returns to built-in. - Manual `wpctl set-default` still works; Bluetooth may outrank dock - (by design). Pass = auto switch without menu intervention. +- [ ] **#87 round 2: dock audio auto-follow (watcher)** — round 1 result + (Newton, 2026-07-12): audio stayed on speakers when docking; the + running generation had no `90-nomarchy-dock-audio` rules yet, and + live testing showed the priority rules alone can never win anyway — + WirePlumber's stored default (`default-nodes` state, written by any + past explicit pick) outranks `priority.session`, and this machine + (like most) has one. Fix shipped: the `nomarchy-dock-audio` user + service `pactl set-default-sink`s dock-class sinks on hotplug; + EasyEffects follows the default (verified live: EE's output links + moved to a fake dock-named null sink mid-playback). Retest after + pull+rebuild+home on ≥ this rev: play audio, plug the dock → + toast "Audio → " and sound moves with EE effects + still active; unplug → sound returns to speakers; while docked, + picking speakers in the Audio menu sticks until the next replug. + If it does NOT switch, capture for diagnosis: `pactl subscribe` + output during the plug, `pactl --format=json list sinks` while + docked (sink names may not match the dock regexes, or the sink + node may pre-exist and only flip route availability — the watcher + keys on new-sink events). +- [ ] **Docked display/audio menu rows** (2026-07-12) — with a real + external monitor: Display menu shows "Laptop screen off · + everything → " (only with >1 active output), "Screen on · + " after a disable, "Move workspace · → next monitor", "Swap + workspaces" (exactly 2 outputs). Laptop-off must move its + workspaces to the external; Screen on restores it (preferred/auto — + declared scale/pos return at the next relogin/switch). Audio menu + shows "Send output → " when one exists and isn't + default; picking it moves output + toasts. Pass = rows appear/act + as described, and none of them show on a single-display session. + Also — **run this one with nothing important open**: with the + laptop screen off via the menu, physically unplug the dock. The + panel should re-enable within ~2s (`rescue_blackout` in the + display watcher). Known risk from the VM harness + (tools/monitor-fallback.nix): on Hyprland 0.55.4 the zero-output + state itself can crash the compositor faster than any rescue + (softGL VM: 4/5 runs; control with the panel active survives + always) — a crash on real DRM ends the session to the greeter on + the re-lit panel. If that happens on hardware, the "Laptop screen + off" row needs a rework (mirror instead of disable, or drop until + a Hyprland bump) — file it as a NOW bug with the coredump. +- [ ] **Parallel fingerprint-or-password on the real reader** (AMD dev + box, 2026-07-12) — with a finger enrolled and + `fingerprint.pam = true` (parallel is the default): `sudo -k true` + must show ONE prompt ("Enter Password or Place finger…"); typing + the password immediately works, touching the sensor instead works, + wrong-finger ×3 falls back to password, and password keeps working + with fprintd stopped (`systemctl stop fprintd`). Also check + hyprlock and the greeter accept both factors and don't wedge on a + leftover prompt after a fingerprint win (known cosmetic quirk of + the hack — pthread_cancel'd prompt). `fingerprint.parallel = false` + must restore the old sequential behavior. VM already asserts the + PAM stack shape + password-only lockout safety with no reader + (checks.hardware-toggles). - [ ] **#55 fingerprint enroll on real reader** — with `nomarchy.hardware.fingerprint.enable` and a physical reader: System › Fingerprint › Enroll a finger; List shows it; Verify succeeds; optional diff --git a/agent/JOURNAL.md b/agent/JOURNAL.md index fa5e0f4..f1d1531 100644 --- a/agent/JOURNAL.md +++ b/agent/JOURNAL.md @@ -26,6 +26,117 @@ Template: - **Pending:** #89 is fully closed. - **Next suggestion:** Pick the next item from the BACKLOG. +## 2026-07-12 — CI OOM: memory-bounded eval for check.yml/bump.yml +- **Task:** Bernardo: check.yml always red since he capped the runner + container at 2 GB (uncapped it OOM'd the whole 4 GB VPS); bump.yml + status unknown. +- **Did:** Measured (GNU time, eval-cache off): one-process `nix flake + check --no-build` peaks **6.0 GB** RSS — that's the whole story. New + `tools/ci-eval.sh`: same coverage, ONE output per nix process (heap + freed between outputs); both workflows now call it. bump.yml also got + max-jobs=1/cores=2. Facts: bump.yml WORKS — landed 8fded63 on + 2026-07-06, on schedule; ROADMAP's "no CI today" was stale → fixed. +- **Verified:** V1+ — ci-eval.sh ran green locally end-to-end (3:22); + per-output cold peaks measured: worst check 0.99 GB + (hardware-toggles), nomarchy toplevel 0.78 GB, HM 0.60 GB, **except + nomarchy-live 2.69 GB** (live ISO eval) — fits only with the + container's swap allowance (docker default --memory-swap = 2×memory); + if Bernardo set swap off, that one output still OOMs → server-side + one-liner (--memory=2g --memory-swap=6g) or a skip-hatch decision. +- **Pending:** real proof is the next push's Actions run on the VPS — + watch nomarchy-live's step; can't be verified from this machine. +- **Next suggestion:** confirm green run on the remote, then hardware + V3 batch. + +## 2026-07-12 — parallel fingerprint-or-password (interactive, promoted) +- **Task:** Bernardo promoted the PROPOSED "fingerprint or password in + parallel" item live; wants it default-on for fingerprint-PAM users. +- **Did:** Packaged pam-fprint-grosshack v0.3.0 (pkgs/, source-reviewed + first: all failure paths → PAM_AUTHINFO_UNAVAIL, typed password only + ferried via AUTHTOK to pam_unix try_first_pass — can't lock out + password login). New `nomarchy.hardware.fingerprint.parallel` + (default true; false = stock sequential). Wiring swaps stock fprintd's + rule modulePath (mkForce) so ordering is inherited. README/template + rows; BACKLOG PROPOSED entry closed into ROADMAP ✓. +- **Verified:** V2 — checks.hardware-toggles extended to three nodes and + green: parallel node asserts grosshack line before pam_unix in + /etc/pam.d/sudo AND `sudo -S` with the right password succeeds / + wrong password fails with NO reader present (the lockout invariant); + seqpam node = stock pam_fprintd, no grosshack; nopam = neither. + Package builds (meson needed libpam-wrapper + python3). flake check + green. +- **Pending:** V3 on the AMD dev box (HARDWARE-QUEUE): the actual + type-or-touch race, fprintd-stopped fallback, hyprlock/greeter + behavior after a fingerprint win (pthread_cancel'd prompt quirk). +- **Next suggestion:** dock + fingerprint V3 batch on hardware, then + #89 slice 3. + +## 2026-07-12 — undock blackout rescue (interactive follow-up) +- **Task:** Bernardo: "if I turn the laptop screen off and undock, does + the panel come back?" Answer had to be tested, not recalled. +- **Did:** Built tools/monitor-fallback.nix (softGL desktop VM, crib of + theme-shot): headless "external" + menu's exact disable + output + remove. Verdict: Hyprland 0.55.4 does NOT re-enable → shipped + `rescue_blackout` in the display hotplug watcher (immediate + retried + keyword re-enable of disabled outputs on zero-active, toast). +- **Verified:** V2 for the hazard (VM reproduced the dead state); + rescue itself is V1+components — the VM crashes before rescue can act + in 4/5 runs (aquamarine ABRT in dispatchIdle on the zero-output + state; control with panel active survives 5/5 — discriminator run). + Crash predates rescue (run with rescue provably inert also crashed). + Also fixed: `grep -c .` exit-1-at-zero disabling the rescue's own + trigger. flake check green. +- **Pending:** real-dock V3 (HARDWARE-QUEUE, "nothing important open" + warning); if real DRM crashes too → rework row (mirror) or wait for + Hyprland bump; consider reporting upstream with the coredump. +- **Next suggestion:** dock V3 results, then #89 slice 3. + +## 2026-07-12 — dock audio auto-follow + docked menu rows (interactive) +- **Task:** Bernardo's dock test (= #87's V3): audio stayed on speakers, + had to hand-tweak EasyEffects; wants docked Display/Audio menu options. +- **Did:** Root-caused live on Newton: WirePlumber's stored default + (default-nodes) outranks the #87 priority rules (control test: new + sink, no watcher → default pinned), and the running gen lacked the + rules anyway; EE 8 follows the default fine (kcfg default true + + observed links move). Shipped `modules/home/dock-audio.nix` + (`nomarchy.dockAudio.enable`, on): pactl-subscribe watcher + set-default-sinks new dock-class sinks (regexes imported from + dock-audio-rules.nix) + toast + startup sweep. Menu: Display gains + laptop-off/screen-on/move-workspace/swap rows (live-only keywords); + Audio gains self-gated "Send output → " row. +- **Verified:** V2-equivalent live on Newton — watcher: fake dock-named + null sink → default followed, unplug → fell back, EE links moved + mid-playback (pw-link); menu: generated script bash -n, dock-row jq + exercised against real pactl JSON, dispatchers probed on live + Hyprland. flake check green; option-docs green. Beware: kill the + subscribe pipe by PID — pkill -f self-matches the test shell. +- **Pending:** real-dock V3 ×2 in HARDWARE-QUEUE (watcher may miss + sinks that pre-exist and only flip route availability — capture pactl + subscribe + sink JSON if it fails again). +- **Next suggestion:** #89 slice 3 (Signal pixmap) or dock V3 feedback. + +## 2026-07-12 — auto-commit sweep before pull/rebuild/home (interactive) +- **Task:** Bernardo's request (no BACKLOG item): with autoCommit on, + hand edits to `~/.nomarchy` (his had 4) stayed forever-dirty — + theme-sync's commit is pathspec-limited to theme-state.json by design. +- **Did:** New internal `nomarchy-autocommit` in nomarchy-lifecycle: + live-reads `settings.autoCommit`, commits *everything* dirty with + message `nomarchy: auto-commit before