Files
daynivo-kiosk-os/docs/ARCHITECTURE.md
T
rothmanandClaude Opus 5.5 6733aacb6e
CI / test-bookworm (push) Successful in 29s
CI / test-latest (push) Successful in 33s
Rename the Python package to daynivo_kiosk
src/voxip_kiosk -> src/daynivo_kiosk, with every import, the launchers,
install.sh, the Makefile and the manager's gunicorn app path. An upgrade
replaces /usr/lib/voxip-kiosk/python as a whole, so the old package
disappears; tested by upgrading an installation of the previous commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 23:52:39 +01:00

12 KiB

Architectuur — Daynivo Kiosk OS

Dit document is het architectuurvoorstel waarop de implementatie in deze repository gebaseerd is. Het beschrijft de onderdelen, hun rechten, hoe ze samenwerken en waarom bepaalde keuzes zijn gemaakt.

Doel en randvoorwaarden

  • Raspberry Pi 3 Model B/B+ (1 GB RAM), Raspberry Pi OS Lite 32-bit (Bookworm of Trixie), 24/7 in bedrijf.
  • Eén HTTPS-pagina fullscreen in Chromium, zonder desktopomgeving.
  • Lokale webbeheerinterface (Daynivo Kiosk Manager) op poort 8080.
  • Geen klantspecifieke code: alles wat per klant verschilt staat in /etc/voxip-kiosk/config.json en kan bij uitrol via de bootpartitie worden meegegeven (daynivo-config.json).

Overzicht

                ┌──────────────────────── Raspberry Pi ────────────────────────┐
                │                                                              │
  HDMI ◄────────┤  voxip-kiosk-x.service       Xorg :0 op vt7 (user kiosk)     │
                │        ▲                                                     │
                │        │ DISPLAY=:0                                          │
                │  voxip-kiosk-browser.service Chromium --kiosk (user kiosk)   │
                │        ▲      CDP 127.0.0.1:9222                             │
                │        │  ┌─────────────────────────────┐                    │
                │        ├──┤ voxip-kiosk-agent.service   │ navigatie, offline-│
                │        │  │ (user kiosk)                │ pagina, verversen, │
                │        │  └─────────────────────────────┘ hang-detectie      │
                │        │                                                     │
  LAN :8080 ───►│  voxip-kiosk-manager.service  Flask + gunicorn (user voxip)  │
                │        │  sudo -n (één commando)                             │
                │        ▼                                                     │
                │  voxip-kiosk-helper           root, vaste werkwoorden,       │
                │                               strikte invoervalidatie        │
                │                                                              │
                │  voxip-kiosk-healthcheck.timer   elke minuut, root           │
                │  voxip-network-guard.service     netwerk-rollback bij boot   │
                │  voxip-boot-import.service       config/SSH-sleutels vanaf   │
                │                                  de bootpartitie             │
                └──────────────────────────────────────────────────────────────┘

Processen en rechten

Onderdeel Gebruiker Taak
voxip-kiosk-x kiosk (via Xorg.wrap) Minimale X-server op vt7, zonder cursor, zonder TCP.
voxip-kiosk-browser kiosk Past rotatie/resolutie/DPMS toe (xrandr/xset), ruimt een vastgelopen profiel op en start Chromium.
voxip-kiosk-agent kiosk Bestuurt Chromium via het DevTools Protocol op localhost: offline-pagina, URL-wissel zonder herstart, periodiek verversen, hang-detectie.
voxip-kiosk-manager voxip Flask-webinterface (gunicorn, 1 worker, 4 threads).
voxip-kiosk-helper root (via sudo) Enige geprivilegieerde ingang: reboot, uitschakelen, Chromium herstarten, hostname, wifi-land, wifi-scan, netwerkconfiguratie + rollback, updates controleren en starten.
voxip-kiosk-updater root (timer, of eigen transient unit via de helper) Haalt de nieuwste release op (HTTPS, SHA-256), pakt hem uit in /opt/daynivo-kiosk-os/<versie> en draait update.sh --no-pull. Nooit een oudere of gelijke versie.
voxip-kiosk-healthcheck root Bewaakt alle services en herstelt ze, met logging.

De webinterface draait nooit als root. De sudoers-regel staat uitsluitend /usr/lib/voxip-kiosk/voxip-kiosk-helper toe. De helper:

  • draait met python3 -I (geen PYTHONPATH, geen user site-packages);
  • kent alleen een vaste lijst werkwoorden; interne werkwoorden (zoals network-activate) weigert hij als hij via sudo wordt aangeroepen;
  • valideert iedere parameter opnieuw (de webinterface is niet vertrouwd);
  • start alle subprocessen met een argumentenlijst, nooit via een shell;
  • schrijft netwerkprofielen zelf als NetworkManager-keyfile (0600), zodat wachtwoorden nooit op een commandoregel verschijnen.

Waarom X11 + twee services

Xorg en Chromium zijn aparte services. Daardoor is Chromium herstarten snel (X blijft draaien) en geeft het exitcode van Chromium direct aan systemd door. voxip-kiosk-browser heeft Requires= en PartOf= op voxip-kiosk-x: valt X weg, dan start de browser opnieuw mee.

Chromium besturen via CDP

Chromium luistert met --remote-debugging-address=127.0.0.1 --remote-debugging-port=9222, dus alleen lokaal. De agent en de webinterface gebruiken dit om:

  • de pagina te herladen zonder de browser te herstarten;
  • naar een nieuwe URL te navigeren na een configuratiewijziging;
  • een screenshot te maken;
  • te detecteren of de renderer nog reageert (Runtime.evaluate).

Offline-gedrag

Chromium start altijd op een lokale startpagina (/usr/share/voxip-kiosk/www/start.html). De agent controleert met een HTTPS-verzoek (mét certificaatvalidatie) of de doel-URL bereikbaar is en navigeert pas dan. De offline-pagina verschijnt alleen als de pagina niet geladen kan worden (startpagina, Chromium-foutpagina). Valt het netwerk weg terwijl een pagina al draait, dan blijft die pagina staan. Zo blijven applicaties met een eigen offline-cache (zoals Daynivo- displays) gewoon hun laatste inhoud tonen. Automatisch verversen wordt overgeslagen zolang de doel-URL onbereikbaar is.

Initiële configuratie

Zolang er geen beheerderswachtwoord is:

  1. maakt de webinterface een eenmalige setupcode aan;
  2. toont de kiosk op het HDMI-scherm een setuppagina met die code en de adressen van de beheerinterface;
  3. kan een wachtwoord alleen ingesteld worden met die code.

Wie fysiek bij het scherm staat (of via SSH daynivo-kiosk-ctl setup-code draait) kan het apparaat dus claimen, iemand anders op het netwerk niet.

Netwerkwijzigingen met rollback

  1. De helper maakt een snapshot van /etc/NetworkManager/system-connections en van de actieve verbindingen.
  2. Hij schrijft het nieuwe profiel (voxip-eth0 / voxip-wlan0, prioriteit 100) en plant een rollback-timer (systemd-run, 180 s).
  3. Activatie gebeurt asynchroon, zodat het HTTP-antwoord nog aankomt.
  4. De beheerder bevestigt binnen 180 s (eventueel via het nieuwe adres), anders zet de timer de snapshot terug.
  5. Valt de stroom uit tijdens die periode, dan zet voxip-network-guard.service de snapshot terug bij het opstarten, nog vóór NetworkManager start.
  6. Noodherstel: een leeg bestand daynivo-network-reset op de bootpartitie verwijdert bij de volgende boot alle Daynivo-netwerkprofielen (terug naar DHCP).

Configuratie

  • /etc/voxip-kiosk/config.json: eigenaar voxip, groep kiosk, 0640. Wordt atomair opgeslagen (tijdelijk bestand, fsync, rename, fsync van de map) met een .bak van de vorige versie. Ongeldige waarden vallen bij het laden terug op de standaard, zodat de kiosk altijd start. Onbekende sleutels blijven bewaard (vooruit-/achteruitcompatibel).
  • /etc/voxip-kiosk/admin.json: Argon2id-hash, 0600, alleen voxip.
  • Updates (install.sh --upgrade / update.sh / de updater) overschrijven nooit bestaande configuratie. De updater draait in een eigen systemd-unit, zodat het herstarten van de beheerinterface tijdens install.sh de update niet afbreekt. De checksum komt van dezelfde server als de download: hij vangt een kapotte download af, geen gehackte releaseserver.

Betrouwbaarheid (24/7)

  • Alle services Restart= met StartLimitBurst; de healthcheck logt een start-limit-hit als fout en probeert na 5 minuten opnieuw. Er zijn dus geen stille herstartloops en geen permanente uitval.
  • Hardware-watchdog (RuntimeWatchdogSec=15s): een vastgelopen kernel zorgt voor een automatische reboot.
  • Chromium-profiel: profielvergrendelingen en de crash-vlag worden voor iedere start opgeruimd, zodat er na stroomuitval geen dialoog verschijnt.
  • HTTP-cache van Chromium in RAM (/run), het profiel zelf (cookies, localStorage/IndexedDB) blijft standaard persistent zodat offline-caches van webapps een reboot overleven (browser_profile: "ephemeral" zet ook dat in RAM).
  • journald maximaal 32 MB en synchroniseert eens per 15 minuten; /tmp als tmpfs; zram in plaats van swap op de SD-kaart.
  • Logrotatie voor het audit-log van de beheerinterface.

Beveiliging

  • Argon2id (argon2-cffi, 64 MiB, t=3, p=2), geen standaardwachtwoord.
  • Sessiecookie: HttpOnly, SameSite=Strict, Secure bij HTTPS, 60 min inactiviteit, ongeldig na een wachtwoordwijziging (sessie-epoch).
  • CSRF-token op iedere POST, plus controle van de Origin-header.
  • Login-throttling per IP (exponentieel tot 15 min) en globaal.
  • Beheerinterface weigert standaard verzoeken van publieke IP-adressen (toegestaan: privé, link-local, loopback, CGNAT/VPN 100.64.0.0/10).
  • Optioneel HTTPS (zelfondertekend certificaat, automatisch aangemaakt).
  • SSH staat in de image standaard uit en wordt alleen ingeschakeld met credentials uit Raspberry Pi Imager of daynivo-authorized_keys op de bootpartitie (dan alleen sleutel-login).
  • Strikte security-headers en CSP (default-src 'self').

Directorystructuur

daynivo-kiosk-os/
├── README.md                    bouw-, flash-, installatie-instructies
├── VERSION
├── Makefile                     test / lint / image
├── install.sh                   installatie op bestaande Pi OS + gebruikt door image-build
├── update.sh                    update met backup van configuratie
├── uninstall.sh                 verwijderen (--purge voor alles)
├── bin/                         dunne launchers → /usr/lib/voxip-kiosk/
├── src/daynivo_kiosk/             Python-package (alle logica)
│   ├── config.py                laden/valideren/atomair opslaan
│   ├── validation.py            gedeelde invoervalidatie
│   ├── cdp.py                   Chrome DevTools Protocol-client
│   ├── browser.py               Chromium-launcher (xrandr, xset, profiel)
│   ├── agent.py                 kiosk-agent
│   ├── healthcheck.py           healthcheck
│   ├── helper.py                root-helper
│   ├── netconfig.py             NetworkManager-keyfiles, nmcli-parsing
│   ├── netinfo.py               netwerkstatus (zonder rechten)
│   ├── sysinfo.py               CPU, temperatuur, geheugen, opslag, IP
│   ├── services.py              systemd- en journal-status
│   ├── ctl.py                   CLI voor SSH-beheer (daynivo-kiosk-ctl)
│   └── web/                     Flask-app, templates, statische bestanden
├── www/                         lokale kioskpagina's (start, offline, setup)
├── systemd/                     unit-bestanden
├── config/                      standaardconfig, sudoers, journald, logrotate, …
├── image/                       pi-gen image-builder (stage-voxip)
├── tests/                       pytest-suite
└── docs/                        architectuur, hardwaretest