VoxIP Kiosk OS 1.0.0: Raspberry Pi kiosk with web management
CI / test-bookworm (push) Failing after 11s
CI / test-latest (push) Successful in 1m27s

Chromium kiosk without a desktop for the Raspberry Pi 3, a Flask web
interface on port 8080 (kiosk, system and network management), a
restricted root helper, network changes with automatic rollback, a
healthcheck timer, install/update/uninstall scripts and a pi-gen image
build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rothmanandClaude Opus 5.5 committed 2026-11-04 21:38:49 +01:00
commit a33c4822bf
96 files changed
+7535

No files matched your search

+35
View File
@@ -0,0 +1,35 @@
VoxIP Kiosk OS — bestanden op deze bootpartitie
================================================
Je kunt een kiosk vooraf instellen door vóór de eerste start bestanden op
deze partitie te zetten (bijvoorbeeld direct na het flashen). Ze worden bij
het opstarten verwerkt en daarna hernoemd naar *.applied (of *.rejected bij
een fout; de reden staat dan in voxip-config.error.txt).
voxip-config.json
Instellingen voor deze kiosk. Zie voxip-config.example.json. Toegestaan:
alle sleutels uit /etc/voxip-kiosk/config.json plus "hostname" en
"wifi_country". Voorbeeld:
{
"device_name": "Hal begane grond",
"url": "https://schermen-klant.voxip.nl/display/hal?token=…",
"hostname": "hal-bg",
"rotation": 90
}
voxip-authorized_keys
Openbare SSH-sleutels (één per regel). SSH wordt dan ingeschakeld, alleen
met sleutel-login, voor de beheerder (voxipadmin of de gebruiker die je
in Raspberry Pi Imager hebt aangemaakt).
voxip-network-reset
Leeg bestand. Verwijdert bij de volgende start alle via de
beheerinterface ingestelde netwerkprofielen (terug naar DHCP). Gebruik
dit als de kiosk na een netwerkwijziging onbereikbaar is.
Wifi, gebruikersnaam/wachtwoord en SSH stel je het makkelijkst in met de
"OS-aanpassingen" van Raspberry Pi Imager.
Beheerinterface: http://kiosk.local:8080 (of http://<hostname>.local:8080).
De setupcode voor het eerste wachtwoord staat op het scherm.
Executable
+123
View File
@@ -0,0 +1,123 @@
#!/usr/bin/env bash
#
# Build a flashable VoxIP Kiosk OS image (.img.xz) with pi-gen.
#
# ./image/build.sh # build in Docker (recommended)
# sudo ./image/build.sh --native # build directly on a Debian/Ubuntu host
#
# Environment (all optional):
# PI_GEN_REF pi-gen tag/branch (32-bit armhf). Default: pinned Bookworm release.
# RELEASE bookworm | trixie (must match PI_GEN_REF). Default: bookworm.
# VOXIP_DEFAULT_URL kiosk URL baked into the default config.
# VOXIP_DEVICE_NAME default device name.
# TARGET_HOSTNAME default hostname (the admin UI is at http://<hostname>.local:8080).
# ALLOW_DIRTY=1 build from the working tree instead of the committed HEAD.
#
# Output: image/deploy/<date>-voxip-kiosk-os-<version>.img.xz (+ .sha256)
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
WORK="${WORK_DIR:-$ROOT/image/work}"
OUT="$ROOT/image/deploy"
PI_GEN_REPO="${PI_GEN_REPO:-https://github.com/RPi-Distro/pi-gen.git}"
PI_GEN_REF="${PI_GEN_REF:-2024-11-19-raspios-bookworm-armhf}"
RELEASE="${RELEASE:-bookworm}"
VOXIP_DEFAULT_URL="${VOXIP_DEFAULT_URL:-https://schermen-ijsselflat.voxip.nl}"
VOXIP_DEVICE_NAME="${VOXIP_DEVICE_NAME:-VoxIP Kiosk}"
TARGET_HOSTNAME="${TARGET_HOSTNAME:-kiosk}"
NATIVE=0
[[ ${1:-} == "--native" ]] && NATIVE=1
[[ ${1:-} == "-h" || ${1:-} == "--help" ]] && { sed -n '3,18p' "$0" | sed 's/^# \{0,1\}//'; exit 0; }
VERSION="$(tr -d '[:space:]' < "$ROOT/VERSION")"
command -v git >/dev/null || { echo "git is vereist" >&2; exit 1; }
if [[ $NATIVE -eq 0 ]]; then
command -v docker >/dev/null || { echo "Docker is vereist (of gebruik --native op Debian/Ubuntu)" >&2; exit 1; }
else
[[ $EUID -eq 0 ]] || { echo "--native moet met sudo" >&2; exit 1; }
fi
echo "==> pi-gen $PI_GEN_REF ophalen"
mkdir -p "$WORK" "$OUT"
rm -rf "$WORK/pi-gen"
git clone --quiet --depth 1 --branch "$PI_GEN_REF" "$PI_GEN_REPO" "$WORK/pi-gen"
PI_GEN_COMMIT="$(git -C "$WORK/pi-gen" rev-parse HEAD)"
PIGEN="$WORK/pi-gen"
# Do not export the plain Lite image from stage2; ours is exported after stage-voxip.
touch "$PIGEN/stage2/SKIP_IMAGES"
rm -f "$PIGEN/stage2/EXPORT_NOOBS"
echo "==> VoxIP-stage voorbereiden"
cp -a "$ROOT/image/stage-voxip" "$PIGEN/stage-voxip"
PAYLOAD="$PIGEN/stage-voxip/00-voxip/files/voxip-kiosk"
mkdir -p "$PAYLOAD"
if [[ ${ALLOW_DIRTY:-0} == 1 ]] || ! git -C "$ROOT" rev-parse --git-dir >/dev/null 2>&1; then
echo " (werkmap wordt gebruikt, niet de laatste commit)"
tar -C "$ROOT" --exclude=./image/work --exclude=./image/deploy --exclude=./.git --exclude=./.venv \
--exclude='__pycache__' --exclude=.pytest_cache -cf - . | tar -C "$PAYLOAD" -xf -
SOURCE_REV="werkmap"
else
if [[ -n "$(git -C "$ROOT" status --porcelain)" ]]; then
echo " Let op: niet-gecommitte wijzigingen worden NIET meegenomen (ALLOW_DIRTY=1 om dat wel te doen)."
fi
git -C "$ROOT" archive HEAD | tar -C "$PAYLOAD" -xf -
SOURCE_REV="$(git -C "$ROOT" rev-parse HEAD)"
fi
BUILD_DATE="$(date -u +%Y-%m-%d)"
cat > "$PIGEN/stage-voxip/00-voxip/files/build.env" <<EOF
VOXIP_DEFAULT_URL=$(printf %q "$VOXIP_DEFAULT_URL")
VOXIP_DEVICE_NAME=$(printf %q "$VOXIP_DEVICE_NAME")
EOF
cat > "$PAYLOAD/BUILD-INFO" <<EOF
voxip_kiosk_os=$VERSION
source_revision=$SOURCE_REV
pi_gen_ref=$PI_GEN_REF
pi_gen_commit=$PI_GEN_COMMIT
release=$RELEASE
build_date=$BUILD_DATE
EOF
# The image has no usable default password: the first user is locked and is
# only unlocked by credentials from Raspberry Pi Imager (or SSH keys placed
# on the boot partition, see README).
FIRST_USER_PASS="$(openssl rand -base64 33 | tr -d '\n/+=')"
cat > "$PIGEN/config" <<EOF
IMG_NAME=voxip-kiosk-os-$VERSION
IMG_DATE=$BUILD_DATE
RELEASE=$RELEASE
DEPLOY_COMPRESSION=xz
COMPRESSION_LEVEL=6
LOCALE_DEFAULT=nl_NL.UTF-8
KEYBOARD_KEYMAP=us
KEYBOARD_LAYOUT="English (US)"
TIMEZONE_DEFAULT=Europe/Amsterdam
WPA_COUNTRY=NL
TARGET_HOSTNAME=$TARGET_HOSTNAME
FIRST_USER_NAME=voxipadmin
FIRST_USER_PASS=$FIRST_USER_PASS
DISABLE_FIRST_BOOT_USER_RENAME=1
ENABLE_SSH=0
STAGE_LIST="stage0 stage1 stage2 stage-voxip"
EOF
echo "==> Image bouwen (dit duurt 30–90 minuten)"
cd "$PIGEN"
if [[ $NATIVE -eq 1 ]]; then
./build.sh
else
CONTINUE=0 PRESERVE_CONTAINER=0 ./build-docker.sh
fi
shopt -s nullglob
images=("$PIGEN"/deploy/*.img.xz)
[[ ${#images[@]} -gt 0 ]] || { echo "Geen image gevonden in $PIGEN/deploy" >&2; exit 1; }
for img in "${images[@]}"; do
cp "$img" "$OUT/"
(cd "$OUT" && sha256sum "$(basename "$img")" > "$(basename "$img").sha256")
echo "==> Klaar: $OUT/$(basename "$img")"
done
cp "$PAYLOAD/BUILD-INFO" "$OUT/BUILD-INFO-$VERSION.txt"
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash -e
# Install VoxIP Kiosk OS into the image with the same install.sh that is
# used on existing Raspberry Pi OS installations.
# shellcheck disable=SC1091
source files/build.env
rm -rf "${ROOTFS_DIR}/usr/src/voxip-kiosk"
install -d "${ROOTFS_DIR}/usr/src/voxip-kiosk"
cp -a files/voxip-kiosk/. "${ROOTFS_DIR}/usr/src/voxip-kiosk/"
on_chroot << EOF_CHROOT
/usr/src/voxip-kiosk/install.sh --image --url $(printf %q "${VOXIP_DEFAULT_URL}") --name $(printf %q "${VOXIP_DEVICE_NAME}")
install -m 0644 /usr/src/voxip-kiosk/BUILD-INFO /usr/share/voxip-kiosk/BUILD-INFO
# No usable default password: unlocked only by Raspberry Pi Imager credentials.
passwd -l ${FIRST_USER_NAME}
systemctl disable ssh.service 2>/dev/null || true
apt-get clean
EOF_CHROOT
# Instructions next to config.txt for whoever opens the boot partition.
install -m 0644 files/voxip-kiosk/image/boot-README.txt "${ROOTFS_DIR}/boot/firmware/VOXIP-LEESMIJ.txt"
install -m 0644 files/voxip-kiosk/image/voxip-config.example.json "${ROOTFS_DIR}/boot/firmware/voxip-config.example.json"
+4
View File
@@ -0,0 +1,4 @@
IMG_SUFFIX=""
if [ "${USE_QEMU}" = "1" ]; then
export IMG_SUFFIX="${IMG_SUFFIX}-qemu"
fi
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash -e
# Start from the root filesystem of the previous stage (stage2 = Raspberry Pi OS Lite).
if [ ! -d "${ROOTFS_DIR}" ]; then
copy_previous
fi
+10
View File
@@ -0,0 +1,10 @@
{
"device_name": "Hal begane grond",
"url": "https://schermen-ijsselflat.voxip.nl",
"hostname": "hal-bg",
"wifi_country": "NL",
"refresh_interval": 0,
"rotation": 0,
"resolution": "auto",
"screen_always_on": true
}