feat: add custom Debian ISO build for automated silent installation
This commit is contained in:
@@ -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
@@ -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
|
||||
Executable
+161
@@ -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
@@ -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'
|
||||
Reference in New Issue
Block a user