VoxIP Kiosk OS 1.0.0: Raspberry Pi kiosk with web management
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:
commit
a33c4822bf
96 files changed
+7535
No files matched your search
@@ -0,0 +1,199 @@
|
||||
# Architectuur — VoxIP 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 (*VoxIP 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 (`voxip-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. |
|
||||
| `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 InfoFlow-
|
||||
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 `voxip-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 `voxip-network-reset` op de bootpartitie
|
||||
verwijdert bij de volgende boot alle VoxIP-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`) overschrijven nooit
|
||||
bestaande configuratie.
|
||||
|
||||
### 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 `voxip-authorized_keys` op de
|
||||
bootpartitie (dan alleen sleutel-login).
|
||||
- Strikte security-headers en CSP (`default-src 'self'`).
|
||||
|
||||
## Directorystructuur
|
||||
|
||||
```
|
||||
voxip-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/voxip_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 (voxip-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
|
||||
```
|
||||
Reference in new issue
Block a user