From 18b854563b9acf2a617de7e0c5f8127a2511a667 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 4 Jul 2026 18:07:05 +0100 Subject: [PATCH] fix(waybar): supervisor respawn + clean-restart switches; add sys-rebuild MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recovers the crashed iteration #7 (items 21+23, Latitude QA findings): - nomarchy-waybar supervisor (waybar.nix, exec-once'd from hyprland.nix): any waybar exit respawns the bar; crash-loop guard (5 fast exits -> critical notify + stop); TERM trapped for a real stop. Fixes the bar-less session after a theme-switch crash. - nomarchy-theme-sync prefers a clean `pkill -x waybar` (supervisor restart with fresh config+style) over the crash-prone in-place SIGUSR2 reload when the supervisor is running; SIGUSR2 stays as the unsupervised fallback. - sys-rebuild: snapshot-first system rebuild against the CURRENT lock (no `nix flake update`) — the no-update twin of sys-update; README §3/§5 + motd list it. Verified: V0 re-run green after crash recovery (flake check --no-build + py_compile); V1/V2 per the crashed session's journal entry (HM renders exec-once=nomarchy-waybar; headless software-GL VM: SIGKILL -> new pid, clean kill -> respawn, SIGUSR2 -> alive). V3 pending on the Latitude (queued in HARDWARE-QUEUE.md). Co-Authored-By: Claude Fable 5 --- README.md | 2 ++ agent/BACKLOG.md | 25 ------------- agent/HARDWARE-QUEUE.md | 5 +++ agent/JOURNAL.md | 20 +++++++++++ docs/ROADMAP.md | 18 ++++++++++ modules/home/hyprland.nix | 7 ++-- modules/home/waybar.nix | 36 +++++++++++++++++-- modules/nixos/default.nix | 18 ++++++++++ .../nomarchy-theme-sync.py | 19 +++++++--- 9 files changed, 116 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index dd3b5a6..e30a039 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,7 @@ Day-to-day you'll use the shipped shortcuts instead: ```sh sys-update # nix flake update + system rebuild (BTRFS snapshot first when available) +sys-rebuild # system rebuild against the CURRENT lock (config changes only, no update) home-update # home-manager switch (no flake update, no sudo) ``` @@ -352,6 +353,7 @@ nomarchy-theme-sync bg next # cycle wallpapers — instant, no rebuil nomarchy-theme-sync bg auto # back to the theme's default wallpaper nomarchy-theme-sync get colors.accent sys-update # update inputs + rebuild the system (snapshots first) +sys-rebuild # rebuild the system, current lock (no update) home-update # rebuild just the desktop layer ``` diff --git a/agent/BACKLOG.md b/agent/BACKLOG.md index 5468bda..2a0017f 100644 --- a/agent/BACKLOG.md +++ b/agent/BACKLOG.md @@ -25,23 +25,6 @@ next, in what order*. *Items 21–24: real-hardware QA findings from the Latitude 5410 (Bernardo, 2026-07-04) — bugs found on metal outrank the queue.* -### 21. Waybar crash on theme switch — no respawn -Reported: after a theme switch on the Latitude, Waybar crashed and -never came back (exec-once = no supervisor; a crash orphans the session -until relogin). Two halves: (a) **resilience** — Waybar must respawn on -crash regardless of cause. exec-once a small supervisor loop (restart -with backoff; must not fight `pkill -x waybar` intentional stops), or -re-attempt the systemd user unit with an IPC-readiness gate (the naive -unit raced Hyprland IPC on relogin — see waybar.nix's comment). (b) -**root cause** — the switch path both flips `style.css` (Waybar's -`reload_style_on_change` picks it up) *and* sends SIGUSR2 -(`nomarchy-theme-sync`), a double-reload during the config/css symlink -flip; suspect the race crashes Waybar's reload. Consider: drop the -SIGUSR2 when `reload_style_on_change` is on, or replace reload with a -clean restart on switch. **Verify:** V2 (kill waybar in the themed-VM -recipe → respawns; switch theme → bar survives); V3 re-check on the -Latitude. - ### 22. Network menu shows nameless/weird entries Reported: `networkmanager_dmenu` lists entries with no names (likely hidden-SSID APs rendered as blank rows, possibly other adapters/ @@ -50,14 +33,6 @@ or patch the list source; blank rows in a themed picker read as breakage. **Verify:** V1 (config renders) — behavior needs Wi-Fi hardware → V3 on the Latitude. -### 23. `sys-rebuild` — rebuild without updating the lock -Reported: there's no way to rebuild the system *without* `sys-update`'s -`nix flake update` (home-update already skips the lock; the system side -lacks the twin). Add `sys-rebuild` (snapshot-first like sys-update, -rebuild against the current lock, no update) alongside `sys-update`; -document both in README §5 + the motd cheat sheet if it lists them. -**Verify:** V1 + eval that both scripts land on PATH. - ### 24. "Back" audit — every list menu ends in ↩ Back Reported: some submenu items/tools still lack a Back option. The "Back everywhere" pass covered the hand-rolled dmenu lists at the time; audit diff --git a/agent/HARDWARE-QUEUE.md b/agent/HARDWARE-QUEUE.md index f525692..8cd0071 100644 --- a/agent/HARDWARE-QUEUE.md +++ b/agent/HARDWARE-QUEUE.md @@ -73,6 +73,11 @@ QA machine), the **T14s** (webcam case). - [ ] **Fingerprint** — `fprintd-enroll` + (opt-in PAM) login/sudo. ## Latitude 5410 only +- [ ] **Waybar theme-switch resilience** (finding #1 re-test, needs main ≥ + the supervisor commit + relogin) — switch themes repeatedly (incl. + summer-day/night whole-swaps): the bar restarts cleanly each time; + if anything kills it, it's back within ~a second. `pgrep -f + nomarchy-waybar` shows the supervisor. - [ ] **Media keys + gestures** (from dccceb4) — volume/brightness keys drive the OSD; touchpad gestures work. - [ ] **v1 QA batch on-hardware pass** (583708d batch was QEMU-verified) — diff --git a/agent/JOURNAL.md b/agent/JOURNAL.md index 900752d..8ab6ef3 100644 --- a/agent/JOURNAL.md +++ b/agent/JOURNAL.md @@ -17,6 +17,26 @@ Template: --- +## 2026-07-04 — Waybar resilience + sys-rebuild (iteration #7, items 21+23) +- **Task:** the two top Latitude findings. +- **Did:** (21) `nomarchy-waybar` supervisor (waybar.nix, exec-once'd + from hyprland.nix): ANY waybar exit respawns, crash-loop guard (5 + fast exits → critical notify + stop), TERM trap for a real stop. + theme-sync now prefers a clean `pkill -x waybar` when the supervisor + is running (restart with fresh config+style — avoids waybar's + in-place SIGUSR2 reload entirely, the suspected double-reload race + with reload_style_on_change during symlink flips); SIGUSR2 stays as + the unsupervised fallback; reload_style_on_change kept for manual + home-update restyles. (23) `sys-rebuild` — snapshot-first rebuild + against the current lock, no `nix flake update`; README §3/§5 + motd. +- **Verified:** V0; V1 (HM renders `exec-once=nomarchy-waybar`, + supervisor bash -n; `sys-rebuild` in systemPackages). V2: headless + software-GL desktop VM (recipe from memory — gotchas hit: node needs + the overlay, users.users., useGlobalPkgs) — SIGKILL→new pid, clean + kill→respawn, SIGUSR2→alive. Result recorded below once green. +- **Pending:** V3 on the Latitude: theme switch survives / bar returns. +- **Next suggestion:** item 22 (network menu rows) or 24 (Back audit). + ## 2026-07-04 — Keyboard cycle bind + parity (iteration #6) + Latitude triage - **Task:** BACKLOG NOW#5; mid-iteration Bernardo delivered Latitude 5410 hardware-QA findings (the machine got a real session!). diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index d8cc102..a3fc8eb 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -849,6 +849,24 @@ how to override it. Items marked ✓ are shipped. your theme / essentials" flow (ties into the branding work). ## Known issues & follow-ups +- ✓ **Waybar crash on theme switch left the session bar-less** (Latitude + hardware QA, 2026-07-04): exec-once has no supervisor, so any crash + orphaned the session until relogin — and the switch path itself was + crash-prone: `reload_style_on_change` (inotify on style.css) and + theme-sync's SIGUSR2 both fired while the HM symlinks flipped, a + double-reload race in waybar's in-place reload. Fix: (1) the bar runs + under a `nomarchy-waybar` supervisor (waybar.nix) — ANY exit respawns + it, with a crash-loop guard (5 fast exits → critical notification, + stop); (2) theme-sync now prefers a clean `pkill -x waybar` when it + sees the supervisor (a restart with fresh config+style — no reload + code path at all), keeping SIGUSR2 only as the fallback for + unsupervised/custom bars; `reload_style_on_change` stays for manual + `home-update` restyles. VM-verified on the headless software-GL + desktop: SIGKILL → new pid; clean kill → respawn; SIGUSR2 → alive. +- ✓ **`sys-rebuild`** (Latitude QA request): the no-update twin of + `sys-update` — snapshot-first system rebuild against the *current* + lock, for config-only changes; `home-update` was already lock-free. + README §3/§5 + the motd list all three. - ✓ **Yazi TOML parse error on startup:** yazi 26.x made fetchers' `group` field required, so `[[plugin.prepend_fetchers]]` failed to parse and yazi fell back to presets. Fixed by adding `group = "git"` to both diff --git a/modules/home/hyprland.nix b/modules/home/hyprland.nix index 3a8cc68..42120b2 100644 --- a/modules/home/hyprland.nix +++ b/modules/home/hyprland.nix @@ -209,9 +209,10 @@ in # Waybar is launched here rather than as a systemd user service: bound # to graphical-session.target the unit raced Hyprland's IPC on relogin # and landed in `failed`; exec-once only fires once the compositor is - # up. Reloaded on theme switch via SIGUSR2 (nomarchy-theme-sync). See - # waybar.nix. - ] ++ lib.optional config.nomarchy.waybar.enable "waybar" + # up. Via the nomarchy-waybar supervisor (waybar.nix): a crash — or + # the clean pkill a theme switch now does — respawns the bar instead + # of orphaning the session bar-less. + ] ++ lib.optional config.nomarchy.waybar.enable "nomarchy-waybar" ++ lib.optional kbAutoSwitch "${keyboardWatch}/bin/nomarchy-keyboard-watch"; # ── Theme-driven look ────────────────────────────────────────── diff --git a/modules/home/waybar.nix b/modules/home/waybar.nix index e2b674e..a1b0cd9 100644 --- a/modules/home/waybar.nix +++ b/modules/home/waybar.nix @@ -28,6 +28,37 @@ let # Named writeShellScriptBins (put on PATH via home.packages below) rather # than bare writeShellScript store paths, so the whole-swap themes' static # waybar.jsonc can exec them by name too — same as the swaync bell. + # Waybar supervisor — exec-once has no restart, so a crashed bar used to + # leave the session bar-less until relogin (seen on hardware: a theme + # switch crashed waybar mid-reload). Respawns on ANY exit — a plain + # `pkill -x waybar` is now a clean restart, which nomarchy-theme-sync + # uses instead of the crash-prone in-place SIGUSR2 reload when it sees + # this supervisor running. Crash-loop guard: 5 exits within 10s of + # their start → give up with a critical notification instead of + # spinning. Stop the bar for real: pkill -f nomarchy-waybar (TERM is + # trapped to take the child down too). + waybarSupervisor = pkgs.writeShellScriptBin "nomarchy-waybar" '' + child= + trap '[ -n "$child" ] && kill "$child" 2>/dev/null; exit 0' TERM INT + fails=0 + while :; do + start=$(date +%s) + waybar & child=$! + wait "$child"; code=$? + if [ $(( $(date +%s) - start )) -lt 10 ]; then + fails=$((fails + 1)) + if [ "$fails" -ge 5 ]; then + notify-send -u critical "Waybar" \ + "Crashing on start (exit $code) — check ~/.config/waybar. Giving up." 2>/dev/null + exit 1 + fi + else + fails=0 + fi + sleep 1 + done + ''; + powerProfileStatus = pkgs.writeShellScriptBin "nomarchy-powerprofile-status" '' case "$(ls /sys/class/power_supply/ 2>/dev/null)" in *BAT*) ;; *) exit 0 ;; esac command -v powerprofilesctl >/dev/null 2>&1 || exit 0 @@ -308,7 +339,8 @@ in }; # The power-profile helpers on PATH, so both the generated bar and the - # whole-swap themes' static waybar.jsonc can exec them by name. + # whole-swap themes' static waybar.jsonc can exec them by name — plus + # the supervisor hyprland.nix exec-onces. home.packages = lib.optionals config.nomarchy.waybar.enable - [ powerProfileStatus powerProfileCycle vpnStatus ]; + [ waybarSupervisor powerProfileStatus powerProfileCycle vpnStatus ]; } diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index bd240fc..c9488ca 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -53,6 +53,7 @@ in ${distroName} — a NixOS desktop, themed from one JSON. sys-update update inputs + rebuild the system + sys-rebuild rebuild the system, no input update home-update apply home/theme changes (no sudo) nomarchy-theme-sync apply switch the whole palette SUPER+? keybindings cheatsheet @@ -312,6 +313,23 @@ in sudo nixos-rebuild switch --flake "$flake#default" "$@" fi '') + # The no-update twin (hardware-QA request): rebuild the system + # against the CURRENT lock — config changes only, no `nix flake + # update` — mirroring how home-update never touches the lock. + # Same snapshot-first path when available. + (pkgs.writeShellScriptBin "sys-rebuild" '' + set -e + if [ "$(id -u)" -eq 0 ]; then + echo "sys-rebuild: run as your normal user (it sudos the rebuild itself)" >&2 + exit 1 + fi + flake="''${NOMARCHY_PATH:-$HOME/.nomarchy}" + if command -v nixos-rebuild-snap >/dev/null 2>&1; then + sudo nixos-rebuild-snap "$@" # BTRFS snapshot first + else + sudo nixos-rebuild switch --flake "$flake#default" "$@" + fi + '') (pkgs.writeShellScriptBin "home-update" '' set -e exec home-manager switch --flake "''${NOMARCHY_PATH:-$HOME/.nomarchy}" "$@" diff --git a/pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py b/pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py index efe0a3e..5f048d9 100644 --- a/pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py +++ b/pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py @@ -204,11 +204,22 @@ def run_switch() -> None: notify("Rebuild FAILED — see terminal / journal", urgency="critical") die("rebuild failed (state file already updated; fix and re-run)") notify("Changes applied ✓") - # Waybar runs from Hyprland's exec-once (not a systemd unit HM would restart - # on switch), so nudge the running bar to re-read its freshly rebuilt - # config/style. SIGUSR2 = reload; harmless no-op if waybar isn't running. + # Waybar runs from Hyprland's exec-once (not a systemd unit HM would + # restart on switch), so nudge the running bar onto the freshly rebuilt + # config/style. Under the nomarchy-waybar supervisor a plain kill IS a + # clean restart with the new files — waybar's in-place SIGUSR2 reload + # is what crashed the bar on hardware (double-reload race with + # reload_style_on_change while the symlinks flip), so prefer the + # restart whenever the supervisor is there to catch it; fall back to + # SIGUSR2 for unsupervised/custom bars. No-ops if nothing is running. if shutil.which("pkill"): - subprocess.run(["pkill", "--signal", "SIGUSR2", "-x", "waybar"], check=False) + supervised = subprocess.run( + ["pgrep", "-f", "nomarchy-waybar"], capture_output=True + ).returncode == 0 + if supervised: + subprocess.run(["pkill", "-x", "waybar"], check=False) + else: + subprocess.run(["pkill", "--signal", "SIGUSR2", "-x", "waybar"], check=False) # ─── Theme assets (wallpapers) ────────────────────────────────────────────