Files
mirror/docs/kiosk-silverblue/install-kiosk.sh
T
ducoterra d43eb407f8
Build and Push Containers / test (push) Successful in 35s
Build and Push Containers / build-and-push-app (push) Successful in 11s
phase: 03_audio_share
**Phase 03 final verification pass — complete, 100% green.**

- Verified all 4 tasks done in `complete/03_audio_share/`; confirmed implementation in `watch.js` (one composed `MediaStream` per tile, staged-only `setMuted()` policy, `data-audio` state), `share.js` (precise `NotAllowedError` rethrow, 3 honest `#stat-audio` states, mid-share audio republish), conftest flags, and the new `tests/e2e/test_audio_share.py` (11 tests, all **passed** — none skipped).
- **Criteria:** (1) viewer inbound audio `bytesReceived` strictly increases + composed stream 1 video/1 audio track while frames still decode — **passed**; (2) staged-only audio via keyboard `m`, mouse button, and `unstage()` re-mute — **passed**; (3) `shared`/`silent`/`not offered` + mid-share audio loss updates panel, roster and tiles — **passed**; (4) cancelled picker reports cancellation, single attempt — **passed**; (5) SECURITY.md §4 Audio, README platform notes, kiosk doc all describe shipped behaviour incl. Linux/PipeWire reality — **verified**; (6) full gate — **passed** (below).
- **Gates:** `uv run ruff check . && uv run ruff format --check .` → clean; `uv run pyright` → 0 errors; `uv run pytest` → 245 passed, 4 skipped (only the `MIRROR_REALDISPLAY`-gated X11 tests); `MIRROR_REALDISPLAY=1 uv run pytest -m realdisplay` → 3 passed, 1 skipped (real-capture audio: documented skip naming the exact host/flag reason, per task 04).
- **Docs:** README.md reviewed in full against code (env table, CLI flags, routes, kiosk key table, quickstart, audio notes) — consistent, no changes needed. AGENTS.md — all commands/paths/coverage claims verified accurate, no stale statements, no changes needed.
- **Defect fixed (pre-existing):** `docs/kiosk-silverblue.md` + `install-kiosk.sh`, `kiosk.conf`, `gnome-kiosk-script` used `mirror.reeselink.com` while README/SECURITY.md use `mirror.reeseapps.com` (the deployment domain, consistent with the fleet); corrected 7 occurrences in 4 files.
- No live-infrastructure changes; realdisplay suite ran on private Xvfb only.
- **Next pending phase:** `04_reachable_share_cta`.
2026-11-01 22:40:30 -05:00

192 lines
8.6 KiB
Bash
Executable File

#!/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=<value> 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"