427 lines
22 KiB
Bash
Executable File
427 lines
22 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Smart Home Thin-Client ISO Builder
|
|
# Target: builds a Debian 12 (Bookworm) live ISO on a Debian/Ubuntu build machine
|
|
#
|
|
# Drives `lb config && lb build` over hosts/thin-client/live-build/ to produce the
|
|
# Sway kiosk media-station image described in docs/project-plan.md Phase 11:
|
|
# - greetd autologin straight into a kiosk Sway session (no greeter UI)
|
|
# - wayvnc for interactive remote view/control (the confirmed replacement for RDP)
|
|
# - thinclient-agent (Python, systemd) — HA MQTT-discovery entities + swaymsg control
|
|
# - Firefox kiosk workspace pointed at digest-web (Phase 12)
|
|
# - mpv + mpv-mpris, playerctl, PipeWire audio
|
|
# - Steam Link via Flathub, under Xwayland
|
|
# - wyoming-satellite + openWakeWord (OPT-IN, mic-enabled rooms only)
|
|
#
|
|
# This script is also the single point that keeps configs/ (the human-edited source of
|
|
# truth, reviewed in git) in sync with live-build/config/includes.chroot/ (the
|
|
# generated tree that actually gets baked into the image). Never hand-edit anything
|
|
# under includes.chroot — it is wiped and regenerated on every run.
|
|
#
|
|
# Run as: sudo -E tools/build-thin-client-iso.sh [hostname]
|
|
#
|
|
# Configuration comes from CoreSystemConfig.json — see tools/README.md.
|
|
|
|
set -euo pipefail
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# CONFIGURATION — comes from CoreSystemConfig.json, NOT from this file.
|
|
#
|
|
# There is nothing to edit here any more. Every value below is read from the one
|
|
# config at the repo root, so an address or token can only be wrong in a single
|
|
# place. Change it there and rebuild; see tools/README.md.
|
|
#
|
|
# sudo -E tools/build-thin-client-iso.sh # the only thin-client in the config
|
|
# sudo -E tools/build-thin-client-iso.sh <hostname> # a specific one, if several are defined
|
|
#
|
|
# The build refuses to start if the config is invalid (validate-config.py runs first),
|
|
# so a typo costs seconds rather than a 40-minute build and a reboot.
|
|
# ---------------------------------------------------------------------------
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
# shellcheck source=lib/coreconfig.sh
|
|
source "${SCRIPT_DIR}/lib/coreconfig.sh"
|
|
|
|
core_select_kiosk "thin-client" "${1:-}"
|
|
|
|
# Mapped onto this script's existing variable names, so everything below is unchanged
|
|
# from when these were hand-edited constants.
|
|
DEBIAN_RELEASE="$CORE_DEBIAN_RELEASE"
|
|
KIOSK_USERNAME="$CORE_KIOSK_USERNAME"
|
|
IMAGE_HOSTNAME="$CORE_KIOSK_HOSTNAME"
|
|
KEYBOARD_LAYOUT="$CORE_KEYBOARD_LAYOUT"
|
|
ENABLE_INSTALLER="$CORE_KIOSK_ENABLE_INSTALLER"
|
|
MQTT_BROKER_HOST="$CORE_MQTT_BROKER_HOST"
|
|
MQTT_BROKER_PORT="$CORE_MQTT_BROKER_PORT"
|
|
MQTT_USERNAME="$CORE_MQTT_USERNAME"
|
|
MQTT_PASSWORD="$CORE_MQTT_PASSWORD"
|
|
SSH_AUTHORIZED_KEY="$CORE_SSH_AUTHORIZED_KEY"
|
|
ENABLE_VOICE_SATELLITE="$CORE_KIOSK_VOICE_SATELLITE"
|
|
VOICE_SATELLITE_NAME="$CORE_KIOSK_FRIENDLY_NAME"
|
|
VOICE_WAKE_WORD="$CORE_KIOSK_WAKE_WORD"
|
|
THINCLIENT_NAME="$CORE_KIOSK_FRIENDLY_NAME"
|
|
HA_URL="$CORE_HA_URL"
|
|
DIGEST_WEB_URL="$CORE_DIGEST_WEB_URL"
|
|
ADMIN_WEB_URL="$CORE_ADMIN_WEB_URL"
|
|
GALLERY_SMB_HOST="$CORE_GALLERY_SMB_HOST"
|
|
ENABLE_STEAM_LINK="$CORE_KIOSK_ENABLE_STEAM_LINK"
|
|
ENABLE_GESTURE_CONTROL="$CORE_KIOSK_ENABLE_GESTURE_CONTROL"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Paths — this script now lives in tools/, so the host directory it drives is
|
|
# addressed from the repo root rather than relative to the script.
|
|
# ---------------------------------------------------------------------------
|
|
THIN_CLIENT_DIR="${CORE_REPO_ROOT}/hosts/thin-client"
|
|
CONFIGS_DIR="${THIN_CLIENT_DIR}/configs"
|
|
AGENT_DIR="${THIN_CLIENT_DIR}/agent"
|
|
LIVE_BUILD_DIR="${THIN_CLIENT_DIR}/live-build"
|
|
INCLUDES="${LIVE_BUILD_DIR}/config/includes.chroot"
|
|
PACKAGE_LIST="${LIVE_BUILD_DIR}/config/package-lists/thin-client.list.chroot"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Sanity checks
|
|
# ---------------------------------------------------------------------------
|
|
if [[ $EUID -ne 0 ]]; then
|
|
echo "Warning: not running as root. 'lb build' needs root to bootstrap and chroot,"
|
|
echo " and will fail partway through. Re-run with: sudo $0"
|
|
echo " Continuing anyway so you can at least regenerate includes.chroot..."
|
|
fi
|
|
|
|
if ! grep -qi "debian\|ubuntu" /etc/os-release; then
|
|
echo "Warning: live-build targets a Debian/Ubuntu build host. Proceeding anyway..."
|
|
fi
|
|
|
|
if ! command -v lb &> /dev/null; then
|
|
if [[ $EUID -eq 0 ]]; then
|
|
echo "--- Installing live-build ---"
|
|
apt-get update
|
|
apt-get install -y live-build
|
|
else
|
|
echo "live-build is not installed and this script is not running as root." >&2
|
|
echo " Install it first: sudo apt-get install live-build" >&2
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "--- live-build already installed, skipping ---"
|
|
fi
|
|
|
|
if [[ ! -f "$PACKAGE_LIST" ]]; then
|
|
echo "Missing package list: $PACKAGE_LIST" >&2
|
|
exit 1
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [[ "$ENABLE_VOICE_SATELLITE" == "true" ]]; then
|
|
echo "Note: ENABLE_VOICE_SATELLITE=true — this image is for a MIC-ENABLED room"
|
|
echo " (\"${VOICE_SATELLITE_NAME}\"). Do not flash it to a room without a microphone."
|
|
fi
|
|
|
|
if [[ "$ENABLE_GESTURE_CONTROL" == "true" ]]; then
|
|
echo "Note: ENABLE_GESTURE_CONTROL=true — this image is for a CAMERA-ENABLED room."
|
|
echo " The camera still stays off until \"enabled\" is set to true in"
|
|
echo " /var/lib/thinclient-agent/gesture-config.json on the booted machine."
|
|
fi
|
|
|
|
echo
|
|
echo "=== Smart Home Thin-Client ISO Builder ==="
|
|
echo "Debian release : $DEBIAN_RELEASE"
|
|
echo "Kiosk user : $KIOSK_USERNAME"
|
|
echo "Image hostname : $IMAGE_HOSTNAME"
|
|
echo "MQTT broker : ${MQTT_BROKER_HOST}:${MQTT_BROKER_PORT}"
|
|
echo "Home Assistant : $HA_URL"
|
|
echo "digest-web : $DIGEST_WEB_URL"
|
|
echo "admin-web : $ADMIN_WEB_URL"
|
|
echo "Steam Link : $ENABLE_STEAM_LINK"
|
|
echo "Voice satellite : $ENABLE_VOICE_SATELLITE"
|
|
echo "Gesture control : $ENABLE_GESTURE_CONTROL"
|
|
echo "Keyboard layout : $KEYBOARD_LAYOUT"
|
|
echo
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 1. Regenerate includes.chroot from configs/ and agent/
|
|
# ---------------------------------------------------------------------------
|
|
echo "--- Regenerating $INCLUDES ---"
|
|
rm -rf "$INCLUDES"
|
|
mkdir -p \
|
|
"$INCLUDES/etc/greetd" \
|
|
"$INCLUDES/etc/wayvnc" \
|
|
"$INCLUDES/etc/thinclient-agent" \
|
|
"$INCLUDES/etc/firefox/policies" \
|
|
"$INCLUDES/etc/thinclient-firefox" \
|
|
"$INCLUDES/usr/local/bin" \
|
|
"$INCLUDES/opt/thinclient-agent" \
|
|
"$INCLUDES/opt/gesture-control" \
|
|
"$INCLUDES/home/${KIOSK_USERNAME}/.config/sway" \
|
|
"$INCLUDES/home/${KIOSK_USERNAME}/.config/eww" \
|
|
"$INCLUDES/home/${KIOSK_USERNAME}/.config/mpv" \
|
|
"$INCLUDES/home/${KIOSK_USERNAME}/.ssh"
|
|
|
|
# @KIOSK_USERNAME@ and @KEYBOARD_LAYOUT@ are the only templated tokens in the configs.
|
|
# Everything else the hooks need is read at build time from /etc/thinclient-agent/
|
|
# config.env (written below), which live-build copies in via chroot_local-includes
|
|
# *before* it runs chroot_local-hooks — that ordering is what lets the hooks be plain
|
|
# scripts with no outer-shell variables of their own.
|
|
subst() {
|
|
sed -e "s/@KIOSK_USERNAME@/${KIOSK_USERNAME}/g" \
|
|
-e "s/@KEYBOARD_LAYOUT@/${KEYBOARD_LAYOUT}/g" "$1" > "$2"
|
|
}
|
|
|
|
subst "${CONFIGS_DIR}/greetd/config.toml" "$INCLUDES/etc/greetd/config.toml"
|
|
subst "${CONFIGS_DIR}/wayvnc/config" "$INCLUDES/etc/wayvnc/config"
|
|
subst "${CONFIGS_DIR}/sway/config" "$INCLUDES/home/${KIOSK_USERNAME}/.config/sway/config"
|
|
subst "${AGENT_DIR}/thinclient-agent.service" "$INCLUDES/opt/thinclient-agent/thinclient-agent.service"
|
|
|
|
install -m 0755 "${CONFIGS_DIR}/greetd/kiosk-session" "$INCLUDES/usr/local/bin/kiosk-session"
|
|
install -m 0755 "${CONFIGS_DIR}/sway/digest-browser" "$INCLUDES/usr/local/bin/digest-browser"
|
|
install -m 0755 "${CONFIGS_DIR}/sway/admin-browser" "$INCLUDES/usr/local/bin/admin-browser"
|
|
install -m 0755 "${CONFIGS_DIR}/sway/capture-view" "$INCLUDES/usr/local/bin/capture-view"
|
|
install -m 0755 "${CONFIGS_DIR}/wayvnc/start-wayvnc" "$INCLUDES/usr/local/bin/start-wayvnc"
|
|
install -m 0644 "${CONFIGS_DIR}/mpv/mpv.conf" "$INCLUDES/home/${KIOSK_USERNAME}/.config/mpv/mpv.conf"
|
|
|
|
# Console (VT/TTY) keymap — separate from Sway's own xkb_layout above, since greetd
|
|
# briefly owns the console before Sway starts, and the maintenance shell (foot, under
|
|
# Sway) already gets the Sway layout either way. /etc/default/keyboard is what
|
|
# console-setup and most desktop layers read for the initial layout.
|
|
mkdir -p "$INCLUDES/etc/default"
|
|
cat > "$INCLUDES/etc/default/keyboard" <<EOF
|
|
XKBMODEL="pc105"
|
|
XKBLAYOUT="${KEYBOARD_LAYOUT}"
|
|
XKBVARIANT=""
|
|
XKBOPTIONS=""
|
|
|
|
BACKSPACE="guess"
|
|
EOF
|
|
|
|
# Now-playing widget (eww) — session-scoped files under the kiosk user's own config,
|
|
# plus the two scripts fullscreen-watcher.sh launches/reads.
|
|
install -m 0644 "${CONFIGS_DIR}/eww/eww.yuck" "$INCLUDES/home/${KIOSK_USERNAME}/.config/eww/eww.yuck"
|
|
install -m 0644 "${CONFIGS_DIR}/eww/eww.scss" "$INCLUDES/home/${KIOSK_USERNAME}/.config/eww/eww.scss"
|
|
install -m 0755 "${CONFIGS_DIR}/eww/fullscreen-watcher.sh" "$INCLUDES/usr/local/bin/fullscreen-watcher"
|
|
install -m 0755 "${CONFIGS_DIR}/eww/now-playing-json" "$INCLUDES/usr/local/bin/now-playing-json"
|
|
install -m 0755 "${CONFIGS_DIR}/eww/weather-json" "$INCLUDES/usr/local/bin/weather-json"
|
|
|
|
# Firefox: the browsable-window launcher, plus the shared chrome/prefs template both
|
|
# it and digest-browser copy into their own (separate) profile directories at launch —
|
|
# see configs/firefox/web-browser's comment for why the profiles can't be merged.
|
|
# policies.json is global (not per-profile) and goes straight into includes.chroot.
|
|
install -m 0755 "${CONFIGS_DIR}/firefox/web-browser" "$INCLUDES/usr/local/bin/web-browser"
|
|
install -m 0644 "${CONFIGS_DIR}/firefox/policies.json" "$INCLUDES/etc/firefox/policies/policies.json"
|
|
install -m 0644 "${CONFIGS_DIR}/firefox/userChrome.css" "$INCLUDES/etc/thinclient-firefox/userChrome.css"
|
|
install -m 0644 "${CONFIGS_DIR}/firefox/user.js" "$INCLUDES/etc/thinclient-firefox/user.js"
|
|
|
|
# Persistent audio-output + outbound RDP/VNC target templates — thinclient_agent's
|
|
# runtime_state.py seeds a writable runtime copy from these on first boot (see its
|
|
# module docstring); the files here are read-only and never the ones actually mutated.
|
|
install -m 0644 "${CONFIGS_DIR}/audio/audio-config.json" "$INCLUDES/etc/thinclient-agent/audio-config.json"
|
|
install -m 0644 "${CONFIGS_DIR}/remote-desktop/rdp-vnc.json" "$INCLUDES/etc/thinclient-agent/rdp-vnc.json"
|
|
install -m 0644 "${CONFIGS_DIR}/remote-desktop/remote-desktop-credentials.env.example" \
|
|
"$INCLUDES/etc/thinclient-agent/remote-desktop-credentials.env.example"
|
|
# The real remote-desktop-credentials.env is never baked in (same reasoning as the
|
|
# wayvnc password) — create it by hand on the booted machine from the .example above.
|
|
|
|
# Camera gesture control. Same template/runtime-copy split as the two above, and always
|
|
# copied in regardless of ENABLE_GESTURE_CONTROL: the config template is what documents
|
|
# the feature and is harmless (its "enabled" default is false), and the wrapper's whole
|
|
# job is to no-op cleanly on an image where 1100-gesture-control.hook.chroot did not run.
|
|
install -m 0644 "${CONFIGS_DIR}/gesture-control/gesture-config.json" \
|
|
"$INCLUDES/etc/thinclient-agent/gesture-config.json"
|
|
install -m 0644 "${CONFIGS_DIR}/gesture-control/gesture_pointer.py" \
|
|
"$INCLUDES/opt/gesture-control/gesture_pointer.py"
|
|
install -m 0755 "${CONFIGS_DIR}/gesture-control/gesture-control.sh" \
|
|
"$INCLUDES/usr/local/bin/gesture-control"
|
|
|
|
# Idle-timeout gallery slideshow. The credentials example is copied for reference only
|
|
# (same reasoning as remote-desktop-credentials.env.example above) — the real
|
|
# /etc/thinclient-agent/gallery-credentials is never baked in, and its absence is what
|
|
# makes idle-gallery.sh fall back to the plain blank-the-panel behaviour on an image
|
|
# where nobody has configured a share yet.
|
|
install -m 0755 "${CONFIGS_DIR}/idle-gallery/idle-gallery.sh" \
|
|
"$INCLUDES/usr/local/bin/idle-gallery"
|
|
install -m 0644 "${CONFIGS_DIR}/idle-gallery/gallery-credentials.example" \
|
|
"$INCLUDES/etc/thinclient-agent/gallery-credentials.example"
|
|
|
|
cp -r "${AGENT_DIR}/thinclient_agent" "$INCLUDES/opt/thinclient-agent/"
|
|
install -m 0644 "${AGENT_DIR}/requirements.txt" "$INCLUDES/opt/thinclient-agent/requirements.txt"
|
|
find "$INCLUDES/opt/thinclient-agent" -name '__pycache__' -type d -prune -exec rm -rf {} +
|
|
|
|
if [[ -n "$SSH_AUTHORIZED_KEY" ]]; then
|
|
echo "$SSH_AUTHORIZED_KEY" > "$INCLUDES/home/${KIOSK_USERNAME}/.ssh/authorized_keys"
|
|
chmod 600 "$INCLUDES/home/${KIOSK_USERNAME}/.ssh/authorized_keys"
|
|
echo " Baked an SSH authorized_keys entry for ${KIOSK_USERNAME}."
|
|
else
|
|
echo " No SSH_AUTHORIZED_KEY set — SSH admin access will not be possible on this image."
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 2. Runtime config, read by thinclient-agent, the sway session wrapper, and hooks
|
|
# ---------------------------------------------------------------------------
|
|
echo "--- Writing /etc/thinclient-agent/config.env into includes.chroot ---"
|
|
cat > "$INCLUDES/etc/thinclient-agent/config.env" <<EOF
|
|
# Generated by tools/build-thin-client-iso.sh — do not hand-edit
|
|
# here; change CoreSystemConfig.json at the repo root and rebuild.
|
|
KIOSK_USERNAME=${KIOSK_USERNAME}
|
|
THINCLIENT_NAME=${THINCLIENT_NAME}
|
|
|
|
MQTT_BROKER_HOST=${MQTT_BROKER_HOST}
|
|
MQTT_BROKER_PORT=${MQTT_BROKER_PORT}
|
|
MQTT_USERNAME=${MQTT_USERNAME}
|
|
MQTT_PASSWORD=${MQTT_PASSWORD}
|
|
|
|
HA_URL=${HA_URL}
|
|
DIGEST_WEB_URL=${DIGEST_WEB_URL}
|
|
ADMIN_WEB_URL=${ADMIN_WEB_URL}
|
|
GALLERY_SMB_HOST=${GALLERY_SMB_HOST}
|
|
|
|
ENABLE_STEAM_LINK=${ENABLE_STEAM_LINK}
|
|
ENABLE_VOICE_SATELLITE=${ENABLE_VOICE_SATELLITE}
|
|
VOICE_SATELLITE_NAME=${VOICE_SATELLITE_NAME}
|
|
VOICE_WAKE_WORD=${VOICE_WAKE_WORD}
|
|
ENABLE_GESTURE_CONTROL=${ENABLE_GESTURE_CONTROL}
|
|
EOF
|
|
chmod 0644 "$INCLUDES/etc/thinclient-agent/config.env"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 3. lb config
|
|
# ---------------------------------------------------------------------------
|
|
cd "$LIVE_BUILD_DIR"
|
|
|
|
chmod +x config/hooks/normal/*.hook.chroot
|
|
|
|
if [[ -e .build ]]; then
|
|
echo "--- Previous build found, running 'lb clean' (package cache is kept) ---"
|
|
lb clean
|
|
fi
|
|
|
|
INSTALLER_MODE="none"
|
|
if [[ "$ENABLE_INSTALLER" == "true" ]]; then
|
|
INSTALLER_MODE="live"
|
|
fi
|
|
|
|
echo "--- Running lb config ---"
|
|
# live-build auto-discovers config/package-lists/*.list.chroot and
|
|
# config/hooks/normal/*.hook.chroot; there is no flag to point at them individually.
|
|
# thin-client.list.chroot and the 0100-0700 hooks are picked up because of where they
|
|
# sit, which is why the sanity check above asserts the list file exists.
|
|
lb config \
|
|
--distribution "$DEBIAN_RELEASE" \
|
|
--architectures amd64 \
|
|
--linux-flavours amd64 \
|
|
--archive-areas "main contrib non-free-firmware" \
|
|
--binary-images iso-hybrid \
|
|
--debian-installer "$INSTALLER_MODE" \
|
|
--iso-application "SmartestHome Thin Client" \
|
|
--iso-publisher "SmartestHome" \
|
|
--iso-volume "smarthome-thin-client" \
|
|
--memtest none \
|
|
--bootappend-live "boot=live components quiet splash noautologin username=${KIOSK_USERNAME} hostname=${IMAGE_HOSTNAME}"
|
|
|
|
# noautologin: live-config would otherwise autologin its own account on tty1 and fight
|
|
# greetd for the VT (0200-greetd.hook.chroot masks getty@tty1 as a belt-and-braces
|
|
# second line of defence).
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 4. lb build
|
|
# ---------------------------------------------------------------------------
|
|
echo "--- Running lb build (this takes a while and needs network) ---"
|
|
lb build
|
|
|
|
ISO_PATH="$(find "$LIVE_BUILD_DIR" -maxdepth 1 -name '*.iso' -print -quit)"
|
|
ISO_PATH="${ISO_PATH:-${LIVE_BUILD_DIR}/live-image-amd64.hybrid.iso}"
|
|
# Published under this kiosk's own hostname — live-build reuses one filename per host
|
|
# tree, so two of the same kiosk type would otherwise overwrite each other.
|
|
ISO_PATH="$(core_publish_image "$ISO_PATH" "thin-client" "$IMAGE_HOSTNAME")"
|
|
|
|
echo
|
|
echo "=== Done ==="
|
|
echo "ISO written to:"
|
|
echo " ${ISO_PATH}"
|
|
echo
|
|
echo "What's in it:"
|
|
echo " greetd : autologin as '${KIOSK_USERNAME}' straight into Sway on vt1"
|
|
echo " Sway : workspaces 1:web / 2:digest / 3:media / 4:admin / 5:capture, no bars, no lock screen"
|
|
echo " wayvnc : 0.0.0.0:5900, auth REQUIRED, refuses to start without a password"
|
|
echo " thinclient-agent: system service, MQTT ${MQTT_BROKER_HOST}:${MQTT_BROKER_PORT}"
|
|
echo " Firefox kiosk : ${DIGEST_WEB_URL}/full.html?detail_level=full"
|
|
echo " Firefox (general): minimal chrome, uBlock Origin + SponsorBlock preinstalled"
|
|
echo " Now-playing : eww widget, hidden while anything visual is on screen"
|
|
echo " Weather overlay : eww widget over the idle-gallery slideshow (clock always,"
|
|
echo " weather once smarthome/weather/current is published — see README)"
|
|
echo " Audio output : HA select, persisted to audio-config.json"
|
|
echo " Remote desktop : Remmina (RDP+VNC) out to other machines, configured via rdp-vnc.json"
|
|
echo " HA input remote : type text / move+click the pointer via ydotool, no VNC needed"
|
|
echo " Keyboard layout : ${KEYBOARD_LAYOUT} (console + Sway; rebuild with a different"
|
|
echo " KEYBOARD_LAYOUT for a non-German room)"
|
|
echo " Maintenance shell: Super+Shift+Ctrl+M opens a floating terminal locally"
|
|
if [[ "$ENABLE_STEAM_LINK" == "true" ]]; then
|
|
echo " Steam Link : Flathub flatpak, launched under Xwayland"
|
|
fi
|
|
if [[ "$ENABLE_VOICE_SATELLITE" == "true" ]]; then
|
|
echo " Voice satellite : wyoming-satellite + openWakeWord ('${VOICE_WAKE_WORD}'), room '${VOICE_SATELLITE_NAME}'"
|
|
else
|
|
echo " Voice satellite : NOT installed (ENABLE_VOICE_SATELLITE=false)"
|
|
fi
|
|
if [[ "$ENABLE_GESTURE_CONTROL" == "true" ]]; then
|
|
echo " Gesture control : MediaPipe hand tracking installed — CAMERA STILL OFF until"
|
|
echo " 'enabled' is set in /var/lib/thinclient-agent/gesture-config.json"
|
|
else
|
|
echo " Gesture control : NOT installed (ENABLE_GESTURE_CONTROL=false)"
|
|
fi
|
|
echo " Idle slideshow : cycles photos from //${GALLERY_SMB_HOST}/gallery after 15min idle,"
|
|
echo " falls back to blanking the panel until gallery-credentials is set up"
|
|
echo
|
|
echo "Next steps:"
|
|
echo " 1. Write the ISO to a USB stick:"
|
|
echo " sudo dd if=${ISO_PATH} of=/dev/sdX bs=4M status=progress oflag=sync"
|
|
echo " (double-check /dev/sdX with 'lsblk' first — dd will happily eat the wrong disk)"
|
|
echo " 2. Boot the target machine from it. No thin-client hardware has been chosen yet"
|
|
echo " (project-plan §4 open decision #7), so confirm the GPU/audio work at all"
|
|
echo " before treating this image as validated."
|
|
echo " 3. Confirm greetd autologin lands in Sway: you should see the dark background and"
|
|
echo " the Firefox kiosk window on workspace 2:digest, with no login prompt."
|
|
echo " 4. SET THE WAYVNC PASSWORD — wayvnc will NOT be running until you do:"
|
|
echo " sudo sh -c 'openssl rand -base64 24 > /etc/wayvnc/wayvnc-password'"
|
|
echo " sudo chmod 600 /etc/wayvnc/wayvnc-password"
|
|
echo " sudo chown ${KIOSK_USERNAME}:${KIOSK_USERNAME} /etc/wayvnc/wayvnc-password"
|
|
echo " swaymsg reload"
|
|
echo " Then connect a VNC client to <thin-client-ip>:5900 with username '${KIOSK_USERNAME}'."
|
|
echo " 5. Confirm thinclient-agent connected and registered:"
|
|
echo " systemctl status thinclient-agent"
|
|
echo " In Home Assistant, a '${THINCLIENT_NAME}' device should appear under the MQTT"
|
|
echo " integration with: Show digest canvas (button), Digest detail level (select),"
|
|
echo " Show admin canvas (button), Workspace (select), Launch Firefox / Launch Steam"
|
|
echo " Link (buttons), Volume (number), Playback state (sensor), and"
|
|
echo " play/pause/next/prev buttons."
|
|
echo " 6. Start something in mpv and confirm Playback state follows it (this goes through"
|
|
echo " mpv-mpris -> playerctl -> the agent's MPRIS bridge)."
|
|
if [[ "$ENABLE_VOICE_SATELLITE" == "true" ]]; then
|
|
echo " 7. Say the wake word ('${VOICE_WAKE_WORD}') and confirm HA's existing Phase 3"
|
|
echo " faster-whisper/Piper Assist pipeline picks it up:"
|
|
echo " journalctl -u wyoming-satellite -f"
|
|
echo " The mic/speaker device names in that unit are still 'default' — set the real"
|
|
echo " 'arecord -L' device once the hardware is known."
|
|
fi
|
|
if [[ "$ENABLE_GESTURE_CONTROL" == "true" ]]; then
|
|
echo " 8. Gesture control is installed but the camera is OFF. To turn it on, on the"
|
|
echo " booted machine — and only in a room where everyone present knows a camera is"
|
|
echo " watching:"
|
|
echo " v4l2-ctl --list-devices # find the right /dev/videoN"
|
|
echo " sudo \$EDITOR /var/lib/thinclient-agent/gesture-config.json"
|
|
echo " swaymsg reload"
|
|
echo " Then watch it come up and check what it costs this hardware (it is a sway"
|
|
echo " exec, so its log lands in greetd's journal alongside sway's own):"
|
|
echo " journalctl -u greetd -f ; top -p \"\$(pgrep -f gesture_pointer.py)\""
|
|
fi
|
|
echo " 9. Idle photo slideshow: create /etc/thinclient-agent/gallery-credentials on the"
|
|
echo " booted machine (chmod 600) from the .example next to it, matching whatever"
|
|
echo " GALLERY_SMB_USERNAME/GALLERY_SMB_PASSWORD you set in"
|
|
echo " tools/setup-container-host.sh. Until that file exists,"
|
|
echo " idle timeout just blanks the panel — the old, pre-slideshow behaviour."
|
|
echo
|
|
echo "Then pull the power on the container host and re-check: the kiosk session must"
|
|
echo "still come up and play local media (project-plan Phase 11.10). thinclient-agent"
|
|
echo "connects asynchronously and will simply keep retrying."
|
|
echo
|
|
echo "Rebuilding later: edit hosts/thin-client/configs/* or agent/*, then re-run this"
|
|
echo "script — includes.chroot is regenerated from them every time."
|