Add kiosk setup and deployment scripts (Phases 5 + 9)
Phase 5 — RPi Kiosk: - setup_kiosk.sh: full RPi OS Lite setup (X11, Chromium kiosk mode, auto-login, DPMS disabled, GPU memory split, screen rotation) - kiosk.service: systemd unit for reliable auto-start - update_kiosk.sh: reconfigure URL/rotation/resolution without re-setup - Handles both Bullseye and Bookworm RPi OS versions Phase 9 — Hardening + Deployment: - install.sh: full server setup (apt/pacman, vigilar user, venv, directories, permissions, mosquitto config, systemd units) - gen_cert.sh: TLS cert via mkcert or openssl fallback - gen_vapid_keys.sh: VAPID keys for Web Push notifications - setup_nut.sh: NUT configuration with USB UPS auto-detection - backup.sh: SQLite snapshot + config archive, cron-ready - uninstall.sh: clean removal with data preservation option - vigilar.service: hardened systemd unit (ProtectSystem, NoNewPrivileges, PrivateTmp, syscall filtering) - vigilar-mosquitto.conf: localhost-only MQTT broker config All scripts idempotent, bash -n validated, support Debian + Arch. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
385
kiosk/setup_kiosk.sh
Executable file
385
kiosk/setup_kiosk.sh
Executable file
@@ -0,0 +1,385 @@
|
||||
#!/usr/bin/env bash
|
||||
# Vigilar Kiosk — Full Raspberry Pi setup script
|
||||
# Run on a fresh Raspberry Pi OS Lite (64-bit, no desktop).
|
||||
# Safe to re-run (idempotent).
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
KIOSK_USER="vigilar"
|
||||
KIOSK_HOME="/home/${KIOSK_USER}"
|
||||
CONFIG_FILE="${KIOSK_HOME}/kiosk_config.txt"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
info() { printf '\n\033[1;34m>>> %s\033[0m\n' "$*"; }
|
||||
warn() { printf '\033[1;33mWARN: %s\033[0m\n' "$*"; }
|
||||
error() { printf '\033[1;31mERROR: %s\033[0m\n' "$*" >&2; exit 1; }
|
||||
|
||||
need_root() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
error "This script must be run as root (use sudo)."
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Pre-flight
|
||||
# ---------------------------------------------------------------------------
|
||||
need_root
|
||||
info "Vigilar Kiosk Setup"
|
||||
echo "This script turns a Raspberry Pi into a dedicated security-camera display."
|
||||
echo ""
|
||||
|
||||
# Detect Debian version
|
||||
if [[ -f /etc/os-release ]]; then
|
||||
# shellcheck source=/dev/null
|
||||
. /etc/os-release
|
||||
DEBIAN_VERSION="${VERSION_ID:-11}"
|
||||
else
|
||||
DEBIAN_VERSION="11"
|
||||
fi
|
||||
info "Detected Debian version: ${DEBIAN_VERSION} (${PRETTY_NAME:-unknown})"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1. Ask for Vigilar server URL
|
||||
# ---------------------------------------------------------------------------
|
||||
DEFAULT_URL="https://vigilar.local:49735/kiosk/"
|
||||
read -r -p "Vigilar server URL [${DEFAULT_URL}]: " USER_URL
|
||||
VIGILAR_URL="${USER_URL:-$DEFAULT_URL}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2. Ask for display settings
|
||||
# ---------------------------------------------------------------------------
|
||||
read -r -p "Screen rotation (0/90/180/270) [0]: " USER_ROTATION
|
||||
ROTATION="${USER_ROTATION:-0}"
|
||||
|
||||
read -r -p "Screen resolution (e.g. 1920x1080, 1280x720) [1920x1080]: " USER_RES
|
||||
RESOLUTION="${USER_RES:-1920x1080}"
|
||||
|
||||
echo ""
|
||||
info "Configuration summary"
|
||||
echo " URL: ${VIGILAR_URL}"
|
||||
echo " Rotation: ${ROTATION}"
|
||||
echo " Resolution: ${RESOLUTION}"
|
||||
echo ""
|
||||
read -r -p "Continue? [Y/n]: " CONFIRM
|
||||
if [[ "${CONFIRM,,}" == "n" ]]; then
|
||||
echo "Aborted."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3. Install packages
|
||||
# ---------------------------------------------------------------------------
|
||||
info "Updating package lists"
|
||||
apt-get update -qq
|
||||
|
||||
info "Installing X11, Chromium, and utilities"
|
||||
PACKAGES=(
|
||||
xserver-xorg
|
||||
xinit
|
||||
x11-xserver-utils
|
||||
chromium-browser
|
||||
unclutter
|
||||
)
|
||||
|
||||
# On Bookworm the package may just be 'chromium'
|
||||
if [[ "${DEBIAN_VERSION}" -ge 12 ]]; then
|
||||
# Try chromium-browser first; fall back to chromium
|
||||
if ! apt-cache show chromium-browser &>/dev/null; then
|
||||
PACKAGES=("${PACKAGES[@]/chromium-browser/chromium}")
|
||||
fi
|
||||
fi
|
||||
|
||||
apt-get install -y -qq "${PACKAGES[@]}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4. Create kiosk user
|
||||
# ---------------------------------------------------------------------------
|
||||
if id "${KIOSK_USER}" &>/dev/null; then
|
||||
info "User '${KIOSK_USER}' already exists"
|
||||
else
|
||||
info "Creating user '${KIOSK_USER}'"
|
||||
useradd -m -s /bin/bash -G video,input,render,tty "${KIOSK_USER}"
|
||||
fi
|
||||
|
||||
# Ensure group memberships even if user existed
|
||||
usermod -aG video,input,render,tty "${KIOSK_USER}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 5. Write config file
|
||||
# ---------------------------------------------------------------------------
|
||||
info "Writing ${CONFIG_FILE}"
|
||||
cat > "${CONFIG_FILE}" <<CFGEOF
|
||||
VIGILAR_URL=${VIGILAR_URL}
|
||||
ROTATION=${ROTATION}
|
||||
RESOLUTION=${RESOLUTION}
|
||||
CFGEOF
|
||||
chown "${KIOSK_USER}:${KIOSK_USER}" "${CONFIG_FILE}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 6. Write .xinitrc
|
||||
# ---------------------------------------------------------------------------
|
||||
info "Writing ${KIOSK_HOME}/.xinitrc"
|
||||
cat > "${KIOSK_HOME}/.xinitrc" <<'XINITEOF'
|
||||
#!/usr/bin/env bash
|
||||
# Vigilar kiosk X session
|
||||
set -euo pipefail
|
||||
|
||||
CONFIG="$HOME/kiosk_config.txt"
|
||||
if [[ -f "$CONFIG" ]]; then
|
||||
# shellcheck source=/dev/null
|
||||
source "$CONFIG"
|
||||
fi
|
||||
URL="${VIGILAR_URL:-https://vigilar.local:49735/kiosk/}"
|
||||
|
||||
# Disable screen blanking and DPMS
|
||||
xset s off
|
||||
xset s noblank
|
||||
xset -dpms
|
||||
|
||||
# Hide cursor after 0.5 s of inactivity
|
||||
unclutter -idle 0.5 -root &
|
||||
|
||||
# Handle rotation
|
||||
ROTATION="${ROTATION:-0}"
|
||||
case "${ROTATION}" in
|
||||
90) xrandr --output "$(xrandr | grep ' connected' | head -1 | awk '{print $1}')" --rotate left ;;
|
||||
180) xrandr --output "$(xrandr | grep ' connected' | head -1 | awk '{print $1}')" --rotate inverted ;;
|
||||
270) xrandr --output "$(xrandr | grep ' connected' | head -1 | awk '{print $1}')" --rotate right ;;
|
||||
*) ;; # 0 = normal, no action needed
|
||||
esac
|
||||
|
||||
# Determine chromium binary
|
||||
CHROMIUM=""
|
||||
for candidate in chromium-browser chromium; do
|
||||
if command -v "$candidate" &>/dev/null; then
|
||||
CHROMIUM="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ -z "$CHROMIUM" ]]; then
|
||||
echo "ERROR: No chromium binary found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Launch Chromium in kiosk mode
|
||||
# --ignore-certificate-errors is needed for self-signed TLS certs on vigilar.local
|
||||
exec "$CHROMIUM" \
|
||||
--noerrdialogs \
|
||||
--disable-infobars \
|
||||
--kiosk \
|
||||
--incognito \
|
||||
--disable-translate \
|
||||
--no-first-run \
|
||||
--fast \
|
||||
--fast-start \
|
||||
--disable-features=TranslateUI \
|
||||
--disk-cache-dir=/dev/null \
|
||||
--check-for-update-interval=31536000 \
|
||||
--ignore-certificate-errors \
|
||||
--disable-component-update \
|
||||
--disable-background-networking \
|
||||
--disable-sync \
|
||||
--autoplay-policy=no-user-gesture-required \
|
||||
"$URL"
|
||||
XINITEOF
|
||||
chmod +x "${KIOSK_HOME}/.xinitrc"
|
||||
chown "${KIOSK_USER}:${KIOSK_USER}" "${KIOSK_HOME}/.xinitrc"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 7. Write .bash_profile for auto-start (fallback method)
|
||||
# ---------------------------------------------------------------------------
|
||||
info "Writing ${KIOSK_HOME}/.bash_profile (auto-start X on tty1)"
|
||||
# Preserve existing content if any
|
||||
BASH_PROFILE="${KIOSK_HOME}/.bash_profile"
|
||||
MARKER="# --- Vigilar kiosk auto-start ---"
|
||||
|
||||
# Remove old block if present
|
||||
if [[ -f "${BASH_PROFILE}" ]]; then
|
||||
sed -i "/${MARKER}/,/# --- end Vigilar kiosk ---/d" "${BASH_PROFILE}"
|
||||
fi
|
||||
|
||||
cat >> "${BASH_PROFILE}" <<'BPEOF'
|
||||
# --- Vigilar kiosk auto-start ---
|
||||
if [[ -z "${DISPLAY:-}" ]] && [[ "$(tty)" == "/dev/tty1" ]]; then
|
||||
exec startx -- -nocursor
|
||||
fi
|
||||
# --- end Vigilar kiosk ---
|
||||
BPEOF
|
||||
chown "${KIOSK_USER}:${KIOSK_USER}" "${BASH_PROFILE}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 8. Install systemd service (default method)
|
||||
# ---------------------------------------------------------------------------
|
||||
info "Installing systemd service"
|
||||
SERVICE_SRC="${SCRIPT_DIR}/kiosk.service"
|
||||
SERVICE_DEST="/etc/systemd/system/vigilar-kiosk.service"
|
||||
|
||||
if [[ -f "${SERVICE_SRC}" ]]; then
|
||||
cp "${SERVICE_SRC}" "${SERVICE_DEST}"
|
||||
else
|
||||
# Generate inline if source file not found
|
||||
cat > "${SERVICE_DEST}" <<SVCEOF
|
||||
[Unit]
|
||||
Description=Vigilar Kiosk (X11 + Chromium)
|
||||
After=network-online.target systemd-user-sessions.service
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=${KIOSK_USER}
|
||||
Group=${KIOSK_USER}
|
||||
PAMName=login
|
||||
TTYPath=/dev/tty1
|
||||
StandardInput=tty
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SupplementaryGroups=video input render
|
||||
Environment=HOME=${KIOSK_HOME}
|
||||
WorkingDirectory=${KIOSK_HOME}
|
||||
ExecStart=/usr/bin/xinit ${KIOSK_HOME}/.xinitrc -- /usr/bin/X :0 vt1 -keeptty -nocursor
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
SVCEOF
|
||||
fi
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable vigilar-kiosk.service
|
||||
info "systemd service enabled (vigilar-kiosk.service)"
|
||||
|
||||
# Disable .bash_profile auto-start when using systemd (comment it out)
|
||||
sed -i "s/^ exec startx/# exec startx/" "${BASH_PROFILE}"
|
||||
info "Disabled .bash_profile auto-start (systemd is the default)"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 9. Configure auto-login on tty1
|
||||
# ---------------------------------------------------------------------------
|
||||
info "Configuring auto-login on tty1 for '${KIOSK_USER}'"
|
||||
AUTOLOGIN_DIR="/etc/systemd/system/getty@tty1.service.d"
|
||||
mkdir -p "${AUTOLOGIN_DIR}"
|
||||
cat > "${AUTOLOGIN_DIR}/autologin.conf" <<ALEOF
|
||||
[Service]
|
||||
ExecStart=
|
||||
ExecStart=-/sbin/agetty --autologin ${KIOSK_USER} --noclear %I \$TERM
|
||||
ALEOF
|
||||
systemctl daemon-reload
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 10. Disable console blanking
|
||||
# ---------------------------------------------------------------------------
|
||||
info "Disabling console screen blanking"
|
||||
# Kernel cmdline approach — add consoleblank=0
|
||||
CMDLINE="/boot/cmdline.txt"
|
||||
# On Bookworm, firmware cmdline may be at /boot/firmware/cmdline.txt
|
||||
if [[ -f /boot/firmware/cmdline.txt ]]; then
|
||||
CMDLINE="/boot/firmware/cmdline.txt"
|
||||
fi
|
||||
if [[ -f "${CMDLINE}" ]]; then
|
||||
if ! grep -q 'consoleblank=0' "${CMDLINE}"; then
|
||||
sed -i 's/$/ consoleblank=0/' "${CMDLINE}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 11. GPU memory split
|
||||
# ---------------------------------------------------------------------------
|
||||
info "Setting GPU memory to 128 MB"
|
||||
CONFIG_TXT="/boot/config.txt"
|
||||
if [[ -f /boot/firmware/config.txt ]]; then
|
||||
CONFIG_TXT="/boot/firmware/config.txt"
|
||||
fi
|
||||
if [[ -f "${CONFIG_TXT}" ]]; then
|
||||
if grep -q '^gpu_mem=' "${CONFIG_TXT}"; then
|
||||
sed -i 's/^gpu_mem=.*/gpu_mem=128/' "${CONFIG_TXT}"
|
||||
else
|
||||
echo "gpu_mem=128" >> "${CONFIG_TXT}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 12. Screen rotation and resolution in config.txt
|
||||
# ---------------------------------------------------------------------------
|
||||
info "Configuring display rotation and resolution"
|
||||
if [[ -f "${CONFIG_TXT}" ]]; then
|
||||
# Rotation via display_rotate (legacy) or display_lcd_rotate
|
||||
if [[ "${ROTATION}" != "0" ]]; then
|
||||
ROTATE_VAL=0
|
||||
case "${ROTATION}" in
|
||||
90) ROTATE_VAL=1 ;;
|
||||
180) ROTATE_VAL=2 ;;
|
||||
270) ROTATE_VAL=3 ;;
|
||||
esac
|
||||
if grep -q '^display_rotate=' "${CONFIG_TXT}"; then
|
||||
sed -i "s/^display_rotate=.*/display_rotate=${ROTATE_VAL}/" "${CONFIG_TXT}"
|
||||
else
|
||||
echo "display_rotate=${ROTATE_VAL}" >> "${CONFIG_TXT}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Resolution via hdmi_group and hdmi_mode
|
||||
# Parse WxH
|
||||
RES_W="${RESOLUTION%%x*}"
|
||||
RES_H="${RESOLUTION##*x}"
|
||||
HDMI_MODE=""
|
||||
case "${RES_W}x${RES_H}" in
|
||||
1920x1080) HDMI_MODE=16 ;;
|
||||
1280x720) HDMI_MODE=4 ;;
|
||||
1680x1050) HDMI_MODE=58 ;;
|
||||
1280x1024) HDMI_MODE=35 ;;
|
||||
1024x768) HDMI_MODE=16 ;; # DMT mode 16
|
||||
*) warn "Unknown resolution ${RESOLUTION}, skipping hdmi_mode config" ;;
|
||||
esac
|
||||
|
||||
if [[ -n "${HDMI_MODE}" ]]; then
|
||||
# hdmi_group=1 = CEA (TVs), hdmi_group=2 = DMT (monitors)
|
||||
HDMI_GROUP=1
|
||||
if [[ "${RES_W}x${RES_H}" == "1680x1050" ]] || [[ "${RES_W}x${RES_H}" == "1280x1024" ]]; then
|
||||
HDMI_GROUP=2
|
||||
fi
|
||||
for KEY_VAL in "hdmi_group=${HDMI_GROUP}" "hdmi_mode=${HDMI_MODE}"; do
|
||||
KEY="${KEY_VAL%%=*}"
|
||||
if grep -q "^${KEY}=" "${CONFIG_TXT}"; then
|
||||
sed -i "s/^${KEY}=.*/${KEY_VAL}/" "${CONFIG_TXT}"
|
||||
else
|
||||
echo "${KEY_VAL}" >> "${CONFIG_TXT}"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 13. Enable SSH
|
||||
# ---------------------------------------------------------------------------
|
||||
info "Enabling SSH"
|
||||
systemctl enable ssh 2>/dev/null || systemctl enable sshd 2>/dev/null || true
|
||||
systemctl start ssh 2>/dev/null || systemctl start sshd 2>/dev/null || true
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 14. Set hostname
|
||||
# ---------------------------------------------------------------------------
|
||||
info "Setting hostname to 'vigilar-kiosk'"
|
||||
hostnamectl set-hostname vigilar-kiosk 2>/dev/null || {
|
||||
echo "vigilar-kiosk" > /etc/hostname
|
||||
sed -i "s/127\.0\.1\.1.*/127.0.1.1\tvigilar-kiosk/" /etc/hosts
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Done
|
||||
# ---------------------------------------------------------------------------
|
||||
info "Setup complete!"
|
||||
echo ""
|
||||
echo " Kiosk URL: ${VIGILAR_URL}"
|
||||
echo " User: ${KIOSK_USER}"
|
||||
echo " Service: vigilar-kiosk.service (systemd, enabled)"
|
||||
echo " Config: ${CONFIG_FILE}"
|
||||
echo ""
|
||||
echo " To start now: sudo systemctl start vigilar-kiosk"
|
||||
echo " To check status: sudo systemctl status vigilar-kiosk"
|
||||
echo " To view logs: sudo journalctl -u vigilar-kiosk -f"
|
||||
echo " To reconfigure: sudo ./update_kiosk.sh"
|
||||
echo ""
|
||||
echo " A reboot is recommended: sudo reboot"
|
||||
Reference in New Issue
Block a user