fix(waybar): supervisor respawn + clean-restart switches; add sys-rebuild

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 <noreply@anthropic.com>
This commit is contained in:
Bernardo Magri
2026-07-04 18:07:05 +01:00
parent f70838c5b5
commit 18b854563b
9 changed files with 116 additions and 34 deletions

View File

@@ -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
```

View File

@@ -25,23 +25,6 @@ next, in what order*.
*Items 2124: 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

View File

@@ -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) —

View File

@@ -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.<u>, 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!).

View File

@@ -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

View File

@@ -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 ──────────────────────────────────────────

View File

@@ -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 ];
}

View File

@@ -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 <theme> 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}" "$@"

View File

@@ -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) ────────────────────────────────────────────