From b7866000b47f459acbe562ada7e44afc0d70f9f7 Mon Sep 17 00:00:00 2001 From: Joe Gitta Date: Sat, 23 May 2026 12:37:31 +0000 Subject: [PATCH] feat: add custom Debian ISO build for automated silent installation --- ISO-BUILD.md | 99 +++++++++++++++++++++++++++++ build-iso.sh | 119 +++++++++++++++++++++++++++++++++++ install-kiosk.sh | 161 +++++++++++++++++++++++++++++++++++++++++++++++ preseed.cfg | 58 +++++++++++++++++ 4 files changed, 437 insertions(+) create mode 100644 ISO-BUILD.md create mode 100755 build-iso.sh create mode 100755 install-kiosk.sh create mode 100644 preseed.cfg diff --git a/ISO-BUILD.md b/ISO-BUILD.md new file mode 100644 index 0000000..67d3b2a --- /dev/null +++ b/ISO-BUILD.md @@ -0,0 +1,99 @@ +# SeniorNet Kiosk — Custom Debian ISO + +Build a bootable ISO that installs and auto-configures the kiosk app on any machine. + +## On Your Workstation (192.168.88.41) + +### 1. Clone the repo (if not already done) +```bash +git clone https://gitea.jgitta.com/jgitta/senior-kiosk.git +cd senior-kiosk +``` + +### 2. Build the ISO +```bash +sudo ./build-iso.sh +``` + +This creates: `iso-output/seniornet-kiosk.iso` (~1-2 GB, takes 5-10 min) + +## Testing on Production Kiosk (192.168.88.44) + +### 1. Write ISO to bootable USB +On your workstation with USB connected: +```bash +# List devices to find your USB +lsblk + +# Write ISO (replace sdX with your USB device, e.g., sdb) +sudo dd if=iso-output/seniornet-kiosk.iso of=/dev/sdX bs=4M status=progress && sync +``` + +### 2. Boot from USB +- Plug USB into production kiosk +- Power on and select USB boot (usually F12, ESC, or DEL during startup) +- Installation starts silently (no prompts) + +### 3. Wait for installation +- Silent Debian installation: ~5-10 minutes +- Clones repo, installs dependencies +- System reboots automatically + +### 4. Verify kiosk is running +After reboot, you should see the kiosk home screen with buttons. + +To check status via SSH: +```bash +ssh jgitta@192.168.88.44 +journalctl -u kiosk.service -f +``` + +## What the ISO Contains + +- **Debian 13 Trixie** (minimal base) +- **Chromium** (system package, not snap) +- **Python 3, PyQt5, websocket-client** +- **Openbox** (window manager) +- **Xvfb** (virtual display) +- **Your kiosk app** (cloned from Gitea at install time) +- **Auto-boot to kiosk mode** (no desktop login) + +## Files + +- `preseed.cfg` — Debian unattended installer config +- `install-kiosk.sh` — Post-install script (runs after Debian install) +- `build-iso.sh` — Build script (creates the ISO) + +## Troubleshooting + +**If installation hangs:** +- Check network connectivity during install +- Gitea must be reachable (or install will use git clone fallback) + +**If kiosk doesn't start:** +```bash +ssh jgitta@192.168.88.44 +journalctl -u kiosk.service -20 # View last 20 lines of logs +sudo systemctl restart kiosk.service +``` + +**To access terminal:** +- Press Alt+F2 during boot +- Log in as `root` (password: `kiosk`) +- Run: `/usr/local/bin/kiosk-restart` to restart + +## Customization + +To modify the ISO: +1. Edit `preseed.cfg` (installer config) +2. Edit `install-kiosk.sh` (post-install) +3. Edit `build-iso.sh` (ISO build options) +4. Rebuild: `sudo ./build-iso.sh` + +## Distribution + +Once tested, you can: +- Copy `seniornet-kiosk.iso` to USB drives for deployment +- Host on web server for download +- Share with end users +- Any machine can boot and auto-install (no Proxmox needed) diff --git a/build-iso.sh b/build-iso.sh new file mode 100755 index 0000000..8a60b9a --- /dev/null +++ b/build-iso.sh @@ -0,0 +1,119 @@ +#!/bin/bash +set -e + +# Build SeniorNet Kiosk ISO +# Run on workstation: ./build-iso.sh +# Requires: debian-live-build, sudo + +echo "==> SeniorNet Kiosk ISO Builder" +echo + +if [ "$EUID" -ne 0 ]; then + echo "ERROR: Run as root (sudo ./build-iso.sh)" + exit 1 +fi + +# Check dependencies +if ! command -v lb &>/dev/null; then + echo "==> Installing debian-live-build..." + apt-get update + apt-get install -y debian-live-build +fi + +BUILD_DIR="/tmp/kiosk-build-$$" +OUTPUT_DIR="$(pwd)/iso-output" +REPO_DIR="$(cd "$(dirname "$0")" && pwd)" + +mkdir -p "$BUILD_DIR" "$OUTPUT_DIR" +cd "$BUILD_DIR" + +echo "==> Initializing live-build..." +lb config \ + --distribution trixie \ + --architectures amd64 \ + --image-type iso-hybrid \ + --bootappend-live "boot=live components autologin username=root quiet splash" \ + --bootappend-install "preseed/file=/preseed.cfg auto=true priority=critical" \ + --linux-packages "linux-image-amd64" \ + --parent-mirror-bootstrap "https://deb.debian.org/debian" \ + --parent-mirror-binary "https://deb.debian.org/debian" \ + --parent-mirror-binary-security "https://security.debian.org/debian-security" \ + --parent-distribution-binary-security trixie-security + +echo "==> Adding packages..." +mkdir -p config/package-lists +cat > config/package-lists/kiosk.list.chroot << 'PKGLIST' +# Base system +openssh-server +build-essential +git +curl +wget +sudo + +# Display and window manager +xserver-xorg +xserver-xorg-video-dummy +xinit +openbox +xvfb +xdotool +xprop + +# Chromium browser +chromium + +# Python and kiosk app +python3 +python3-pip +sqlite3 +PKGLIST + +echo "==> Adding preseed file..." +mkdir -p config/includes.installer +cp "$REPO_DIR/preseed.cfg" config/includes.installer/ + +echo "==> Adding hook script..." +mkdir -p config/hooks/normal +cat > config/hooks/normal/9999-kiosk-postinstall.chroot << 'HOOK' +#!/bin/bash +set -e + +echo "==> Running kiosk post-install hook..." + +# Install Python packages +pip3 install --break-system-packages PyQt5 websocket-client + +# Clone repo +if [ ! -d /home/jgitta/kiosk-browser ]; then + git clone https://gitea.jgitta.com/jgitta/senior-kiosk.git /home/jgitta/kiosk-browser + cd /home/jgitta/kiosk-browser + bash install-kiosk.sh +fi + +echo "==> Kiosk post-install complete" +HOOK +chmod +x config/hooks/normal/9999-kiosk-postinstall.chroot + +echo "==> Building ISO (this may take 5-10 minutes)..." +lb build 2>&1 | tail -20 + +ISO_FILE="live-image-amd64.hybrid.iso" +if [ -f "$ISO_FILE" ]; then + cp "$ISO_FILE" "$OUTPUT_DIR/seniornet-kiosk.iso" + echo + echo "==> ✓ ISO build complete!" + echo " Output: $OUTPUT_DIR/seniornet-kiosk.iso" + echo + echo "Next steps:" + echo " 1. Write to USB:" + echo " sudo dd if=$OUTPUT_DIR/seniornet-kiosk.iso of=/dev/sdX bs=4M status=progress && sync" + echo + echo " 2. Boot from USB and wait ~10 minutes for silent installation" + echo + echo " 3. Machine will reboot into kiosk mode automatically" + echo +else + echo "ERROR: ISO build failed" + exit 1 +fi diff --git a/install-kiosk.sh b/install-kiosk.sh new file mode 100755 index 0000000..07ce0a6 --- /dev/null +++ b/install-kiosk.sh @@ -0,0 +1,161 @@ +#!/bin/bash +set -e + +# Post-install script for SeniorNet Kiosk ISO +# Runs after Debian installation completes + +echo "==> SeniorNet Kiosk Post-Install" +echo + +REPO_DIR="/home/jgitta/kiosk-browser" +HOME_DIR="/home/jgitta/kiosk-home" +CHROMIUM_PROFILE="/home/jgitta/chromium-kiosk" + +# Ensure jgitta user exists +if ! id "jgitta" &>/dev/null; then + useradd -m -s /bin/bash jgitta +fi + +# Install packages +echo "==> Installing dependencies..." +apt-get update +apt-get install -y python3 python3-pip chromium openbox xserver-xorg xserver-xorg-video-dummy xinit xdotool xprop git sqlite3 xvfb 2>&1 | grep -v "^Reading" + +# Install Python packages +echo "==> Installing Python packages..." +pip3 install --break-system-packages PyQt5 websocket-client 2>&1 | tail -5 + +# Clone repo +echo "==> Cloning repository..." +if [ ! -d "$REPO_DIR" ]; then + git clone https://gitea.jgitta.com/jgitta/senior-kiosk.git "$REPO_DIR" +fi + +cd "$REPO_DIR" + +# Deploy home screen with config +echo "==> Deploying home screen..." +mkdir -p "$HOME_DIR" +if [ -f config.json ] && [ -f home-screen-index.html ]; then + config_json=$(cat config.json | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | tr '\n' ' ') + sed "s|const config = {};|const config = $config_json;|" home-screen-index.html > "$HOME_DIR/index.html" + chown jgitta:jgitta "$HOME_DIR/index.html" +fi + +# Create Chromium profile directory +mkdir -p "$CHROMIUM_PROFILE" +chown -R jgitta:jgitta "$CHROMIUM_PROFILE" + +# Systemd units +echo "==> Installing systemd units..." + +cat > /etc/systemd/system/kiosk-display.service << 'UNIT1' +[Unit] +Description=SeniorNet Kiosk Display +Before=kiosk.service +After=network.target + +[Service] +Type=forking +User=jgitta +ExecStart=/bin/bash -c 'Xvfb :0 -screen 0 1920x1080x24 -ac &' +ExecStartPost=/bin/sleep 2 +ExecStartPost=/bin/bash -c 'DISPLAY=:0 su - jgitta -c "openbox &"' +ExecStartPost=/bin/sleep 1 + +[Install] +WantedBy=multi-user.target +UNIT1 + +cat > /etc/systemd/system/kiosk.service << 'UNIT2' +[Unit] +Description=SeniorNet Kiosk +After=network.target kiosk-display.service +Wants=kiosk-display.service + +[Service] +Type=simple +User=jgitta +Environment="DISPLAY=:0" +WorkingDirectory=/home/jgitta/kiosk-browser +ExecStart=/usr/bin/python3 /home/jgitta/kiosk-browser/browser.py +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target +UNIT2 + +systemctl daemon-reload +systemctl enable kiosk-display.service kiosk.service + +# kiosk-restart helper +cat > /usr/local/bin/kiosk-restart << 'HELPER' +#!/bin/bash +pkill -f "remote-debugging-port=9222" || true +pkill -f "python3.*browser" || true +sleep 1 +systemctl restart kiosk.service +HELPER +chmod +x /usr/local/bin/kiosk-restart + +# Openbox config +echo "==> Configuring Openbox..." +mkdir -p /home/jgitta/.config/openbox + +cat > /home/jgitta/.config/openbox/rc.xml << 'OPENBOX' + + + + +OPENBOX + +cat > /home/jgitta/.xinitrc << 'XINITRC' +#!/bin/bash +exec openbox +XINITRC +chmod +x /home/jgitta/.xinitrc + +# X11 hardening +echo "==> Configuring X11..." +cat > /etc/X11/xorg.conf.d/50-kiosk.conf << 'XORG' +Section "Device" + Identifier "Dummy0" + Driver "dummy" +EndSection + +Section "Monitor" + Identifier "Monitor0" + HorizSync 30-80 + VertRefresh 30-75 +EndSection + +Section "Screen" + Identifier "Screen0" + Device "Dummy0" + Monitor "Monitor0" + DefaultDepth 24 + SubSection "Display" + Depth 24 + Modes "1920x1080" + EndSubSection +EndSection + +Section "ServerFlags" + Option "DontVTSwitch" "true" + Option "BlankTime" "0" + Option "StandbyTime" "0" +EndSection +XORG + +# Permissions +echo "==> Setting permissions..." +chown -R jgitta:jgitta /home/jgitta/.config /home/jgitta/.xinitrc "$REPO_DIR" "$HOME_DIR" + +# Enable getty on ttys for troubleshooting (optional, comment out for full lockdown) +systemctl set-default multi-user.target + +echo +echo "==> Post-install complete" +echo " Machine will auto-start kiosk on next boot" +echo diff --git a/preseed.cfg b/preseed.cfg new file mode 100644 index 0000000..920872c --- /dev/null +++ b/preseed.cfg @@ -0,0 +1,58 @@ +# Debian Preseed — SeniorNet Kiosk +# Silent/unattended installer configuration + +# Locale and keyboard +d-i debian-installer/locale string en_US.UTF-8 +d-i keyboard-configuration/xkb-keymap select us + +# Network +d-i netcfg/choose_interface select auto +d-i netcfg/get_hostname string kiosk +d-i netcfg/get_domain string local +d-i netcfg/hostname string kiosk + +# Mirror +d-i mirror/country string manual +d-i mirror/http/hostname string deb.debian.org +d-i mirror/http/directory string /debian +d-i mirror/http/proxy string + +# Partitioning — use entire disk +d-i partman-auto/method string lvm +d-i partman-lvm/device_remove_lvm boolean true +d-i partman-auto/purge_lvm_confirmation boolean true +d-i partman-auto/disk string /dev/sda +d-i partman-auto/choose_recipe select atomic +d-i partman/confirm_write_new_label boolean true +d-i partman/confirm boolean true + +# Clock/timezone +d-i time/zone string UTC +d-i clock-setup/utc boolean true +d-i clock-setup/ntp boolean true + +# Account setup — root with no password +d-i passwd/root-login boolean true +d-i passwd/make-user boolean false +d-i passwd/root-password password kiosk +d-i passwd/root-password-again password kiosk + +# APT +d-i apt-setup/services-select multiselect security, updates +d-i apt-setup/security_host string security.debian.org + +# Package selection +tasksel tasksel/first multiselect standard, ssh-server +d-i pkgsel/include string openssh-server build-essential git curl wget + +# Grub bootloader +d-i grub-installer/only boolean true +d-i grub-installer/bootdev string default + +# Finish installation +d-i finish-install/reboot_in_background boolean true + +# Run post-install script +d-i preseed/late_command string \ + in-target bash -c 'curl -fsSL https://gitea.jgitta.com/jgitta/senior-kiosk/raw/master/install-kiosk.sh | bash' || \ + in-target bash -c 'cd /root && git clone https://gitea.jgitta.com/jgitta/senior-kiosk.git && bash senior-kiosk/install-kiosk.sh'