Own plymouth theme (logo centred on white), quiet kernel command line without boot text, Raspberry logos or cursor, and no firmware rainbow square. The initramfs is only rebuilt when the theme changes; uninstall.sh restores the previous theme. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
340 lines
15 KiB
Bash
Executable File
340 lines
15 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# VoxIP Kiosk OS installer.
|
|
#
|
|
# Turns Raspberry Pi OS Lite (Bookworm/Trixie, NetworkManager) into a VoxIP
|
|
# kiosk. Also used by the image builder (inside the pi-gen chroot), so a
|
|
# flashed image and a manual install are identical.
|
|
#
|
|
# sudo ./install.sh [--url https://…] [--name "Hal begane grond"] [--hostname hal-bg]
|
|
# sudo ./install.sh --upgrade # update code, keep configuration
|
|
# ./install.sh --image … # used by image/stage-voxip (no service starts)
|
|
#
|
|
# Existing configuration in /etc/voxip-kiosk is never overwritten.
|
|
|
|
set -euo pipefail
|
|
|
|
SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
MODE="install"
|
|
URL=""
|
|
NAME=""
|
|
NEW_HOSTNAME=""
|
|
FORCE=0
|
|
|
|
LIB=/usr/lib/voxip-kiosk
|
|
SHARE=/usr/share/voxip-kiosk
|
|
DOC=/usr/share/doc/voxip-kiosk
|
|
ETC=/etc/voxip-kiosk
|
|
UNITS=(voxip-kiosk-x.service voxip-kiosk-browser.service voxip-kiosk-agent.service voxip-kiosk-manager.service
|
|
voxip-kiosk-healthcheck.service voxip-kiosk-healthcheck.timer voxip-network-guard.service voxip-boot-import.service)
|
|
ENABLE=(voxip-kiosk-x.service voxip-kiosk-browser.service voxip-kiosk-agent.service voxip-kiosk-manager.service
|
|
voxip-kiosk-healthcheck.timer voxip-network-guard.service voxip-boot-import.service)
|
|
|
|
say() { printf '\033[1;34m==>\033[0m %s\n' "$*"; }
|
|
warn() { printf '\033[1;33mLet op:\033[0m %s\n' "$*" >&2; }
|
|
die() { printf '\033[1;31mFout:\033[0m %s\n' "$*" >&2; exit 1; }
|
|
|
|
usage() {
|
|
sed -n '3,15p' "$0" | sed 's/^# \{0,1\}//'
|
|
exit "${1:-0}"
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--url) URL="${2:?--url vereist een waarde}"; shift 2 ;;
|
|
--name) NAME="${2:?--name vereist een waarde}"; shift 2 ;;
|
|
--hostname) NEW_HOSTNAME="${2:?--hostname vereist een waarde}"; shift 2 ;;
|
|
--upgrade) MODE="upgrade"; shift ;;
|
|
--image) MODE="image"; shift ;;
|
|
--force) FORCE=1; shift ;;
|
|
-h|--help) usage 0 ;;
|
|
*) warn "Onbekende optie: $1"; usage 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ $EUID -eq 0 ]] || die "Voer dit script uit met sudo."
|
|
[[ -f "$SRC/src/voxip_kiosk/config.py" ]] || die "Start install.sh vanuit de VoxIP Kiosk OS-repository."
|
|
|
|
in_chroot() {
|
|
if command -v systemd-detect-virt >/dev/null 2>&1 && systemd-detect-virt --chroot --quiet; then
|
|
return 0
|
|
fi
|
|
[[ "$(stat -c %d:%i /)" != "$(stat -c %d:%i /proc/1/root/. 2>/dev/null || echo x)" ]]
|
|
}
|
|
LIVE=1
|
|
if [[ $MODE == image ]] || in_chroot; then LIVE=0; fi
|
|
|
|
# --- checks ---------------------------------------------------------------
|
|
# In a subshell: os-release defines NAME and VERSION, which must not clobber ours.
|
|
# shellcheck disable=SC1091
|
|
CODENAME="$(. /etc/os-release && echo "${VERSION_CODENAME:-}")"
|
|
case "$CODENAME" in
|
|
bookworm|trixie) ;;
|
|
*) [[ $FORCE -eq 1 ]] || die "Niet-ondersteunde release '${CODENAME:-?}'. Ondersteund: bookworm, trixie (--force om toch door te gaan)." ;;
|
|
esac
|
|
|
|
if [[ $LIVE -eq 1 && $MODE == install ]]; then
|
|
if ! systemctl is-enabled --quiet NetworkManager.service 2>/dev/null && systemctl is-enabled --quiet dhcpcd.service 2>/dev/null; then
|
|
die "Dit systeem gebruikt dhcpcd. VoxIP Kiosk OS vereist NetworkManager (standaard sinds Bookworm). Schakel om via 'sudo raspi-config' → Advanced Options → Network Config, of gebruik de kant-en-klare image."
|
|
fi
|
|
fi
|
|
|
|
VERSION="$(tr -d '[:space:]' < "$SRC/VERSION")"
|
|
say "VoxIP Kiosk OS $VERSION installeren (modus: $MODE)"
|
|
|
|
# --- packages -----------------------------------------------------------------
|
|
pkg_available() { apt-cache policy "$1" 2>/dev/null | grep -q 'Candidate: [0-9]'; }
|
|
|
|
{
|
|
say "Pakketten installeren"
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
apt-get update -y
|
|
CHROMIUM=chromium
|
|
if pkg_available chromium-browser && [[ $CODENAME == bookworm ]]; then CHROMIUM=chromium-browser; fi
|
|
PKGS=(
|
|
"$CHROMIUM" xserver-xorg-core xserver-xorg-input-libinput xserver-xorg-legacy x11-xserver-utils xauth
|
|
fonts-dejavu-core fonts-noto-color-emoji
|
|
python3 python3-flask python3-argon2 python3-websocket gunicorn
|
|
network-manager avahi-daemon iw rfkill iproute2 openssl logrotate sudo kbd plymouth
|
|
)
|
|
for opt in systemd-zram-generator libraspberrypi-bin raspi-utils-core; do
|
|
if pkg_available "$opt"; then PKGS+=("$opt"); fi
|
|
done
|
|
# The "chromium" package needs its setuid sandbox where unprivileged user namespaces are unavailable.
|
|
if [[ $CHROMIUM == chromium ]] && pkg_available chromium-sandbox; then PKGS+=(chromium-sandbox); fi
|
|
apt-get install -y --no-install-recommends "${PKGS[@]}"
|
|
}
|
|
|
|
# --- users ----------------------------------------------------------------------
|
|
say "Gebruikers aanmaken"
|
|
getent group kiosk >/dev/null || groupadd --system kiosk
|
|
id kiosk >/dev/null 2>&1 || useradd --system --gid kiosk --home-dir /home/kiosk --create-home \
|
|
--shell /usr/sbin/nologin --comment "VoxIP kiosk display" kiosk
|
|
for g in video render input audio; do
|
|
getent group "$g" >/dev/null && usermod -aG "$g" kiosk
|
|
done
|
|
getent group voxip >/dev/null || groupadd --system voxip
|
|
id voxip >/dev/null 2>&1 || useradd --system --gid voxip --home-dir /var/lib/voxip-kiosk --no-create-home \
|
|
--shell /usr/sbin/nologin --comment "VoxIP Kiosk Manager" voxip
|
|
for g in systemd-journal video; do
|
|
getent group "$g" >/dev/null && usermod -aG "$g" voxip
|
|
done
|
|
|
|
# --- application files --------------------------------------------------------------
|
|
say "Programmabestanden installeren"
|
|
install -d -m 0755 "$LIB" "$SHARE" "$DOC"
|
|
rm -rf "$LIB/python.new"
|
|
install -d -m 0755 "$LIB/python.new"
|
|
tar -C "$SRC/src" --exclude='__pycache__' --exclude='*.pyc' -cf - voxip_kiosk | tar -C "$LIB/python.new" -xf -
|
|
chown -R root:root "$LIB/python.new"
|
|
chmod -R u=rwX,go=rX "$LIB/python.new"
|
|
python3 -m compileall -q "$LIB/python.new" >/dev/null
|
|
rm -rf "$LIB/python.old"
|
|
[[ -d "$LIB/python" ]] && mv "$LIB/python" "$LIB/python.old"
|
|
mv "$LIB/python.new" "$LIB/python"
|
|
rm -rf "$LIB/python.old"
|
|
|
|
for bin in voxip-kiosk-agent voxip-kiosk-browser voxip-kiosk-healthcheck voxip-kiosk-helper voxip-kiosk-manager voxip-kiosk-x; do
|
|
install -m 0755 -o root -g root "$SRC/bin/$bin" "$LIB/$bin"
|
|
done
|
|
install -m 0755 -o root -g root "$SRC/bin/voxip-kiosk-ctl" /usr/local/sbin/voxip-kiosk-ctl
|
|
|
|
rm -rf "$SHARE/www"
|
|
install -d -m 0755 "$SHARE/www"
|
|
install -m 0644 "$SRC"/www/* "$SHARE/www/"
|
|
install -m 0644 "$SRC/VERSION" "$SHARE/VERSION"
|
|
install -m 0644 "$SRC/README.md" "$DOC/README.md"
|
|
install -m 0644 "$SRC"/docs/*.md "$DOC/"
|
|
|
|
# --- configuration -------------------------------------------------------------------
|
|
say "Configuratie"
|
|
install -d -o voxip -g kiosk -m 2750 "$ETC"
|
|
install -d -o voxip -g voxip -m 0750 /var/lib/voxip-kiosk
|
|
install -d -o root -g root -m 0755 /var/lib/voxip-kiosk-network
|
|
install -d -o voxip -g voxip -m 0750 /var/log/voxip-kiosk
|
|
|
|
# Migrate from InfoFlow's raspberry-pi/install-kiosk.sh: take over its URL, then disable it.
|
|
INFOFLOW_UNIT=/etc/systemd/system/display-kiosk.service
|
|
if [[ -f $INFOFLOW_UNIT ]]; then
|
|
OLD_URL="$(grep -oE 'https://[^" ]+' "$INFOFLOW_UNIT" | tail -n1 || true)"
|
|
if [[ -n $OLD_URL && -z $URL && ! -f $ETC/config.json ]]; then
|
|
URL="$OLD_URL"
|
|
say "URL overgenomen van de InfoFlow-kioskinstallatie"
|
|
fi
|
|
if [[ $LIVE -eq 1 ]]; then systemctl disable --now display-kiosk.service 2>/dev/null || true; fi
|
|
mv "$INFOFLOW_UNIT" "$INFOFLOW_UNIT.voxip-disabled"
|
|
fi
|
|
|
|
PYRUN=(python3 -I -c)
|
|
"${PYRUN[@]}" '
|
|
import json, os, sys
|
|
sys.path.insert(0, "/usr/lib/voxip-kiosk/python")
|
|
from voxip_kiosk import config as c
|
|
path, default, url, name = sys.argv[1:5]
|
|
updates = {}
|
|
if url: updates["url"] = url
|
|
if name: updates["device_name"] = name
|
|
if os.path.exists(path):
|
|
if updates:
|
|
c.save_config(c.Path(path), updates)
|
|
print("Bestaande configuratie bijgewerkt:", ", ".join(updates))
|
|
else:
|
|
print("Bestaande configuratie behouden")
|
|
else:
|
|
with open(default, encoding="utf-8") as fh:
|
|
data = json.load(fh)
|
|
data.update(c.validate(updates))
|
|
c.validate(data)
|
|
c.atomic_write_json(c.Path(path), data)
|
|
print("Nieuwe configuratie aangemaakt")
|
|
' "$ETC/config.json" "$SRC/config/config.default.json" "$URL" "$NAME" || die "Ongeldige --url of --name."
|
|
chown voxip:kiosk "$ETC/config.json" "$ETC/config.json.bak" 2>/dev/null || true
|
|
chmod 0640 "$ETC/config.json"
|
|
touch "$ETC/.config.lock" && chown voxip:kiosk "$ETC/.config.lock" && chmod 0640 "$ETC/.config.lock"
|
|
[[ -f $ETC/admin.json ]] && chown voxip:voxip "$ETC/admin.json" && chmod 0600 "$ETC/admin.json"
|
|
|
|
# --- system integration ----------------------------------------------------------------
|
|
say "Systeemintegratie"
|
|
TMP_SUDOERS="$(mktemp)"
|
|
install -m 0440 "$SRC/config/sudoers" "$TMP_SUDOERS"
|
|
visudo -cqf "$TMP_SUDOERS" || die "sudoers-bestand ongeldig"
|
|
mv "$TMP_SUDOERS" /etc/sudoers.d/voxip-kiosk
|
|
chown root:root /etc/sudoers.d/voxip-kiosk
|
|
|
|
install -D -m 0644 "$SRC/config/journald.conf" /etc/systemd/journald.conf.d/voxip-kiosk.conf
|
|
install -D -m 0644 "$SRC/config/watchdog.conf" /etc/systemd/system.conf.d/voxip-watchdog.conf
|
|
install -D -m 0644 "$SRC/config/logrotate" /etc/logrotate.d/voxip-kiosk
|
|
|
|
if [[ -f /etc/X11/Xwrapper.config && ! -f /etc/X11/Xwrapper.config.voxip-orig ]]; then
|
|
cp -a /etc/X11/Xwrapper.config /etc/X11/Xwrapper.config.voxip-orig
|
|
fi
|
|
install -D -m 0644 "$SRC/config/Xwrapper.config" /etc/X11/Xwrapper.config
|
|
|
|
for dir in /etc/chromium/policies/managed /etc/chromium-browser/policies/managed; do
|
|
install -D -m 0644 "$SRC/config/chromium/voxip-kiosk.json" "$dir/voxip-kiosk.json"
|
|
done
|
|
|
|
ADMIN_PORT="$(python3 -I -c 'import sys; sys.path.insert(0, "/usr/lib/voxip-kiosk/python"); from voxip_kiosk.config import load_config; print(load_config(__import__("pathlib").Path(sys.argv[1]))["admin_port"])' "$ETC/config.json")"
|
|
install -d -m 0755 /etc/avahi/services
|
|
sed "s#<port>8080</port>#<port>${ADMIN_PORT}</port>#" "$SRC/config/avahi-voxip.service" > /etc/avahi/services/voxip-kiosk.service
|
|
chmod 0644 /etc/avahi/services/voxip-kiosk.service
|
|
|
|
# Swap: zram in RAM instead of a swap file on the SD card (Trixie's rpi-swap already does this).
|
|
if dpkg -s systemd-zram-generator >/dev/null 2>&1 && ! dpkg -s rpi-swap >/dev/null 2>&1; then
|
|
install -m 0644 "$SRC/config/zram-generator.conf" /etc/systemd/zram-generator.conf
|
|
if dpkg -s dphys-swapfile >/dev/null 2>&1; then
|
|
if [[ $LIVE -eq 1 ]]; then dphys-swapfile swapoff 2>/dev/null || true; fi
|
|
systemctl disable dphys-swapfile.service 2>/dev/null || true
|
|
rm -f /var/swap
|
|
fi
|
|
fi
|
|
|
|
# /tmp in RAM (default on Trixie, opt-in on Bookworm).
|
|
if [[ -f /usr/share/systemd/tmp.mount && ! -e /etc/systemd/system/tmp.mount ]] && ! systemctl is-enabled --quiet tmp.mount 2>/dev/null; then
|
|
cp /usr/share/systemd/tmp.mount /etc/systemd/system/tmp.mount
|
|
systemctl enable tmp.mount 2>/dev/null || true
|
|
fi
|
|
|
|
# Kernel command line: no console blanking (the kiosk runs on vt7, but keep the console
|
|
# awake too), and a quiet boot behind the splash: no boot text, Raspberry logos or cursor.
|
|
add_cmdline() { # add "key" or "key=value" unless the key is already present
|
|
local key="${2%%=*}"
|
|
grep -qE "(^| )${key//./\\.}(=[^ ]*)?( |\$)" "$1" || sed -i "1 s/\$/ $2/" "$1"
|
|
}
|
|
for cmdline in /boot/firmware/cmdline.txt /boot/cmdline.txt; do
|
|
if [[ -f $cmdline ]]; then
|
|
for opt in consoleblank=0 quiet splash plymouth.ignore-serial-consoles logo.nologo vt.global_cursor_default=0; do
|
|
add_cmdline "$cmdline" "$opt"
|
|
done
|
|
config_txt="$(dirname "$cmdline")/config.txt"
|
|
# No rainbow square from the firmware. [all] in case the file ends in a model section.
|
|
if [[ -f $config_txt ]] && ! grep -q '^disable_splash=' "$config_txt"; then
|
|
printf '\n[all]\ndisable_splash=1\n' >> "$config_txt"
|
|
fi
|
|
break
|
|
fi
|
|
done
|
|
|
|
# Boot splash: logo on white (config/plymouth). Rebuilding the initramfs takes a while
|
|
# on a Pi 3, so only when the theme changed.
|
|
SPLASH_DIR=/usr/share/plymouth/themes/voxip-kiosk
|
|
install -d -m 0755 "$SPLASH_DIR"
|
|
install -m 0644 "$SRC"/config/plymouth/voxip-kiosk/* "$SPLASH_DIR/"
|
|
if [[ ! -f /var/lib/voxip-kiosk/previous-plymouth-theme ]]; then
|
|
plymouth-set-default-theme > /var/lib/voxip-kiosk/previous-plymouth-theme 2>/dev/null || true
|
|
fi
|
|
SPLASH_SUM="$(cat "$SPLASH_DIR"/* | sha256sum | cut -d' ' -f1)"
|
|
if [[ "$(plymouth-set-default-theme 2>/dev/null)" != voxip-kiosk || "$(cat /var/lib/voxip-kiosk/splash.sha256 2>/dev/null)" != "$SPLASH_SUM" ]]; then
|
|
say "Opstartscherm instellen (initramfs bijwerken, dit duurt even)"
|
|
plymouth-set-default-theme voxip-kiosk
|
|
if update-initramfs -u -k all >/dev/null 2>&1; then
|
|
echo "$SPLASH_SUM" > /var/lib/voxip-kiosk/splash.sha256
|
|
else
|
|
warn "initramfs bijwerken mislukt; het opstartscherm verschijnt mogelijk pas later tijdens het opstarten."
|
|
fi
|
|
fi
|
|
|
|
# A desktop display manager would fight over vt7 and display :0 (Pi OS Desktop images).
|
|
# uninstall.sh re-enables the ones recorded here.
|
|
for dm in lightdm.service gdm3.service sddm.service; do
|
|
if systemctl is-enabled --quiet "$dm" 2>/dev/null; then
|
|
warn "$dm wordt uitgeschakeld (de kiosk gebruikt een eigen minimale X-sessie)."
|
|
systemctl disable "$dm" 2>/dev/null || true
|
|
grep -qx "$dm" /var/lib/voxip-kiosk/disabled-display-managers 2>/dev/null \
|
|
|| echo "$dm" >> /var/lib/voxip-kiosk/disabled-display-managers
|
|
fi
|
|
if [[ $LIVE -eq 1 ]] && systemctl is-active --quiet "$dm" 2>/dev/null; then
|
|
warn "$dm wordt gestopt: de desktop op het scherm sluit nu af."
|
|
systemctl stop "$dm" || true
|
|
fi
|
|
done
|
|
|
|
# --- services ------------------------------------------------------------------------------
|
|
say "Services installeren"
|
|
for unit in "${UNITS[@]}"; do
|
|
install -m 0644 "$SRC/systemd/$unit" "/etc/systemd/system/$unit"
|
|
done
|
|
if [[ $LIVE -eq 1 ]]; then systemctl daemon-reload; fi
|
|
systemctl enable "${ENABLE[@]}" NetworkManager.service avahi-daemon.service >/dev/null
|
|
if [[ ! -f /var/lib/voxip-kiosk/previous-default-target ]]; then
|
|
systemctl get-default > /var/lib/voxip-kiosk/previous-default-target 2>/dev/null || true
|
|
fi
|
|
systemctl set-default multi-user.target >/dev/null
|
|
|
|
if [[ -n $NEW_HOSTNAME && $LIVE -eq 0 ]]; then
|
|
warn "--hostname wordt genegeerd in image-modus (gebruik TARGET_HOSTNAME van pi-gen)."
|
|
elif [[ -n $NEW_HOSTNAME ]]; then
|
|
"$LIB/voxip-kiosk-helper" set-hostname "$NEW_HOSTNAME" >/dev/null || die "Ongeldige hostname: $NEW_HOSTNAME"
|
|
fi
|
|
|
|
if [[ $LIVE -eq 0 ]]; then
|
|
say "Klaar (image-modus: services starten bij de eerste boot)"
|
|
exit 0
|
|
fi
|
|
|
|
say "Services starten"
|
|
systemctl restart systemd-journald.service || true
|
|
systemctl restart avahi-daemon.service || true
|
|
systemctl restart voxip-kiosk-manager.service voxip-kiosk-agent.service
|
|
systemctl start voxip-kiosk-healthcheck.timer
|
|
systemctl reset-failed voxip-kiosk-x.service voxip-kiosk-browser.service 2>/dev/null || true
|
|
systemctl restart voxip-kiosk-x.service
|
|
systemctl restart voxip-kiosk-browser.service
|
|
|
|
HOST="$(hostname)"
|
|
SCHEME=http
|
|
grep -q '"admin_https": true' "$ETC/config.json" && SCHEME=https
|
|
echo
|
|
say "VoxIP Kiosk OS $VERSION is geïnstalleerd."
|
|
echo " Beheerinterface: ${SCHEME}://${HOST}.local:${ADMIN_PORT}"
|
|
for ip in $(hostname -I 2>/dev/null); do
|
|
[[ $ip == *:* ]] || echo " ${SCHEME}://${ip}:${ADMIN_PORT}"
|
|
done
|
|
if [[ ! -f $ETC/admin.json ]]; then
|
|
for _ in $(seq 1 20); do [[ -f $ETC/setup-code ]] && break; sleep 0.5; done
|
|
if [[ -f $ETC/setup-code ]]; then
|
|
echo " Setupcode: $(cat "$ETC/setup-code") (staat ook op het scherm)"
|
|
fi
|
|
fi
|
|
echo " Status: sudo voxip-kiosk-ctl status"
|