Files
rothmanandClaude Opus 5.5 eb0242c182
CI / test-bookworm (push) Successful in 29s
CI / test-latest (push) Successful in 31s
Rename every remaining voxip name to daynivo, with a one-time migration
Services (daynivo-kiosk-*, daynivo-network-guard, daynivo-boot-import),
programs in /usr/lib/daynivo-kiosk, folders /etc, /var/lib, /var/log and
/var/cache daynivo-kiosk*, the system user daynivo, DAYNIVO_* settings,
the plymouth theme, network profiles daynivo-eth0/wlan0, the image stage
and its first user daynivoadmin.

install.sh moves a kiosk installed up to 1.2.x over once: old services
stopped and removed, folders moved (configuration, password, browser
profile, logs) with symlinks under the old names, the voxip user renamed
with the same uid, network profiles renamed unless a change awaits
confirmation, and old leftovers removed. Tested by upgrading a configured
1.2.0 installation in a container, and running the install a second time.

Old names keep working where people type or place them: voxip-config.json,
voxip-authorized_keys, voxip-network-reset, voxip-kiosk-ctl, and
VOXIP_DEFAULT_URL / VOXIP_DEVICE_NAME for image builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 23:58:03 +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/daynivo-kiosk/config.json en kan bij uitrol via de bootpartitie worden meegegeven (daynivo-config.json).

Overzicht

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

Processen en rechten

Onderdeel Gebruiker Taak
daynivo-kiosk-x kiosk (via Xorg.wrap) Minimale X-server op vt7, zonder cursor, zonder TCP.
daynivo-kiosk-browser kiosk Past rotatie/resolutie/DPMS toe (xrandr/xset), ruimt een vastgelopen profiel op en start Chromium.
daynivo-kiosk-agent kiosk Bestuurt Chromium via het DevTools Protocol op localhost: offline-pagina, URL-wissel zonder herstart, periodiek verversen, hang-detectie.
daynivo-kiosk-manager daynivo Flask-webinterface (gunicorn, 1 worker, 4 threads).
daynivo-kiosk-helper root (via sudo) Enige geprivilegieerde ingang: reboot, uitschakelen, Chromium herstarten, hostname, wifi-land, wifi-scan, netwerkconfiguratie + rollback, updates controleren en starten.
daynivo-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.
daynivo-kiosk-healthcheck root Bewaakt alle services en herstelt ze, met logging.

De webinterface draait nooit als root. De sudoers-regel staat uitsluitend /usr/lib/daynivo-kiosk/daynivo-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. daynivo-kiosk-browser heeft Requires= en PartOf= op daynivo-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/daynivo-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 (daynivo-eth0 / daynivo-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 daynivo-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/daynivo-kiosk/config.json: eigenaar daynivo, 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/daynivo-kiosk/admin.json: Argon2id-hash, 0600, alleen daynivo.
  • 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/daynivo-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-daynivo)
├── tests/                       pytest-suite
└── docs/                        architectuur, hardwaretest