Files
mirror/docs/kiosk-silverblue/check-kiosk.sh
T
ducoterra 7d4bcf7e23 fix(kiosk): stop GDM covering the wall with a greeter after auto-login
Auto-login succeeded and the wall kept streaming, but about ten seconds
after the kiosk session came up GDM autospawned a greeter on the same GPU
and a login screen covered it. gnome-kiosk ships X-GDM-SessionRegisters=false,
so nothing ever registers and GDM concludes the display is still free — which
is why every remote check (loginctl, the mirror roster) stayed green while
the physical screen showed a password prompt.

Override that session file with the flag set to true, keeping the file name
and session id identical: inventing an id makes GDM quietly fall back to the
plain GNOME session, top bar and all.

Add a per-minute guard that reclaims the foreground VT, because this class
of failure is invisible from the outside, and have the checker assert both
the foreground VT and the greeter-session count. That count had a silent
false green: journalctl has no --no-legend, so the check read an empty
stream and passed unconditionally.

Also keep a copy of the payload in /usr/local/share/mirror-kiosk. /tmp is
cleared at every boot, which deletes the checker just after the one event
you are most likely to want to re-run it for.

Verified on the reference client: a cold boot now creates no greeter session
at all, the kiosk compositor and Firefox come up undisturbed, and a console
stolen with chvt is taken back within a minute.

LLM-Generated: true
2026-11-01 19:46:17 -05:00

185 lines
7.6 KiB
Bash
Executable File

#!/bin/sh
# check-kiosk.sh — read-only report on a mirror kiosk client. Run ON THE CLIENT.
#
# sudo sh check-kiosk.sh
#
# PASS/FAIL describe this machine; WARN means "configured fine, but the thing
# you can see on the screen will still be wrong". Exits non-zero on FAIL.
#
# Nothing here needs the console: every check is readable over SSH, which is
# the whole point of a kiosk you never plug a keyboard into.
KIOSK_USER="${KIOSK_USER:-kiosk}"
FAILS=0
ok() { printf 'PASS %s\n' "$*"; }
bad() { printf 'FAIL %s\n' "$*"; FAILS=$((FAILS + 1)); }
warn() { printf 'WARN %s\n' "$*"; }
info() { printf 'info %s\n' "$*"; }
SUDO=""
[ "$(id -u)" -eq 0 ] || SUDO="sudo"
printf '%s\n' '--- machine ---'
if [ -f /etc/os-release ]; then
info "$( . /etc/os-release && printf '%s %s' "$NAME" "$VERSION_ID" ) on $(uname -m)"
fi
if command -v rpm-ostree >/dev/null 2>&1; then
info "booted deployment: $(rpm-ostree status 2>/dev/null | sed -n 's/^ *● *//p' | head -1)"
info "layered: $(rpm-ostree status 2>/dev/null | sed -n 's/^ *LayeredPackages: //p' | head -1)"
fi
printf '\n%s\n' '--- packages ---'
for p in gnome-kiosk gnome-kiosk-script-session firefox; do
if rpm -q "$p" >/dev/null 2>&1; then ok "$p installed"; else bad "$p not installed (rpm-ostree install $p)"; fi
done
printf '\n%s\n' '--- user ---'
if id -u "$KIOSK_USER" >/dev/null 2>&1; then
HOME_DIR=$(getent passwd "$KIOSK_USER" | cut -d: -f6)
ok "user $KIOSK_USER exists, home $HOME_DIR"
else
bad "user $KIOSK_USER does not exist"
HOME_DIR=""
fi
printf '\n%s\n' '--- payload ---'
check_file() {
path=$1 needle=$2 label=$3
if [ -f "$path" ]; then
if [ -n "$needle" ] && ! $SUDO grep -q "$needle" "$path" 2>/dev/null; then
bad "$label: $path exists but has no '$needle'"
else
ok "$label: $path"
fi
else
bad "$label: $path is missing"
fi
}
URL=""
if [ -n "$HOME_DIR" ]; then
check_file "$HOME_DIR/.local/bin/gnome-kiosk-script" "firefox --kiosk" "kiosk script"
if [ -f "$HOME_DIR/.config/mirror/kiosk.conf" ]; then
URL=$($SUDO sed -n 's/^MIRROR_KIOSK_URL="\{0,1\}\([^"]*\)"\{0,1\}$/\1/p' \
"$HOME_DIR/.config/mirror/kiosk.conf" 2>/dev/null | head -1)
ok "kiosk URL: ${URL:-<empty>}"
case "$URL" in
*/watch) : ;;
"") bad "kiosk URL is empty" ;;
*) warn "kiosk URL is not the /watch wall: $URL" ;;
esac
else
bad "kiosk config $HOME_DIR/.config/mirror/kiosk.conf is missing"
fi
check_file "$HOME_DIR/.config/gnome-initial-setup-done" "" "initial-setup guard"
fi
check_file /etc/firefox/policies/policies.json "DisableAppUpdate" "firefox policy"
check_file /usr/local/share/gnome-kiosk/window-config.ini "org.mozilla" "kiosk window rules"
# Without this key the shipped gnome-kiosk session never registers with GDM and
# GDM autospawns a greeter over the wall ~8s after auto-login. The single most
# confusing failure this setup has: the kiosk keeps running underneath, so the
# mirror roster and loginctl both still look perfect while the screen shows a
# login prompt.
check_file /usr/local/share/wayland-sessions/gnome-kiosk-script-wayland.desktop \
"X-GDM-SessionRegisters=true" "GDM session override"
printf '\n%s\n' '--- session wiring ---'
ACCT="/var/lib/AccountsService/users/$KIOSK_USER"
if [ -f "$ACCT" ] && $SUDO grep -q '^Session=gnome-kiosk-script-wayland' "$ACCT"; then
ok "AccountsService default session is the kiosk"
else
bad "AccountsService session for $KIOSK_USER is not gnome-kiosk-script-wayland"
[ -f "$ACCT" ] && info "$($SUDO cat "$ACCT" 2>/dev/null | tr '\n' ' ')"
fi
if [ -f /etc/gdm/custom.conf ] &&
$SUDO grep -qi "^AutomaticLogin=$KIOSK_USER\$" /etc/gdm/custom.conf &&
$SUDO grep -qi "^AutomaticLoginEnable=True" /etc/gdm/custom.conf; then
ok "gdm auto-login targets $KIOSK_USER"
else
bad "gdm auto-login is not enabled for $KIOSK_USER"
fi
printf '\n%s\n' '--- running state ---'
SEAT_USER=$(loginctl list-sessions --no-legend 2>/dev/null | awk '$4=="seat0"{print $3; exit}')
if [ -z "$SEAT_USER" ]; then
warn "nobody is logged in on seat0 (sudo systemctl restart gdm)"
elif [ "$SEAT_USER" = "$KIOSK_USER" ]; then
ok "seat0 is logged in as $KIOSK_USER"
else
bad "seat0 is logged in as '$SEAT_USER', not $KIOSK_USER (sudo systemctl restart gdm)"
fi
SEAT_SESSION=$(loginctl list-sessions --no-legend 2>/dev/null | awk '$4=="seat0"{print $1; exit}')
if [ -n "$SEAT_SESSION" ]; then
info "seat0 session $SEAT_SESSION: class=$(loginctl show-session "$SEAT_SESSION" -p Class --value 2>/dev/null) type=$(loginctl show-session "$SEAT_SESSION" -p Type --value 2>/dev/null)"
fi
# Does the kiosk actually hold the screen? A session can be Active while another
# one owns the foreground VT, which is what a covered wall looks like from here.
KIOSK_SESSION=$(loginctl list-sessions --no-legend 2>/dev/null |
awk -v u="$KIOSK_USER" '$3==u && $4=="seat0"{print $1; exit}')
if [ -n "$KIOSK_SESSION" ]; then
K_ACTIVE=$(loginctl show-session "$KIOSK_SESSION" -p Active --value 2>/dev/null)
K_TTY=$(loginctl show-session "$KIOSK_SESSION" -p TTY --value 2>/dev/null)
K_FG=$(sudo cat /sys/class/tty/tty0/active 2>/dev/null)
if [ "$K_ACTIVE" = "yes" ] && [ -n "$K_TTY" ] && [ "$K_FG" = "$K_TTY" ]; then
ok "the kiosk holds the console (foreground VT $K_FG)"
else
bad "console is on ${K_FG:-?} but the kiosk is on ${K_TTY:-?} (Active=$K_ACTIVE) — the screen is showing something else"
info "fix: sudo loginctl activate $KIOSK_SESSION, then check the session override above"
fi
fi
# Count the greeter *sessions* GDM created this boot, not every mention: a
# healthy kiosk creates none, and the autospawn bug creates exactly one soon
# after auto-login. (journalctl has no --no-legend; using it here once made this
# test read an empty stream and pass unconditionally — a silent false green.)
GREETERS=$(sudo journalctl -b --no-pager 2>/dev/null |
grep -c "New session .* of user 'gdm-greeter'")
if [ "${GREETERS:-0}" -gt 0 ]; then
warn "$GREETERS GDM greeter session(s) this boot: the wall may be covered by a login screen"
info "check X-GDM-SessionRegisters=true in /usr/local/share/wayland-sessions/"
fi
if [ -n "$HOME_DIR" ]; then
KUID=$(id -u "$KIOSK_USER")
FF=$(pgrep -u "$KUID" -a firefox 2>/dev/null | head -1)
if [ -z "$FF" ]; then
warn "no firefox running as $KIOSK_USER yet"
else
case "$FF" in
*--kiosk*) ok "$FF" ;;
*) warn "firefox is running without --kiosk: $FF" ;;
esac
fi
if [ -f "$HOME_DIR/.local/state/mirror-kiosk.log" ]; then
info "last kiosk log lines:"
$SUDO tail -5 "$HOME_DIR/.local/state/mirror-kiosk.log" 2>/dev/null | sed 's/^/ /'
fi
fi
printf '\n%s\n' '--- what the screen will show ---'
if [ -n "$URL" ]; then
BODY=$(curl -fsSL -m 8 "$URL" 2>/dev/null)
if [ -z "$BODY" ]; then
warn "$URL is not reachable from this machine right now"
else
CODE=$(curl -s -o /dev/null -w '%{http_code}' -m 8 "$URL" 2>/dev/null)
if printf '%s' "$BODY" | grep -q "Watch · mirror"; then
ok "$URL serves the mirror watch page (HTTP $CODE)"
else
warn "$URL answered HTTP $CODE but it is NOT the mirror watch page — the kiosk"
warn "will happily display whatever this is. Check DNS and the nginx vhost."
info "title: $(printf '%s' "$BODY" | sed -n 's/.*<title>\(.*\)<\/title>.*/\1/p' | head -1)"
fi
fi
else
info "no URL configured, nothing to fetch"
fi
printf '\n'
if [ "$FAILS" -gt 0 ]; then
printf '%s check(s) FAILED.\n' "$FAILS"
exit 1
fi
printf 'All checks passed.\n'