303 lines
13 KiB
Bash
Executable File
303 lines
13 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Smart Home Door-Panel ISO Builder
|
|
# Target: builds a Debian 12 (Bookworm) live ISO on a Debian/Ubuntu build machine
|
|
#
|
|
# Drives `lb config && lb build` over hosts/door-panel/live-build/ to produce the
|
|
# door/wardrobe panel described in docs/project-plan.md Phase 18:
|
|
# - greetd autologin straight into a kiosk Sway session (no greeter UI)
|
|
# - ONE Chromium kiosk window, default screen identity's dashboard.html (weather +
|
|
# what-to-wear, who's home, groceries running low), "Show registration" on demand
|
|
# - door-panel-agent (Python, systemd) — HA MQTT "Show home/registration" buttons
|
|
# only; presence/weather/groceries/registration all come from identity's and
|
|
# pantry-vision's own published APIs, called directly by the browser
|
|
# - wyoming-satellite + openWakeWord for voice registration ("register me as
|
|
# <name>") — opt-in like every other host's mic, but this device's whole point,
|
|
# so expect it on for a real deployment
|
|
#
|
|
# Reuses hosts/thin-client's live-build toolchain and directory-split convention, not
|
|
# its live-build tree — same relationship hosts/touch-panel, hosts/audio-endpoint,
|
|
# and hosts/kitchen-display already have to the thin client's. Structurally this is
|
|
# hosts/kitchen-display's twin: same one-workspace-two-kiosk-destinations shape,
|
|
# different default content and a mic that's actually expected to be used.
|
|
#
|
|
# Run as: sudo -E tools/build-door-panel-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-door-panel-iso.sh # the only door-panel in the config
|
|
# sudo -E tools/build-door-panel-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 "door-panel" "${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"
|
|
DOOR_PANEL_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.
|
|
DOOR_PANEL_ROOM="${CORE_KIOSK_ROOM:-}"
|
|
IDENTITY_WEB_URL="$CORE_IDENTITY_WEB_URL"
|
|
IDENTITY_URL="$CORE_IDENTITY_URL"
|
|
IDENTITY_TOKEN="$CORE_IDENTITY_TOKEN"
|
|
PANTRY_VISION_URL="$CORE_PANTRY_VISION_URL"
|
|
PANTRY_VISION_TOKEN="$CORE_PANTRY_VISION_TOKEN"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 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.
|
|
# ---------------------------------------------------------------------------
|
|
DOOR_PANEL_DIR="${CORE_REPO_ROOT}/hosts/door-panel"
|
|
CONFIGS_DIR="${DOOR_PANEL_DIR}/configs"
|
|
AGENT_DIR="${DOOR_PANEL_DIR}/agent"
|
|
LIVE_BUILD_DIR="${DOOR_PANEL_DIR}/live-build"
|
|
INCLUDES="${LIVE_BUILD_DIR}/config/includes.chroot"
|
|
PACKAGE_LIST="${LIVE_BUILD_DIR}/config/package-lists/door-panel.list.chroot"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Sanity checks
|
|
# ---------------------------------------------------------------------------
|
|
if [[ $EUID -ne 0 ]]; then
|
|
echo "Warning: not running as root. 'lb build' needs root; re-run with: sudo $0"
|
|
echo " Continuing anyway so you can at least regenerate includes.chroot..."
|
|
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 expects a real microphone on"
|
|
echo " the physical unit. Don't flash it to hardware that doesn't have one."
|
|
fi
|
|
|
|
echo
|
|
echo "=== Smart Home Door-Panel 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 "identity-web : $IDENTITY_WEB_URL"
|
|
echo "identity : $IDENTITY_URL"
|
|
echo "pantry-vision : $PANTRY_VISION_URL"
|
|
echo "Voice satellite : $ENABLE_VOICE_SATELLITE"
|
|
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/door-panel-agent" \
|
|
"$INCLUDES/usr/local/bin" \
|
|
"$INCLUDES/opt/door-panel-agent" \
|
|
"$INCLUDES/home/${KIOSK_USERNAME}/.config/sway" \
|
|
"$INCLUDES/home/${KIOSK_USERNAME}/.ssh"
|
|
|
|
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}/sway/config" "$INCLUDES/home/${KIOSK_USERNAME}/.config/sway/config"
|
|
subst "${AGENT_DIR}/door-panel-agent.service" "$INCLUDES/opt/door-panel-agent/door-panel-agent.service"
|
|
|
|
install -m 0755 "${CONFIGS_DIR}/greetd/kiosk-session" "$INCLUDES/usr/local/bin/kiosk-session"
|
|
install -m 0755 "${CONFIGS_DIR}/sway/home-kiosk" "$INCLUDES/usr/local/bin/home-kiosk"
|
|
install -m 0755 "${CONFIGS_DIR}/sway/identity-kiosk" "$INCLUDES/usr/local/bin/identity-kiosk"
|
|
|
|
mkdir -p "$INCLUDES/etc/default"
|
|
cat > "$INCLUDES/etc/default/keyboard" <<EOF
|
|
XKBMODEL="pc105"
|
|
XKBLAYOUT="${KEYBOARD_LAYOUT}"
|
|
XKBVARIANT=""
|
|
XKBOPTIONS=""
|
|
|
|
BACKSPACE="guess"
|
|
EOF
|
|
|
|
cp -r "${AGENT_DIR}/door_panel_agent" "$INCLUDES/opt/door-panel-agent/"
|
|
install -m 0644 "${AGENT_DIR}/requirements.txt" "$INCLUDES/opt/door-panel-agent/requirements.txt"
|
|
find "$INCLUDES/opt/door-panel-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
|
|
# ---------------------------------------------------------------------------
|
|
echo "--- Writing /etc/door-panel-agent/config.env into includes.chroot ---"
|
|
cat > "$INCLUDES/etc/door-panel-agent/config.env" <<EOF
|
|
# Generated by tools/build-door-panel-iso.sh — do not hand-edit
|
|
# here; change CoreSystemConfig.json at the repo root and rebuild.
|
|
KIOSK_USERNAME=${KIOSK_USERNAME}
|
|
DOOR_PANEL_NAME=${DOOR_PANEL_NAME}
|
|
DOOR_PANEL_ROOM=${DOOR_PANEL_ROOM}
|
|
|
|
MQTT_BROKER_HOST=${MQTT_BROKER_HOST}
|
|
MQTT_BROKER_PORT=${MQTT_BROKER_PORT}
|
|
MQTT_USERNAME=${MQTT_USERNAME}
|
|
MQTT_PASSWORD=${MQTT_PASSWORD}
|
|
|
|
IDENTITY_WEB_URL=${IDENTITY_WEB_URL}
|
|
IDENTITY_URL=${IDENTITY_URL}
|
|
IDENTITY_TOKEN=${IDENTITY_TOKEN}
|
|
|
|
PANTRY_VISION_URL=${PANTRY_VISION_URL}
|
|
PANTRY_VISION_TOKEN=${PANTRY_VISION_TOKEN}
|
|
|
|
ENABLE_VOICE_SATELLITE=${ENABLE_VOICE_SATELLITE}
|
|
VOICE_SATELLITE_NAME=${VOICE_SATELLITE_NAME}
|
|
VOICE_WAKE_WORD=${VOICE_WAKE_WORD}
|
|
EOF
|
|
chmod 0644 "$INCLUDES/etc/door-panel-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 ---"
|
|
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 Door Panel" \
|
|
--iso-publisher "SmartestHome" \
|
|
--iso-volume "smarthome-door-panel" \
|
|
--memtest none \
|
|
--bootappend-live "boot=live components quiet splash noautologin username=${KIOSK_USERNAME} hostname=${IMAGE_HOSTNAME}"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 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" "door-panel" "$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 : one workspace, one Chromium kiosk window"
|
|
echo " Default screen : ${IDENTITY_WEB_URL}/dashboard.html (weather+clothing,"
|
|
echo " who's home, groceries running low)"
|
|
echo " Registration : ${IDENTITY_WEB_URL}/register.html, on 'Show registration'"
|
|
echo " (MQTT) or by voice — see identity/README.md"
|
|
if [[ "$ENABLE_VOICE_SATELLITE" == "true" ]]; then
|
|
echo " Voice satellite : wyoming-satellite + openWakeWord ('${VOICE_WAKE_WORD}'), room"
|
|
echo " '${VOICE_SATELLITE_NAME}'"
|
|
else
|
|
echo " Voice satellite : NOT installed (ENABLE_VOICE_SATELLITE=false) — registration"
|
|
echo " still works via the touchscreen form"
|
|
fi
|
|
echo " door-panel-agent : system service, MQTT ${MQTT_BROKER_HOST}:${MQTT_BROKER_PORT}"
|
|
echo " Keyboard layout : ${KEYBOARD_LAYOUT} (console + Sway)"
|
|
echo " Maintenance shell : Super+Shift+Ctrl+M opens a floating terminal locally"
|
|
echo " Remote admin : SSH only — no wayvnc, same scope decision as hosts/touch-panel"
|
|
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)"
|
|
echo " 2. Boot the target machine — a touchscreen, mic, and speaker are required"
|
|
echo " hardware, none of it built in or chosen yet. See the README."
|
|
echo " 3. Confirm greetd autologin lands in Sway with the dashboard up."
|
|
echo " 4. Register yourself via the touchscreen form (tap 'Show registration') to"
|
|
echo " exercise the identity path before setting up HA's voice intent wiring."
|
|
echo " 5. Confirm door-panel-agent connected and registered:"
|
|
echo " systemctl status door-panel-agent"
|
|
echo " In Home Assistant, a '${DOOR_PANEL_NAME}' device should appear under the MQTT"
|
|
echo " integration with: Show home / Show registration (buttons)."
|
|
if [[ "$ENABLE_VOICE_SATELLITE" == "true" ]]; then
|
|
echo " 6. Once HA's custom-sentence + intent script (identity/README.md's worked"
|
|
echo " example) are wired up, say 'register me as <name>' and confirm it works."
|
|
fi
|
|
echo
|
|
echo "Then pull the power on the container host and re-check: the panel must still"
|
|
echo "boot (the dashboard will show connection errors, which is the expected degraded"
|
|
echo "state — same acceptance already documented for hosts/touch-panel's Home"
|
|
echo "workspace and hosts/kitchen-display's Scan/Inventory/Recipes tabs)."
|
|
echo
|
|
echo "Rebuilding later: edit hosts/door-panel/configs/* or agent/*, then re-run this"
|
|
echo "script — includes.chroot is regenerated from them every time."
|