diff --git a/agent/BACKLOG.md b/agent/BACKLOG.md index 394e9c6..cc2f463 100644 --- a/agent/BACKLOG.md +++ b/agent/BACKLOG.md @@ -214,20 +214,48 @@ the existing `refresh_callback` (already running at 50 fps) whenever included), entry, lock, any live bullets, progress box/bar. Keep it a canvas-change check, not an every-frame recompute. -**Testing — the rig exists, and it needs root.** nixpkgs' plymouth ships -`x11.so`, and plymouth has a built-in fake dual-head mode: -`PLY_CREATE_FAKE_MULTI_HEAD_SETUP=1` (`renderers/x11/plugin.c:294`) creates -800×600 + 640×480 heads, so the splash can be rendered in a window with no -DRM and no reboot. Caveat: that fake path builds both heads up front, so it -proves *rendering and centring*, not the canvas-change bug itself — for that, -plug/unplug while the splash is up, or compare a boot with and without the -external. `plymouthd` wants root and its theme comes from -`/etc/plymouth/plymouthd.conf`, so this needs a sudo session. +**The rig works, needs no root, and is in `scratchpad/ply-rig.sh`** (recipe +below — rebuild it, it is ~20 lines). Rendered on the dev box 2026-07-16: -**Do not ship this blind:** the same script draws the **LUKS passphrase box**, -this machine is encrypted, and a script that fails to parse leaves a prompt you -cannot see. Pass = with the rig, a two-head render centred on both; then a real -docked boot + shutdown, undocked unchanged. +- `unshare -rm` gives a user + mount namespace: root *in there* only, every + mount invisible to the real system and gone when it exits. +- `/run/plymouth` is BOTH the daemon's socket dir **and** its compiled-in + plugin/theme path (`strings plymouthd` → `/run/plymouth/plugins/`). Bind a + writable dir over it **and recreate its three symlinks** (`plugins`, + `themes`, `plymouthd.defaults` → `/etc/plymouth/…`) or plymouth cannot find + `x11.so`, silently skips X11 and reaches for a tty. +- **Do not pass `--tty`**: `main.c:2133` only takes the X11 path while + `default_tty` is unset. Bind an empty dir over `/dev/dri` too — with X11 + first in the renderer order (`ply-renderer.c:283`) DRM is never used, and the + mask makes that a guarantee rather than a hope. +- `PLY_CREATE_FAKE_MULTI_HEAD_SETUP=1` → 800×600 + 640×480 windows. + `plymouth show-splash`, then `plymouth ask-for-password` renders the LUKS + dialog. Debug: `--kernel-command-line="plymouth.debug=stream:/path splash"` + (**`stream:`**, not `file:` — `file:` buffers and you get nothing). +- Fast loop: copy the theme into the rig's `themes/` and sed its `.plymouth` + `ScriptFile=`/`ImageDir=` to the copy — then editing the script is a 10s + render instead of a 3min `nixos-rebuild`. + +**What the rig already proved (2026-07-16):** with a *stable* two-head canvas +the **current** theme centres correctly on both heads — logo and progress bar +each in their own head's middle. So the geometry is not the bug, which is the +evidence for the timing diagnosis above. The fake path builds both heads up +front, so it cannot reproduce a canvas *change*: for that, plug/unplug while +the splash is up, or compare a boot with and without the external. + +**Attempt 1 failed, and the rig caught it — that is the point.** Hoisting the +positions into a `layout()` called from `refresh_callback` rendered the +background on both heads and **nothing else**: no logo, no progress bar. The +script plugin logs `starting boot animation` and **no parse error**, so it +fails *silently* — reverted rather than shipped (that script draws the LUKS box +on an encrypted machine). Next session: bisect with the fast loop. Prime +suspect is scope — plymouth script assigns into `local` unless the name already +exists, so a `logo.image = …` inside a function may not be the `logo` the rest +of the script sees, even though the same pattern works in `update_progress_bar`. +Test that hypothesis *first*, in isolation, before rewriting anything. + +Pass = with the rig, a two-head render centred on both **plus the password +dialog visible**; then a real docked boot + shutdown, undocked unchanged. ### 143. Rofi keeps the selected *row number* when the search changes, so the highlight lands on an unrelated entry diff --git a/docs/TESTING.md b/docs/TESTING.md index 92e1ab5..105ceb3 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -108,6 +108,19 @@ unattended env it uses is in the script, and the same flow works interactively from the live terminal. If the desktop comes up unthemed, read `/var/log/nomarchy-hm-preactivate.log` on the installed system. +`tools/plymouth-preview.sh` renders the **boot splash** — including the LUKS +password dialog — into X11 windows in about ten seconds: no reboot, no root, +and no DRM. It runs plymouthd inside `unshare -rm` (a user + mount namespace), +binds a writable dir over `/run/plymouth` (which is both the daemon's socket +dir *and* its compiled-in plugin path), masks `/dev/dri` so a preview can never +mode-set a real output, and sets `PLY_CREATE_FAKE_MULTI_HEAD_SETUP` for plymouth's +built-in fake dual head. Pass it a theme store path to iterate on the script +without a rebuild. Use it before touching `modules/nixos/plymouth/`: that script +draws the passphrase box, a failure there is silent (the plugin logs +`starting boot animation` and no error), and "type your LUKS password blind" is +a bad way to find out. Its limit: the fake heads exist from the start, so it +cannot reproduce a head arriving *mid-splash* (#137). + Two full-desktop VM harnesses (softGL Hyprland, too heavy for `checks.*`) live next to the scripts: `tools/theme-shot.nix` (themed-desktop screenshots) and `tools/monitor-fallback.nix` (KVM display transitions: diff --git a/tools/plymouth-preview.sh b/tools/plymouth-preview.sh new file mode 100755 index 0000000..8041212 --- /dev/null +++ b/tools/plymouth-preview.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# Render the Plymouth splash in X11 windows — no root, no reboot, no DRM (#137). +# +# tools/plymouth-preview.sh [] +# +# Plymouth is otherwise untestable without rebooting, which is a poor loop for +# a script that draws the LUKS passphrase box on an encrypted machine. This +# gives you a real render in seconds: +# +# * `unshare -rm` = user + mount namespace. We are root only in here, and +# every mount below dies with the process. +# * /run/plymouth is both the daemon's socket dir AND its compiled-in plugin +# path (`strings plymouthd` → /run/plymouth/plugins/). So we bind a +# writable dir over it and recreate its symlinks — miss those and plymouth +# cannot find x11.so, silently skips X11, and goes looking for a tty. +# * X11 is first in plymouth's renderer order (ply-renderer.c:283) and is +# taken only while default_tty is unset — so never pass --tty. /dev/dri is +# masked as well: a preview must not be able to mode-set the real outputs. +# * PLY_CREATE_FAKE_MULTI_HEAD_SETUP (renderers/x11/plugin.c) fakes two heads +# (800x600 + 640x480) — the multi-monitor case, on a single-monitor desk. +# +# Caveat: the fake heads are created up front, so this proves rendering and +# centring, NOT #137's canvas-change bug (a head arriving mid-splash). +set -u +THEME="${1:-}" +PLY=$(nix build --no-link --print-out-paths nixpkgs#plymouth 2>/dev/null | head -1) +[ -n "$PLY" ] || { echo "could not build plymouth" >&2; exit 1; } +[ -n "${DISPLAY:-}" ] || { echo "no DISPLAY — the x11 renderer needs Xwayland" >&2; exit 1; } + +export PLY THEME +unshare -rm -- bash -euc ' + rm -rf /tmp/plyrig; mkdir -p /tmp/plyrig/run /tmp/plyrig/dri /tmp/plyrig/themes + if [ -n "$THEME" ]; then + cp -rL "$THEME/share/plymouth/themes/nomarchy" /tmp/plyrig/themes/ + # The .plymouth bakes absolute store paths at build time; point them at the + # copy so the script here is the one that renders (edit + rerun = seconds). + sed -i "s|^ScriptFile=.*|ScriptFile=/tmp/plyrig/themes/nomarchy/nomarchy.script|; + s|^ImageDir=.*|ImageDir=/tmp/plyrig/themes/nomarchy|" \ + /tmp/plyrig/themes/nomarchy/nomarchy.plymouth + ln -s /tmp/plyrig/themes /tmp/plyrig/run/themes + else + ln -s /etc/plymouth/themes /tmp/plyrig/run/themes + fi + ln -s /etc/plymouth/plugins /tmp/plyrig/run/plugins + ln -s /etc/plymouth/plymouthd.defaults /tmp/plyrig/run/plymouthd.defaults + mount --bind /tmp/plyrig/run /run/plymouth + mount --bind /tmp/plyrig/dri /dev/dri 2>/dev/null || true + + export PLY_CREATE_FAKE_MULTI_HEAD_SETUP=1 + "$PLY/sbin/plymouthd" --no-daemon --mode=boot --no-boot-log \ + --kernel-command-line="plymouth.debug=stream:/tmp/plyrig/dbg.log splash" \ + >/dev/null 2>&1 & + d=$! + sleep 2 + "$PLY/bin/plymouth" show-splash >/dev/null 2>&1 + echo "splash up — two windows (800x600, 640x480). Ctrl+C or wait 20s." + sleep 8 + "$PLY/bin/plymouth" ask-for-password --prompt="Unlock" --number-of-tries=1 >/dev/null 2>&1 & + echo "password dialog requested (the LUKS path)" + sleep 10 + "$PLY/bin/plymouth" quit >/dev/null 2>&1; sleep 1; kill $d 2>/dev/null + cp /tmp/plyrig/dbg.log /tmp/plymouth-preview.log 2>/dev/null + echo "log: /tmp/plymouth-preview.log" +'