#!/usr/bin/env bash
# appliance-pi.sh - turn a vanilla Raspberry Pi OS Lite (Bookworm, 64-bit) OR a
# generic Debian/Ubuntu x86 box into a locked single-app kiosk running
# kraken-player. Run as root, AFTER install.sh has placed the binary + config:
#
#   sudo bash appliance-pi.sh              # kiosk user defaults to the sudo user
#   sudo KIOSK_USER=signage bash appliance-pi.sh
#
# The X + autologin + relaunch mechanism is generic (getty autologin -> startx ->
# .xinitrc loop); the only Pi-specific step (dphys-swapfile) is command-guarded and
# the chromium package name falls back, so it works on x86 Debian/Ubuntu unchanged.
#
# What it configures (all idempotent - safe to re-run):
#   - console autologin for the kiosk user on tty1
#   - a minimal X session auto-started from the user's profile (no desktop)
#   - .xinitrc: disable DPMS/blank/screensaver, hide the cursor, run the player in
#     a relaunch loop (so a crash or RESTART_PLAYER comes back up)
#   - disable swap (24/7 sign + SD-card wear)
#   - a first-boot oneshot that regenerates /etc/machine-id + SSH host keys, so
#     SD-card CLONES don't collide on deviceId (the player derives deviceId from
#     /etc/machine-id)
#   - disable the kraken-player systemd service: the appliance runs the player from
#     the X session (so Chromium has a DISPLAY); the service would be a 2nd, blind
#     instance. RESTART_PLAYER falls back to a clean exit + the loop relaunches.
#
# NOTE: this is OS-level automation that cannot be exercised in CI. Validate on
# real Pi hardware before fleet rollout. Pi OS Lite (no desktop) is the target;
# on Pi OS Desktop, lightdm autologin would be used instead.
set -euo pipefail

if [[ "${EUID}" -ne 0 ]]; then
  echo "Run as root: sudo $0" >&2
  exit 1
fi

# Default to the sudo-invoking user (a generic x86 box has no `pi` account).
KIOSK_USER="${KIOSK_USER:-${SUDO_USER:-pi}}"
PLAYER_BIN="${PLAYER_BIN:-/var/lib/kraken-player/bin/kraken-player}"

if ! id "${KIOSK_USER}" >/dev/null 2>&1; then
  echo "Kiosk user '${KIOSK_USER}' does not exist. Set KIOSK_USER=<name> and re-run." >&2
  exit 1
fi
USER_HOME="$(getent passwd "${KIOSK_USER}" | cut -d: -f6)"
if [[ -z "${USER_HOME}" || ! -d "${USER_HOME}" ]]; then
  echo "Cannot resolve home dir for ${KIOSK_USER}." >&2
  exit 1
fi

echo "==> Installing kiosk packages (X, openbox, diagnostics, chromium)..."
# A kiosk without X or Chromium can only produce a black screen. Package
# installation is retried across both Raspberry Pi package names, then every
# runtime command is verified BEFORE autologin/default boot is changed.
export DEBIAN_FRONTEND=noninteractive
apt-get update -y >/dev/null 2>&1 || echo "  (apt update failed - checking already-installed packages)"
apt-get install -y --no-install-recommends \
  xserver-xorg xinit x11-xserver-utils x11-utils openbox unclutter chromium-browser \
  >/dev/null 2>&1 || \
  apt-get install -y --no-install-recommends \
  xserver-xorg xinit x11-xserver-utils x11-utils openbox unclutter chromium \
  >/dev/null 2>&1 || true

missing=()
for command_name in startx xset xsetroot xmessage openbox-session unclutter; do
  command -v "${command_name}" >/dev/null 2>&1 || missing+=("${command_name}")
done
if ! command -v chromium-browser >/dev/null 2>&1 && ! command -v chromium >/dev/null 2>&1; then
  missing+=("chromium")
fi
if [[ ! -x "${PLAYER_BIN}" ]]; then
  missing+=("${PLAYER_BIN}")
fi
if (( ${#missing[@]} > 0 )); then
  echo "ERROR: kiosk prerequisites are missing: ${missing[*]}" >&2
  echo "The Pi boot configuration was NOT changed. Connect it to the internet, run install.sh, then run appliance-pi.sh again." >&2
  exit 1
fi

if command -v chromium-browser >/dev/null 2>&1; then
  CHROMIUM_COMMAND="chromium-browser"
else
  CHROMIUM_COMMAND="chromium"
fi
if ! "${CHROMIUM_COMMAND}" --version >/dev/null 2>&1; then
  echo "ERROR: ${CHROMIUM_COMMAND} is installed but cannot start." >&2
  echo "The Pi boot configuration was NOT changed." >&2
  exit 1
fi

if ! "${PLAYER_BIN}" --version >/dev/null 2>&1; then
  echo "ERROR: the Whitecore Player binary cannot run on this computer ($(uname -m))." >&2
  echo "Raspberry Pi requires 64-bit Raspberry Pi OS and the ARM64 player package." >&2
  echo "The Pi boot configuration was NOT changed." >&2
  exit 1
fi
if [[ ! -r /var/lib/kraken-player/config.json ]] || \
   ! runuser -u "${KIOSK_USER}" -- test -r /var/lib/kraken-player/config.json; then
  echo "ERROR: /var/lib/kraken-player/config.json is missing or cannot be read by ${KIOSK_USER}." >&2
  echo "Run install.sh again, then run appliance-pi.sh again. The Pi boot configuration was NOT changed." >&2
  exit 1
fi

# Repair the stale package-name value written by older installers. The config
# produced by Whitecore is formatted one field per line, so this targeted
# replacement preserves the device identity and all pairing fields.
sed -i -E "s#(\"chromiumPath\"[[:space:]]*:[[:space:]]*)\"[^\"]*\"#\1\"${CHROMIUM_COMMAND}\"#" \
  /var/lib/kraken-player/config.json
chown "${KIOSK_USER}:${KIOSK_USER}" /var/lib/kraken-player/config.json

echo "==> Universal kiosk shortcuts (Ctrl+Shift+Q exit, Ctrl+Shift+K return)..."
OPENBOX_DIR="${USER_HOME}/.config/openbox"
install -d -m 0755 -o "${KIOSK_USER}" -g "${KIOSK_USER}" "${OPENBOX_DIR}"
cat > "${OPENBOX_DIR}/rc.xml" <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<openbox_config xmlns="http://openbox.org/3.4/rc">
  <keyboard>
    <keybind key="C-S-q">
      <action name="Execute"><command>pkill -USR1 -x kraken-player</command></action>
    </keybind>
    <keybind key="C-S-k">
      <action name="Execute"><command>pkill -USR2 -x kraken-player</command></action>
    </keybind>
  </keyboard>
</openbox_config>
EOF
chown "${KIOSK_USER}:${KIOSK_USER}" "${OPENBOX_DIR}/rc.xml"

echo "==> Console autologin for ${KIOSK_USER} on tty1..."
install -d -m 0755 /etc/systemd/system/getty@tty1.service.d
cat > /etc/systemd/system/getty@tty1.service.d/autologin.conf <<EOF
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin ${KIOSK_USER} --noclear %I
EOF

echo "==> Profile: start X on tty1 login..."
PROFILE="${USER_HOME}/.bash_profile"
MARK="# kraken-player kiosk autostart"
if ! grep -qF "${MARK}" "${PROFILE}" 2>/dev/null; then
  cat >> "${PROFILE}" <<EOF

${MARK}
if [ -z "\${DISPLAY:-}" ] && [ "\$(tty)" = "/dev/tty1" ]; then
  exec startx -- -nocursor
fi
EOF
fi

echo "==> .xinitrc: blank/DPMS off, hide cursor, relaunch loop..."
cat > "${USER_HOME}/.xinitrc" <<EOF
#!/bin/sh
# kraken-player kiosk session (managed by appliance-pi.sh - edits will be overwritten)
LOG=/var/lib/kraken-player/kiosk.log
xset s off
xset s noblank
xset -dpms
xsetroot -solid '#080b10'
unclutter -idle 0 -root &
# Chromium's --kiosk flag still needs a window manager on a bare X session to
# learn the monitor work area. Without one Debian 13 opened a borderless window
# at its default size, making Whitecore appear on only the left half of the
# display. Openbox is deliberately minimal and expands kiosk windows correctly.
openbox-session >> "\${LOG}" 2>&1 &
sleep 1
# Keep a visible startup notice behind Chromium. A working kiosk covers it; if
# Chromium/player startup fails, the operator sees recovery
# instructions instead of an unexplained black display.
xmessage -center -buttons '' "Whitecore Player is starting...\n\nIf this message remains, press Ctrl+Alt+F2 and run:\n  tail -100 \${LOG}" &
# Relaunch loop: a crash, auto-update re-exec, or RESTART_PLAYER (clean exit)
# brings the player back without leaving a black screen.
while true; do
  printf '\n[%s] starting Whitecore Player\n' "\$(date -Is)" >> "\${LOG}"
  "${PLAYER_BIN}" --config /var/lib/kraken-player/config.json >> "\${LOG}" 2>&1
  rc=\$?
  printf '[%s] player exited with code %s; restarting in 2 seconds\n' "\$(date -Is)" "\${rc}" >> "\${LOG}"
  sleep 2
done
EOF
chmod +x "${USER_HOME}/.xinitrc"
chown "${KIOSK_USER}:${KIOSK_USER}" "${PROFILE}" "${USER_HOME}/.xinitrc"

echo "==> Disabling swap..."
if command -v dphys-swapfile >/dev/null 2>&1; then
  dphys-swapfile swapoff >/dev/null 2>&1 || true
  dphys-swapfile uninstall >/dev/null 2>&1 || true
fi
systemctl disable dphys-swapfile >/dev/null 2>&1 || true

echo "==> First-boot machine-id + SSH host key regeneration (clone safety)..."
cat > /usr/local/sbin/kraken-firstboot.sh <<'EOF'
#!/usr/bin/env bash
# One-shot on first boot of a cloned image: give this device a unique identity so
# the player's deviceId (derived from /etc/machine-id) and SSH host keys don't
# collide with the image's source or sibling clones.
set -e
: > /etc/machine-id
rm -f /var/lib/dbus/machine-id
systemd-machine-id-setup
ln -sf /etc/machine-id /var/lib/dbus/machine-id || true
rm -f /etc/ssh/ssh_host_* 2>/dev/null || true
if command -v dpkg-reconfigure >/dev/null 2>&1; then
  DEBIAN_FRONTEND=noninteractive dpkg-reconfigure openssh-server >/dev/null 2>&1 || ssh-keygen -A
else
  ssh-keygen -A
fi
systemctl disable kraken-firstboot.service
EOF
chmod 0755 /usr/local/sbin/kraken-firstboot.sh
cat > /etc/systemd/system/kraken-firstboot.service <<'EOF'
[Unit]
Description=Kraken first-boot identity reset (machine-id + SSH host keys)
ConditionFirstBoot=yes
After=systemd-machine-id-commit.service

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/kraken-firstboot.sh

[Install]
WantedBy=multi-user.target
EOF
systemctl enable kraken-firstboot.service >/dev/null 2>&1 || true

echo "==> Disabling the kraken-player systemd service (appliance runs it from X)..."
systemctl disable kraken-player.service >/dev/null 2>&1 || true
systemctl stop kraken-player.service >/dev/null 2>&1 || true
# Marker so a later install.sh re-run does NOT re-enable the service (double instance).
install -d -m 0755 /var/lib/kraken-player
touch /var/lib/kraken-player/appliance.mode

systemctl daemon-reload
systemctl set-default multi-user.target >/dev/null 2>&1 || true

echo
echo "Done. Reboot to enter kiosk mode:  sudo reboot"
echo "The player config stays at /var/lib/kraken-player/config.json (run install.sh first if absent)."
