#!/usr/bin/env bash
#
# Smart Home Steam-TV-Box ISO Builder
# Target: builds a Debian live ISO on a Debian/Ubuntu build machine
#
# Drives `lb config && lb build` over hosts/steam-tv-box/live-build/ to produce the
# living-room gaming + media image:
#   - greetd autologin straight into a kiosk Sway session (no greeter UI)
#   - boots into STEAM BIG PICTURE and nothing else
#   - native, local gaming: steam-installer + i386 multiarch + the full Mesa/Vulkan
#     stack (this box has a real GPU; it is NOT the thin client's Steam Link streaming)
#   - Prism Launcher (Minecraft), registered as a non-Steam game so it runs *through*
#     Steam with Steam Input and the Steam Controller API live
#   - a media session — Firefox (uBlock Origin + SponsorBlock), Spotify, mpv — that is
#     launched LAZILY, the first time somebody leaves Big Picture
#   - steamtv-agent (Python, systemd) — HA MQTT-discovery entities + swaymsg control
#   - wayvnc for administration (password mandatory, fails closed)
#
# 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. The same now goes
# for live-build/config/preseed.cfg, which is generated from configs/installer/.
#
# Run as: sudo -E tools/build-steam-tv-box-iso.sh [hostname]
#
# Configuration comes from CoreSystemConfig.json — see tools/README.md.

set -euo pipefail

# ---------------------------------------------------------------------------
# CONFIGURATION — comes from CoreSystemConfig.json, NOT from this file.
#
#   sudo -E tools/build-steam-tv-box-iso.sh                 # the only steam-tv-box
#   sudo -E tools/build-steam-tv-box-iso.sh <hostname>      # a specific one, if several
# ---------------------------------------------------------------------------
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib/coreconfig.sh
source "${SCRIPT_DIR}/lib/coreconfig.sh"

core_select_kiosk "steam-tv-box" "${1:-}"

# CORE_KIOSK_DEBIAN_RELEASE is the per-kiosk `debian_release` override, falling back to
# household.debian_release when the kiosk does not set one. It exists mostly for this
# image: the household default is bookworm, whose Mesa (22.3) predates most of the
# driver work current titles depend on. Every other host here is a browser and a
# Python agent and does not care what Mesa it has; this one is the only machine in the
# project that renders anything demanding, and it is also the only one where being a
# release behind cannot be fixed later by editing a config file. Set
# "debian_release": "trixie" on this kiosk — see CoreSystemConfig.json.template.
DEBIAN_RELEASE="$CORE_KIOSK_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"
STEAMTV_NAME="$CORE_KIOSK_FRIENDLY_NAME"
# The HA area this device physically sits in, from the kiosk's `room` in
# CoreSystemConfig.json. The agent publishes it as suggested_area so HA files the
# device in the right room by itself — see docs/rooms-and-endpoints.md.
STEAMTV_ROOM="${CORE_KIOSK_ROOM:-}"
HA_URL="$CORE_HA_URL"
# Which GPU is in this machine: amd | intel | nvidia. Read by 0300-steam.hook.chroot
# (to install the NVIDIA driver, or deliberately not to) and by kiosk-session (the
# wlroots cursor workaround). Guessing this wrong on an AMD box by installing the
# NVIDIA driver actively breaks it, which is why it is configuration and not detection.
GPU_VENDOR="${CORE_KIOSK_GPU_VENDOR:-amd}"
ENABLE_CEC="${CORE_KIOSK_ENABLE_CEC:-true}"

# ---------------------------------------------------------------------------
# Paths
# ---------------------------------------------------------------------------
HOST_DIR="${CORE_REPO_ROOT}/hosts/steam-tv-box"
CONFIGS_DIR="${HOST_DIR}/configs"
AGENT_DIR="${HOST_DIR}/agent"
LIVE_BUILD_DIR="${HOST_DIR}/live-build"
INCLUDES="${LIVE_BUILD_DIR}/config/includes.chroot"
PACKAGE_LIST="${LIVE_BUILD_DIR}/config/package-lists/steam-tv-box.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

case "$GPU_VENDOR" in
  amd|intel|nvidia) ;;
  *)
    echo "gpu_vendor is '${GPU_VENDOR}' — must be one of: amd, intel, nvidia." >&2
    echo "  Fix it on this kiosk in CoreSystemConfig.json and re-run." >&2
    exit 1
    ;;
esac

if [[ "$DEBIAN_RELEASE" == "bookworm" ]]; then
  echo "Note: building this image against bookworm. Its Mesa is 22.3, which is old for"
  echo "  a machine whose job is running games locally — expect missing Vulkan features"
  echo "  and poor performance in anything recent. Set \"debian_release\": \"trixie\" on"
  echo "  this kiosk in CoreSystemConfig.json unless you have a specific reason not to."
fi

echo
echo "=== Smart Home Steam-TV-Box ISO Builder ==="
echo "Debian release   : $DEBIAN_RELEASE"
echo "Kiosk user       : $KIOSK_USERNAME"
echo "Image hostname   : $IMAGE_HOSTNAME"
echo "GPU vendor       : $GPU_VENDOR"
echo "MQTT broker      : ${MQTT_BROKER_HOST}:${MQTT_BROKER_PORT}"
echo "Home Assistant   : $HA_URL"
echo "HDMI-CEC         : $ENABLE_CEC"
echo "Debian installer : $ENABLE_INSTALLER"
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/steamtv-agent" \
  "$INCLUDES/etc/firefox/policies" \
  "$INCLUDES/etc/steamtv-firefox" \
  "$INCLUDES/usr/local/bin" \
  "$INCLUDES/opt/steamtv-agent" \
  "$INCLUDES/home/${KIOSK_USERNAME}/.config/sway" \
  "$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/steamtv-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}/steamtv-agent.service" "$INCLUDES/opt/steamtv-agent/steamtv-agent.service"

install -m 0755 "${CONFIGS_DIR}/greetd/kiosk-session"   "$INCLUDES/usr/local/bin/kiosk-session"
install -m 0755 "${CONFIGS_DIR}/wayvnc/start-wayvnc"    "$INCLUDES/usr/local/bin/start-wayvnc"
# Bound to the remote's power/sleep buttons — see the Sway config's remote-control
# section for why those turn the display off rather than the machine.
install -m 0755 "${CONFIGS_DIR}/sway/display-toggle"    "$INCLUDES/usr/local/bin/display-toggle"

# The session: the two halves of the leave-Big-Picture detection, the launchers they
# call, and the Steam shortcut registration that puts Prism inside Steam Input.
install -m 0755 "${CONFIGS_DIR}/session/steam-session"       "$INCLUDES/usr/local/bin/steam-session"
install -m 0755 "${CONFIGS_DIR}/session/steam-big-picture"   "$INCLUDES/usr/local/bin/steam-big-picture"
install -m 0755 "${CONFIGS_DIR}/session/steam-shortcut-prism" "$INCLUDES/usr/local/bin/steam-shortcut-prism"
install -m 0755 "${CONFIGS_DIR}/session/session-watcher"     "$INCLUDES/usr/local/bin/session-watcher"
install -m 0755 "${CONFIGS_DIR}/session/media-session"       "$INCLUDES/usr/local/bin/media-session"
install -m 0755 "${CONFIGS_DIR}/session/media-player"        "$INCLUDES/usr/local/bin/media-player"
install -m 0755 "${CONFIGS_DIR}/session/spotify-launch"      "$INCLUDES/usr/local/bin/spotify-launch"
install -m 0755 "${CONFIGS_DIR}/session/prism-launch"        "$INCLUDES/usr/local/bin/prism-launch"

# fleet-bootstrap: fetch this machine's published monitoring script and run it. Opt-in —
# with no /etc/fleet-bootstrap.conf it exits 0 and does nothing.
install -m 0755 "${CORE_REPO_ROOT}/tools/fleet-bootstrap.sh" "$INCLUDES/usr/local/bin/fleet-bootstrap"

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.
mkdir -p "$INCLUDES/etc/default"
cat > "$INCLUDES/etc/default/keyboard" <<EOF
XKBMODEL="pc105"
XKBLAYOUT="${KEYBOARD_LAYOUT}"
XKBVARIANT=""
XKBOPTIONS=""

BACKSPACE="guess"
EOF

# Firefox: the browsing window's launcher, plus the chrome/prefs template it copies
# into its profile at launch. policies.json (uBlock Origin, SponsorBlock — the
# "browser with ublock origin" half of the requirement) is global, not per-profile.
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/steamtv-firefox/userChrome.css"
install -m 0644 "${CONFIGS_DIR}/firefox/user.js"        "$INCLUDES/etc/steamtv-firefox/user.js"

# Persistent audio-output template — steamtv_agent's runtime_state.py seeds a writable
# runtime copy from this on first boot (see its module docstring); the file here is
# read-only and never the one actually mutated.
install -m 0644 "${CONFIGS_DIR}/audio/audio-config.json" "$INCLUDES/etc/steamtv-agent/audio-config.json"

cp -r "${AGENT_DIR}/steamtv_agent" "$INCLUDES/opt/steamtv-agent/"
install -m 0644 "${AGENT_DIR}/requirements.txt" "$INCLUDES/opt/steamtv-agent/requirements.txt"
find "$INCLUDES/opt/steamtv-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 steamtv-agent, the sway session wrapper, and the hooks
# ---------------------------------------------------------------------------
echo "--- Writing /etc/steamtv-agent/config.env into includes.chroot ---"
cat > "$INCLUDES/etc/steamtv-agent/config.env" <<EOF
# Generated by tools/build-steam-tv-box-iso.sh — do not hand-edit
# here; change CoreSystemConfig.json at the repo root and rebuild.
KIOSK_USERNAME=${KIOSK_USERNAME}
STEAMTV_NAME=${STEAMTV_NAME}
STEAMTV_ROOM=${STEAMTV_ROOM}

MQTT_BROKER_HOST=${MQTT_BROKER_HOST}
MQTT_BROKER_PORT=${MQTT_BROKER_PORT}
MQTT_USERNAME=${MQTT_USERNAME}
MQTT_PASSWORD=${MQTT_PASSWORD}

HA_URL=${HA_URL}

GPU_VENDOR=${GPU_VENDOR}
ENABLE_CEC=${ENABLE_CEC}
EOF
chmod 0644 "$INCLUDES/etc/steamtv-agent/config.env"

# ---------------------------------------------------------------------------
# 3. Generated preseed (only consulted when the installer is enabled)
# ---------------------------------------------------------------------------
sed -e "s/@KEYBOARD_LAYOUT@/${KEYBOARD_LAYOUT}/g" \
  "${CONFIGS_DIR}/installer/preseed.cfg" > "${LIVE_BUILD_DIR}/config/preseed.cfg"

# ---------------------------------------------------------------------------
# 4. 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.
#
# --archive-areas: contrib is REQUIRED — steam-installer lives there and
# 0300-steam.hook.chroot fails without it, producing an image with no Steam on it.
# non-free is required for the NVIDIA driver (and is harmless on an AMD/Intel box,
# since nothing from it is installed unless gpu_vendor says so).
lb config \
  --distribution "$DEBIAN_RELEASE" \
  --architectures amd64 \
  --linux-flavours amd64 \
  --archive-areas "main contrib non-free non-free-firmware" \
  --binary-images iso-hybrid \
  --debian-installer "$INSTALLER_MODE" \
  --iso-application "SmartestHome Steam TV Box" \
  --iso-publisher "SmartestHome" \
  --iso-volume "smarthome-steam-tv-box" \
  --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).

# ---------------------------------------------------------------------------
# 5. lb build
# ---------------------------------------------------------------------------
echo "--- Running lb build (this takes a while and needs network) ---"
echo "    Longer than the other images: the i386 multiarch stack and Steam's"
echo "    dependency chain are a few hundred extra packages."
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}"
ISO_PATH="$(core_publish_image "$ISO_PATH" "steam-tv-box" "$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 "  Boots to        : Steam Big Picture, and nothing else"
echo "  Steam           : NATIVE (steam-installer + i386 multiarch) — games run on this"
echo "                    machine. This is not the thin client's Steam Link streaming."
echo "  GPU stack       : Mesa/Vulkan (${GPU_VENDOR}), 64- and 32-bit"
echo "  Prism Launcher  : Flathub, registered as a non-Steam game so it launches THROUGH"
echo "                    Steam with Steam Input / the Steam Controller API active"
echo "  Media session   : Firefox (uBlock Origin + SponsorBlock), Spotify, mpv —"
echo "                    started the FIRST TIME you leave Big Picture, not at boot"
echo "  Sway            : workspaces 1:steam / 2:games / 3:web / 4:media / 5:music"
echo "  wayvnc          : 0.0.0.0:5900, auth REQUIRED, refuses to start without a password"
echo "  steamtv-agent   : system service, MQTT ${MQTT_BROKER_HOST}:${MQTT_BROKER_PORT}"
echo "  Audio output    : HA select, persisted to audio-config.json"
echo "  Keyboard layout : ${KEYBOARD_LAYOUT} (console + Sway)"
echo "  Maintenance shell: Super+Shift+Ctrl+M opens a floating terminal locally"
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)"
if [[ "$ENABLE_INSTALLER" != "true" ]]; then
  echo "  2. NOTE: enable_installer is false, so this boots live — games would live in RAM"
  echo "     and vanish on reboot. For a real gaming box set \"enable_installer\": true on"
  echo "     this kiosk in CoreSystemConfig.json and rebuild, then install to the disk."
else
  echo "  2. Boot the target machine and run the installer. Give /home its own large"
  echo "     partition (or mount a games disk at /home/${KIOSK_USERNAME}/Games) — a Steam"
  echo "     library and a few modpacks are hundreds of gigabytes."
fi
echo "  3. Confirm it lands in Big Picture with no login prompt, and that a gamepad is"
echo "     recognised (that is steam-devices' udev rules doing their job)."
echo "  4. Check the box is actually accelerated before blaming any game — from the"
echo "     maintenance shell (Super+Shift+Ctrl+M):"
echo "       vulkaninfo | head -n 20     # should name your GPU, not llvmpipe"
echo "       glxinfo -B                  # same"
echo "  5. 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 "  6. Log into Steam on the TV. Until you do, Prism cannot be registered as a"
echo "     non-Steam game (there is no user profile to register it in) and will fall"
echo "     back to launching directly, without Steam Input. After the first login,"
echo "     restart the session (or reboot) — steam-session registers the shortcut on"
echo "     the way in, before Steam starts, because Steam overwrites shortcuts.vdf on"
echo "     exit. Then confirm 'Prism Launcher' is in the Big Picture library and that"
echo "     the pad works inside Minecraft."
echo "  7. Leave Big Picture and confirm the media session appears: Firefox on 3:web,"
echo "     mpv on 4:media, Spotify on 5:music. Then go back into a game and confirm"
echo "     they are NOT killed (that is deliberate — see configs/session/media-session)."
echo "  8. Confirm steamtv-agent connected and registered:"
echo "       systemctl status steamtv-agent"
echo "     In Home Assistant, a '${STEAMTV_NAME}' device should appear under the MQTT"
echo "     integration with: Session (sensor), Mode (select), Screen (select), Launch"
echo "     buttons for Steam/Prism/browser/player/Spotify, Stop media apps (button),"
echo "     Audio output (select), Display (switch), Volume (number), Playback state"
echo "     (sensor) and the transport buttons."
echo "  9. Set the audio output. On a TV box the right sink is usually HDMI, and if the"
echo "     set was off at boot WirePlumber may have defaulted to a headphone jack with"
echo "     nothing in it — the classic 'the game has no sound' report. Pick it in HA's"
echo "     'Audio output' select; the choice persists across reboots."
echo
echo "Then pull the power on the container host and re-check: the box must still boot"
echo "into Big Picture and play a game. steamtv-agent connects asynchronously and will"
echo "simply keep retrying."
echo
echo "Rebuilding later: edit hosts/steam-tv-box/configs/* or agent/*, then re-run this"
echo "script — includes.chroot is regenerated from them every time."
