#!/bin/sh # install-kiosk.sh — turn this Fedora Silverblue box into a mirror kiosk client. # # Run ON THE CLIENT, as root, from the directory holding the payload files: # # sudo sh install-kiosk.sh # sudo KIOSK_URL=http://192.168.1.10:8055/watch sh install-kiosk.sh # # Idempotent: safe to re-run to repair or to change the URL. It deliberately # does NOT restart gdm, because that kills whatever is on the console right # now — it prints the command instead. # # What it configures, and why, is in ../kiosk-silverblue.md. set -eu KIOSK_USER="${KIOSK_USER:-kiosk}" KIOSK_URL="${KIOSK_URL:-https://mirror.reeseapps.com/watch}" KIOSK_SESSION="gnome-kiosk-script-wayland" HERE=$(cd "$(dirname "$0")" && pwd) NEED_REBOOT=0 say() { printf '%s\n' "$*"; } die() { printf 'error: %s\n' "$*" >&2; exit 1; } need() { command -v "$1" >/dev/null 2>&1 || die "missing required command: $1"; } # Rewrite key= inside [section] of an ini file, in place, preserving the # file's own inode so its SELinux label and mode survive. set_ini() { file=$1 section=$2 key=$3 value=$4 awk -v sec="$section" -v key="$key" -v val="$value" ' BEGIN { insec = 0; done = 0; hassec = 0 } /^[[:space:]]*\[/ { if (insec && !done) { print key "=" val; done = 1 } cur = $0 gsub(/[[:space:]]/, "", cur); sub(/^\[/, "", cur); sub(/\]$/, "", cur) insec = (cur == sec) if (insec) hassec = 1 print next } insec && $0 ~ "^[[:space:]]*" key "[[:space:]]*=" { if (!done) { print key "=" val; done = 1 } next } { print } END { if (!done) { if (!hassec) print "[" sec "]" print key "=" val } } ' "$file" >"$file.tmp" && cat "$file.tmp" >"$file" && rm -f "$file.tmp" } [ "$(id -u)" -eq 0 ] || die "run me as root (sudo sh $0)" 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/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 # Layering is the only supported way to add RPMs on Silverblue; /usr stays # read-only and the result is transactional. rpm-ostree install gnome-kiosk gnome-kiosk-script-session NEED_REBOOT=1 say "layered the kiosk packages — a reboot is required before they exist" fi 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 useradd --home-dir "/var/home/$KIOSK_USER" --create-home --shell /bin/bash "$KIOSK_USER" HOME_DIR="/var/home/$KIOSK_USER" say "created user $KIOSK_USER (no password; auto-login does not need one)" fi [ -d "$HOME_DIR" ] || die "$KIOSK_USER has no home directory at $HOME_DIR" KIOSK_GROUP=$(getent passwd "$KIOSK_USER" | cut -d: -f4) say "user $KIOSK_USER uid=$(id -u "$KIOSK_USER") home=$HOME_DIR" 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 say " /etc/firefox/policies/policies.json" # Window rules for gnome-kiosk. /usr/local is /var/usrlocal (writable) and is # searched BEFORE /usr/share, so this overrides the shipped file without # touching the read-only /usr. 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 # keyrings and ICC profiles. Create each level, then chown it explicitly. for d in .local .local/bin .local/state .local/share .config .config/mirror; do install -d -m 0755 "$HOME_DIR/$d" chown "$KIOSK_USER:$KIOSK_GROUP" "$HOME_DIR/$d" done install -m 0755 -o "$KIOSK_USER" -g "$KIOSK_GROUP" \ "$HERE/gnome-kiosk-script" "$HOME_DIR/.local/bin/gnome-kiosk-script" say " $HOME_DIR/.local/bin/gnome-kiosk-script" # The URL is the one per-client value; keep it in its own file so everything # else can stay byte-identical across the fleet. sed "s|^MIRROR_KIOSK_URL=.*|MIRROR_KIOSK_URL=\"$KIOSK_URL\"|" "$HERE/kiosk.conf" \ | install -m 0644 -o "$KIOSK_USER" -g "$KIOSK_GROUP" /dev/stdin \ "$HOME_DIR/.config/mirror/kiosk.conf" say " $HOME_DIR/.config/mirror/kiosk.conf -> $KIOSK_URL" # Without this the GNOME initial-setup wizard claims the screen on first login # and the kiosk never starts. install -m 0644 -o "$KIOSK_USER" -g "$KIOSK_GROUP" /dev/null \ "$HOME_DIR/.config/gnome-initial-setup-done" say "== 4/7 default session ==" ACCT="/var/lib/AccountsService/users/$KIOSK_USER" if [ -f "$ACCT" ]; then set_ini "$ACCT" User Session "$KIOSK_SESSION" else mkdir -p /var/lib/AccountsService/users printf '[User]\nSession=%s\nSystemAccount=false\n' "$KIOSK_SESSION" >"$ACCT" fi # accounts-daemon caches these files and would happily write the old value back. systemctl restart accounts-daemon.service 2>/dev/null || : say " $ACCT Session=$KIOSK_SESSION" say "== 5/7 auto-login ==" if [ ! -d /etc/gdm ]; then say " gdm is not installed — installing it" rpm-ostree install gdm NEED_REBOOT=1 fi if [ -d /etc/gdm ]; then [ -f /etc/gdm/custom.conf ] || : >/etc/gdm/custom.conf set_ini /etc/gdm/custom.conf daemon AutomaticLoginEnable True set_ini /etc/gdm/custom.conf daemon AutomaticLogin "$KIOSK_USER" # A timed login would hand the console to the greeter on its own schedule. set_ini /etc/gdm/custom.conf daemon TimedLoginEnable False say " /etc/gdm/custom.conf: AutomaticLogin=$KIOSK_USER" fi 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 /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 (this path survives reboots):" say " sudo sh /usr/local/share/mirror-kiosk/check-kiosk.sh"