feat: add custom Debian ISO build for automated silent installation

This commit is contained in:
2026-05-23 12:37:31 +00:00
parent 38f7ae467a
commit b7866000b4
4 changed files with 437 additions and 0 deletions
+99
View File
@@ -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)
Executable
+119
View File
@@ -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
+161
View File
@@ -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'
<?xml version="1.0" encoding="UTF-8"?>
<openbox_config xmlns="http://openbox.org/3.4/rc">
<startup/>
</openbox_config>
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
+58
View File
@@ -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'