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
This commit is contained in:
@@ -75,6 +75,13 @@ if [ -n "$HOME_DIR" ]; then
|
||||
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"
|
||||
@@ -103,7 +110,34 @@ else
|
||||
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) vt=$(loginctl show-session "$SEAT_SESSION" -p VTHint --value 2>/dev/null)"
|
||||
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
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
[Desktop Entry]
|
||||
Name=Kiosk Script Session (Wayland Display Server)
|
||||
Comment=This session logs you into the session started by ~/.local/bin/gnome-kiosk-script
|
||||
Exec=gnome-session --session gnome-kiosk-script
|
||||
TryExec=gnome-session
|
||||
Type=Application
|
||||
DesktopNames=GNOME-Kiosk;GNOME;
|
||||
X-GDM-SessionRegisters=true
|
||||
X-GDM-CanRunHeadless=true
|
||||
@@ -58,7 +58,7 @@ for c in rpm getent restorecon useradd sed awk grep install; do need "$c"; done
|
||||
[ -f /etc/os-release ] && grep -q '^ID=fedora' /etc/os-release ||
|
||||
say "warning: this does not look like Fedora; continuing anyway"
|
||||
|
||||
say "== 1/6 packages =="
|
||||
say "== 1/7 packages =="
|
||||
if rpm -q gnome-kiosk gnome-kiosk-script-session >/dev/null 2>&1; then
|
||||
say "gnome-kiosk + gnome-kiosk-script-session already present"
|
||||
else
|
||||
@@ -69,7 +69,7 @@ else
|
||||
say "layered the kiosk packages — a reboot is required before they exist"
|
||||
fi
|
||||
|
||||
say "== 2/6 kiosk user =="
|
||||
say "== 2/7 kiosk user =="
|
||||
if id -u "$KIOSK_USER" >/dev/null 2>&1; then
|
||||
HOME_DIR=$(getent passwd "$KIOSK_USER" | cut -d: -f6)
|
||||
else
|
||||
@@ -81,7 +81,7 @@ fi
|
||||
KIOSK_GROUP=$(getent passwd "$KIOSK_USER" | cut -d: -f4)
|
||||
say "user $KIOSK_USER uid=$(id -u "$KIOSK_USER") home=$HOME_DIR"
|
||||
|
||||
say "== 3/6 payload =="
|
||||
say "== 3/7 payload =="
|
||||
# Firefox enterprise policy: no update prompts, no account/sync, no telemetry,
|
||||
# no first-run pages. /etc is mutable on Silverblue and survives upgrades.
|
||||
install -D -m 0644 "$HERE/policies.json" /etc/firefox/policies/policies.json
|
||||
@@ -93,6 +93,18 @@ say " /etc/firefox/policies/policies.json"
|
||||
install -D -m 0644 "$HERE/window-config.ini" /usr/local/share/gnome-kiosk/window-config.ini
|
||||
say " /usr/local/share/gnome-kiosk/window-config.ini"
|
||||
|
||||
# THE GREETER FIX. The shipped gnome-kiosk session sets X-GDM-SessionRegisters=
|
||||
# false, so nothing ever registers with GDM and GDM concludes the display is
|
||||
# still free: about eight seconds after auto-login it autospawns a full greeter
|
||||
# session on the same GPU and a login screen covers the wall. The kiosk keeps
|
||||
# running underneath, so loginctl and the mirror roster both still look healthy.
|
||||
# Registering makes GDM treat the session as the display's owner. Keep the file
|
||||
# name identical to the shipped one and change only that key — inventing a new
|
||||
# session id makes GDM quietly fall back to the plain GNOME session.
|
||||
install -D -m 0644 "$HERE/gnome-kiosk-script-wayland.desktop" \
|
||||
/usr/local/share/wayland-sessions/gnome-kiosk-script-wayland.desktop
|
||||
say " /usr/local/share/wayland-sessions/gnome-kiosk-script-wayland.desktop (registers with GDM)"
|
||||
|
||||
# `install -d` creates missing parents but leaves them owned by whoever is
|
||||
# running the installer, which is how a kiosk ends up with a root-owned
|
||||
# ~/.local and then spends its life logging "Permission denied" about
|
||||
@@ -117,7 +129,7 @@ say " $HOME_DIR/.config/mirror/kiosk.conf -> $KIOSK_URL"
|
||||
install -m 0644 -o "$KIOSK_USER" -g "$KIOSK_GROUP" /dev/null \
|
||||
"$HOME_DIR/.config/gnome-initial-setup-done"
|
||||
|
||||
say "== 4/6 default session =="
|
||||
say "== 4/7 default session =="
|
||||
ACCT="/var/lib/AccountsService/users/$KIOSK_USER"
|
||||
if [ -f "$ACCT" ]; then
|
||||
set_ini "$ACCT" User Session "$KIOSK_SESSION"
|
||||
@@ -129,7 +141,7 @@ fi
|
||||
systemctl restart accounts-daemon.service 2>/dev/null || :
|
||||
say " $ACCT Session=$KIOSK_SESSION"
|
||||
|
||||
say "== 5/6 auto-login =="
|
||||
say "== 5/7 auto-login =="
|
||||
if [ ! -d /etc/gdm ]; then
|
||||
say " gdm is not installed — installing it"
|
||||
rpm-ostree install gdm
|
||||
@@ -144,13 +156,36 @@ if [ -d /etc/gdm ]; then
|
||||
say " /etc/gdm/custom.conf: AutomaticLogin=$KIOSK_USER"
|
||||
fi
|
||||
|
||||
say "== 6/6 selinux labels =="
|
||||
say "== 6/7 console guard =="
|
||||
# Cheap insurance against the class of bug above: if the kiosk session exists but
|
||||
# something else holds the foreground VT, bring the wall back. Stops short of
|
||||
# ever restarting gdm. Pause it while debugging the console with:
|
||||
# sudo systemctl stop mirror-kiosk-console-guard.timer
|
||||
install -D -m 0755 "$HERE/mirror-kiosk-console-guard" /usr/local/libexec/mirror-kiosk-console-guard
|
||||
install -D -m 0644 "$HERE/mirror-kiosk-console-guard.service" /etc/systemd/system/mirror-kiosk-console-guard.service
|
||||
install -D -m 0644 "$HERE/mirror-kiosk-console-guard.timer" /etc/systemd/system/mirror-kiosk-console-guard.timer
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now mirror-kiosk-console-guard.timer
|
||||
say " mirror-kiosk-console-guard.timer enabled"
|
||||
|
||||
# Keep a copy of the payload on the box. /tmp is cleared at every boot, so a
|
||||
# checker staged there is gone precisely when you want to run it again — after a
|
||||
# reboot, or from a rescue session hours later.
|
||||
if [ "$HERE" != "/usr/local/share/mirror-kiosk" ]; then
|
||||
install -d -m 0755 /usr/local/share/mirror-kiosk
|
||||
cp -a "$HERE"/. /usr/local/share/mirror-kiosk/
|
||||
say " /usr/local/share/mirror-kiosk (survives reboot; run check-kiosk.sh from here)"
|
||||
fi
|
||||
|
||||
say "== 7/7 selinux labels =="
|
||||
restorecon -RF "$HOME_DIR/.local" "$HOME_DIR/.config" 2>/dev/null || :
|
||||
restorecon -RF /etc/firefox /usr/local/share/gnome-kiosk 2>/dev/null || :
|
||||
restorecon -RF /etc/firefox /usr/local/share/gnome-kiosk /usr/local/share/wayland-sessions \
|
||||
/usr/local/libexec 2>/dev/null || :
|
||||
|
||||
say ""
|
||||
say "Installed."
|
||||
[ "$NEED_REBOOT" -eq 1 ] && say "NEXT: sudo reboot (new RPMs are only in the new deployment)"
|
||||
say "Then, to start the kiosk now (this closes anything on the console):"
|
||||
say " sudo systemctl restart gdm"
|
||||
say "Check it with: sh check-kiosk.sh"
|
||||
say "Check it with (this path survives reboots):"
|
||||
say " sudo sh /usr/local/share/mirror-kiosk/check-kiosk.sh"
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/bin/sh
|
||||
# mirror-kiosk-console-guard — make sure the kiosk owns the console.
|
||||
#
|
||||
# Run by mirror-kiosk-console-guard.timer once a minute as root.
|
||||
#
|
||||
# The failure this exists for is invisible remotely: the kiosk session keeps
|
||||
# running (loginctl says active, the mirror roster counts the wall as a viewer)
|
||||
# while some other session holds the foreground VT, so the screen shows a login
|
||||
# prompt and every check says everything is fine. That is exactly how the
|
||||
# greeter-autospawn bug presented itself.
|
||||
#
|
||||
# It deliberately does very little: if the kiosk HAS a seat0 session and that
|
||||
# session is not the foreground one, activate it. It never restarts gdm and
|
||||
# never logs anyone out, because a display manager is a bad thing to fight
|
||||
# automatically. If there is no kiosk session at all it only says so — that is a
|
||||
# problem for a human, and the message lands in the journal.
|
||||
#
|
||||
# Debugging the console by hand? Stop the fight:
|
||||
# sudo systemctl stop mirror-kiosk-console-guard.timer
|
||||
|
||||
set -eu
|
||||
|
||||
KIOSK_USER="${KIOSK_USER:-kiosk}"
|
||||
FG_VT=/sys/class/tty/tty0/active
|
||||
|
||||
say() { logger -t mirror-kiosk-guard -- "$*"; }
|
||||
|
||||
[ "$(id -u)" -eq 0 ] || { printf 'run me as root\n' >&2; exit 1; }
|
||||
|
||||
# SESSION UID USER SEAT ... — the kiosk's own session on the physical seat. The
|
||||
# greeter also sits on seat0 but under the gdm-greeter user, so the name filter
|
||||
# is what keeps this from chasing the greeter.
|
||||
session=$(loginctl list-sessions --no-legend 2>/dev/null |
|
||||
awk -v u="$KIOSK_USER" '$3 == u && $4 == "seat0" { print $1; exit }')
|
||||
|
||||
if [ -z "$session" ]; then
|
||||
say "no $KIOSK_USER session on seat0: auto-login did not produce one. Not touching gdm."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
active=$(loginctl show-session "$session" -p Active --value 2>/dev/null || echo unknown)
|
||||
tty=$(loginctl show-session "$session" -p TTY --value 2>/dev/null || echo "")
|
||||
foreground=$(cat "$FG_VT" 2>/dev/null || echo "")
|
||||
|
||||
if [ "$active" = "yes" ] && [ -n "$tty" ] && [ "$foreground" = "$tty" ]; then
|
||||
exit 0 # the wall already has the screen
|
||||
fi
|
||||
|
||||
say "console is on ${foreground:-unknown}, kiosk session $session is on ${tty:-unknown} (Active=$active) — activating it"
|
||||
if loginctl activate "$session" 2>/dev/null; then
|
||||
say "kiosk session $session brought back to the foreground"
|
||||
else
|
||||
say "could not activate kiosk session $session — look at journalctl -b -u gdm"
|
||||
fi
|
||||
@@ -0,0 +1,8 @@
|
||||
[Unit]
|
||||
Description=Keep the mirror kiosk on the console
|
||||
# Nothing else may be mid-switch when we look at the console.
|
||||
After=graphical.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/libexec/mirror-kiosk-console-guard
|
||||
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Check every minute that the mirror kiosk owns the console
|
||||
|
||||
[Timer]
|
||||
# Give the session time to come up first, then check forever.
|
||||
OnBootSec=90s
|
||||
OnUnitActiveSec=60s
|
||||
AccuracySec=15s
|
||||
# If the box was suspended, run once immediately on resume.
|
||||
WakeSystem=false
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
Reference in New Issue
Block a user