This was superseded thank God
Ubuntu Jumpbox + Dashboard Kiosk
Pi Zero Dashboard Kiosk
STATUS: WORKING, as of 2026-08-29 — see Homepage Dashboard Screenshotter (Playwright) for the final architecture. Browser-in-kiosk approach (below) was abandoned as a dead end; the working solution renders on the LXC via Playwright and displays a static PNG on the Pi via fbi, no browser/X involved at all.
Goal: $30 Raspberry Pi Zero + spare 1080p (LG) monitor as an always-on display for the Homepage dashboard at http://192.168.1.95:3000.
Static IP:
192.168.2.97
Confirmed hardware/OS
- Original Pi Zero/Zero W (ARMv6, no NEON) — confirmed via chromium’s NEON error and later
armv6lin kernel banner. Not a Zero 2 W. - Running Raspberry Pi OS Lite (Bookworm-based,
armv6l), set up viarpi-imagerwith cloud-init (ds=nocloud). - Hostname
coytisboard, usercoytis, static-ish on192.168.1.x/192.168.2.xLAN. - Monitor: LG FULL HD, confirmed via EDID in Xorg log — X drives it fine at 1920x1080.
Dead ends (all confirmed, don’t retry blindly)
- Chromium (
chromium/chromium-browser) — dead on arrival. Debian/Raspberry Pi OS Bookworm’s stockchromiumpackage requires NEON (ARMv7+). This is a hard CPU incompatibility, not fixable by reflashing or reinstalling — RPi OS now ships the same NEON-requiring Debian build (no longer maintains their own ARMv6-compatible fork). - Epiphany (GNOME Web) —
--application-mode <URL>doesn’t work as expected; it wants a pre-installed.desktopweb-app file, not a raw URL (Invalid desktop file passed to --application-mode). Switched to plainepiphany <URL>+ xdotool F11 — got past that, but then hit: - Epiphany GL/EGL failure —
Unable to create a GL context/No EGL configuration available. X’s own glamor/DRI2 GL works fine (confirmed in Xorg log — VC4 V3D acceleration loads correctly), but WebKitGTK’s own separate EGL context creation fails. TriedWEBKIT_DISABLE_COMPOSITING_MODE=1— screen stayed black (unclear if this alone would’ve fixed Epiphany specifically; moved to surf before fully isolating). - surf (suckless browser, same WebKitGTK backend) — got furthest here. X launches perfectly (verified full clean Xorg log, HDMI-1 connected, EDID read correctly, VC4 GL accelerated). Surf itself launches, runs ~28s, then crashes with “Floating point exception” (SIGFPE) — confirmed via
xinitlog, not a kernel OOM (checkeddmesg/free -h, both clean) and not an X-level crash. TriedJSC_useJIT=0to force JavaScriptCore into pure-interpreter mode (ruling out a JIT codegen bug) — same SIGFPE, so the crash is not in the JIT, it’s deeper in WebKit (likely a genuine ARMv6 float-handling bug provoked by Homepage’s React/JS rendering — chart widgets, icon libraries etc. are the usual suspects for this class of bug).
Abandoned here — never ran the planned info.cern.ch isolation test. Pivoted straight to Plan C instead, which turned out to be the right call.
Key debugging gotchas learned (useful beyond this project too)
xorgis a metapackage name, not a command.- X refuses to start over an SSH session even with
Xwrapper.config allowed_users=anybodyset — it also needs genuine console/VT access (/dev/tty0), which only a real tty1 login session gets via systemd-logind. Can’t test kiosk X sessions over SSH directly — must either physically watch the monitor after a real console login, or force a fresh tty1 login remotely viasudo systemctl restart getty@tty1.service(this re-triggers.bash_profile→startxwithout a full reboot). - Redirecting
startxoutput to a file (> xstart.log 2>&1) can appear truncated when cat’d immediately — stdio fully-buffers when not attached to a tty, so partial reads mid-session are misleading. Xorg’s own log at~/.local/share/xorg/Xorg.0.logflushes properly and is more reliable for mid-session checks. Once the session has fully exited, the redirected file is reliable too (that’s how the “Floating point exception” line was actually found).
Current .bash_profile (working, don’t need to touch again)
if [ -z "$DISPLAY" ] && [ "$(tty)" = "/dev/tty1" ]; then
startx -- -nocursor
fiCurrent .xinitrc (X launch works; browser client crashes)
#!/bin/sh
xset s off
xset s noblank
xset -dpms
export WEBKIT_DISABLE_COMPOSITING_MODE=1
export JSC_useJIT=0
(sleep 15 && while true; do sleep 60; xdotool key --clearmodifiers F5; done) &
surf -F http://192.168.1.95:3000Note: the F5-refresh loop is actually unnecessary — Homepage auto-updates its widgets client-side already. Can be dropped once something renders.
Also set: hdmi_force_hotplug=1 in /boot/firmware/config.txt, allowed_users=anybody in /etc/X11/Xwrapper.config, autologin on tty1 via raspi-config nonint do_boot_behaviour B2.
Plan C — the winning approach (now live)
Render on the LXC instead of the Pi Zero — it’s fundamentally too weak/incompatible for a modern JS-heavy dashboard (Chromium needs NEON it doesn’t have; WebKitGTK crashes on Homepage’s content). Full build-out, script, and Docker Compose config: Homepage Dashboard Screenshotter (Playwright). Pi-side deployment gotchas (the fun part) are below.
Getting fbi actually working on the Pi — three separate bugs, stacked
Once the LXC side (Playwright + nginx) was up and serving a real PNG, the Pi side still took three rounds of debugging before fbi would stay on screen:
- fbi under a bare systemd service (
TTYPath=/dev/tty1) couldn’t do VT ioctls —ioctl VT_GETSTATE/VT_GETMODE: Inappropriate ioctl for device. Assumed this meant it needed a genuine console/logind session (same as X did earlier) — reasonable theory, but wrong, see #3. - fbi would flash the image for under a second then exit — no crash, no error, just silently gone. Assumed it was fbi’s keypress-wait logic misfiring on a broken stdin; adding
-t 65(auto-timeout instead of waiting for a key) didn’t fix it — ruled that theory out. - Actual root cause: the script backgrounds fbi with a plain
&inside a#!/bin/shloop. POSIX shells (dash, which is Debian’s/bin/sh) redirect a backgrounded job’s stdin to/dev/nullby default, regardless of the script’s own controlling terminal. So fbi’s stdin was never/dev/tty1in any attempt — systemd service, SSH, or genuine tty1 autologin — it was always/dev/null, which explains the identicalVT_GETSTATEfailure no matter how the session was launched. Fix: explicitly redirect fbi’s stdin —fbi ... < /dev/tty1 > /tmp/fbi.log 2>&1 &. That one flag fixed it after the session-type red herring wasted real time.
Worth remembering generally: any time a backgrounded (&) process inside a shell script needs a real tty (VT ioctls, terminal-aware CLI tools, etc.), redirect its stdin explicitly — don’t assume it inherits the script’s own terminal.
Final working Pi-side setup
~/.bash_profile:
if [ -z "$DISPLAY" ] && [ "$(tty)" = "/dev/tty1" ]; then
exec /home/coytis/dash-display.sh
fi/home/coytis/dash-display.sh:
#!/bin/sh
while true; do
curl -fsS -o /tmp/dash.png.tmp http://192.168.1.95:8080/dash.png && mv /tmp/dash.png.tmp /tmp/dash.png
pkill fbi 2>/dev/null
fbi -d /dev/fb0 -a --noverbose -t 65 /tmp/dash.png < /dev/tty1 > /tmp/fbi.log 2>&1 &
sleep 60
doneLaunched via genuine tty1 autologin (getty@tty1.service + /etc/systemd/system/getty@tty1.service.d/autologin.conf, same override used for the abandoned X approach) — not a standalone systemd service with TTYPath, which is what caused bug #1 above (a dash-display.service unit was tried and scrapped in favor of this).
-d /dev/fb0 forces plain framebuffer mode (skips fbi’s DRM auto-detect path entirely). No X, no .xinitrc, no browser — this fully replaces the entire dead-end setup above.
Smoother image transition (queued, untested)
Current script kills and relaunches fbi every cycle — the visible “flash” is mostly the process restart (reopening the framebuffer device, reloading the font) rather than the image change itself. fbi has no crossfade/blend capability at all (it’s a raw framebuffer writer, no compositing), so a true smooth dissolve isn’t achievable on this hardware — but the restart-flash itself is fixable: keep one fbi process alive permanently and use its remote-control FIFO (--comm) to trigger a redraw instead of restarting the process each time.
#!/bin/sh
FIFO=/tmp/fbi-ctrl
[ -p "$FIFO" ] || mkfifo "$FIFO"
fbi -u -a --noverbose -d /dev/fb0 --comm "$FIFO" /tmp/dash.png < /dev/tty1 > /tmp/fbi.log 2>&1 &
while true; do
curl -fsS -o /tmp/dash.png.tmp http://192.168.1.95:8080/dash.png && mv /tmp/dash.png.tmp /tmp/dash.png
echo > "$FIFO"
sleep 60
donefbi now launches once outside the loop with -u (don’t cache, always re-read from disk) and --comm "$FIFO"; the loop only fetches the image and pokes the FIFO with a blank line to trigger a redraw — no more pkill/relaunch. Not yet confirmed: whether a bare echo > is actually the right trigger command for fbi’s comm protocol — check man fbi before relying on this, might need a specific keyword instead of a blank line.
Rotating for a vertical/portrait monitor
fbi has no built-in rotation flag — rotate the image file itself before displaying it. Simplest approach, works regardless of what the LXC-side Playwright script captures:
sudo apt install -y imagemagickAdd a rotate step in dash-display.sh right after the curl/mv, before fbi runs:
convert /tmp/dash.png -rotate 90 /tmp/dash.pngUse -rotate 90 or -rotate 270 depending on which way the monitor is physically turned — try one, and if the dashboard reads upside-down or sideways, switch to the other.
Optional refinement, not required to just get it working: Homepage is likely responsive, so instead of capturing a landscape (1920x1080) screenshot and then rotating it — which just tilts the landscape layout sideways, small text and all — you could swap WIDTH/HEIGHT in the screenshotter’s docker-compose.yml environment (e.g. WIDTH=1080, HEIGHT=1920) so Homepage’s own CSS reflows into a genuine portrait layout, then still apply the same -rotate 90 step on the Pi to match the physical panel’s native landscape scan-out. Untested — depends on how well Homepage’s layout actually reflows at a narrow viewport.
Next steps
- Build Plan C (screenshot source +
fbiloop on the Pi) — done and confirmed working 2026-08-29 - Consider a cleaner systemd-based launch for the Pi side now that the stdin bug is understood (the
TTYPathapproach would work fine now with< /dev/tty1added) — not urgent, current autologin setup works ✅ 2026-09-02 - Try the
--commFIFO approach above to smooth out the transition flash ✅ 2026-09-02 - If going vertical: install imagemagick + add the rotate step, decide 90 vs 270 by testing ✅ 2026-09-02
- Longer-term: notification wiring for Uptime Kuma is still outstanding per log - homepage dashboard setup — unrelated to this note but flagged there ✅ 2026-09-02