Rename every remaining voxip name to daynivo, with a one-time migration
CI / test-bookworm (push) Successful in 29s
CI / test-latest (push) Successful in 31s

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>
This commit is contained in:
rothmanandClaude Opus 5.5 committed 2026-11-05 23:58:03 +01:00
1 parent 6733aacb6e
commit eb0242c182
68 files changed
+527 -424

No files matched your search

+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
- name: Tests
run: python3 -m pytest -p no:cacheprovider
- name: Shellcheck
run: shellcheck -S warning install.sh install-online.sh update.sh uninstall.sh tools/release-upload.sh image/build.sh image/stage-voxip/prerun.sh image/stage-voxip/00-voxip/00-run.sh bin/voxip-kiosk-x
run: shellcheck -S warning install.sh install-online.sh update.sh uninstall.sh tools/release-upload.sh image/build.sh image/stage-daynivo/prerun.sh image/stage-daynivo/00-daynivo/00-run.sh bin/daynivo-kiosk-x
test-latest:
runs-on: ubuntu-latest
+6 -6
View File
@@ -1,8 +1,8 @@
PY ?= .venv/bin/python
DEV := $(CURDIR)/.dev
DEV_ENV := PYTHONPATH=src VOXIP_CONFIG_DIR=$(DEV)/etc VOXIP_STATE_DIR=$(DEV)/state VOXIP_LOG_DIR=$(DEV)/log \
VOXIP_SHARE_DIR=$(DEV)/share VOXIP_AGENT_RUN_DIR=$(DEV)/run VOXIP_BROWSER_RUN_DIR=$(DEV)/run \
VOXIP_NETWORK_STATE_DIR=$(DEV)/net VOXIP_HELPER=/nonexistent/voxip-kiosk-helper
DEV_ENV := PYTHONPATH=src DAYNIVO_CONFIG_DIR=$(DEV)/etc DAYNIVO_STATE_DIR=$(DEV)/state DAYNIVO_LOG_DIR=$(DEV)/log \
DAYNIVO_SHARE_DIR=$(DEV)/share DAYNIVO_AGENT_RUN_DIR=$(DEV)/run DAYNIVO_BROWSER_RUN_DIR=$(DEV)/run \
DAYNIVO_NETWORK_STATE_DIR=$(DEV)/net DAYNIVO_HELPER=/nonexistent/daynivo-kiosk-helper
.PHONY: venv test lint test-bookworm dev image clean
@@ -13,8 +13,8 @@ test:
$(PY) -m pytest
lint:
shellcheck -S warning install.sh update.sh uninstall.sh image/build.sh image/stage-voxip/prerun.sh \
image/stage-voxip/00-voxip/00-run.sh bin/voxip-kiosk-x
shellcheck -S warning install.sh update.sh uninstall.sh image/build.sh image/stage-daynivo/prerun.sh \
image/stage-daynivo/00-daynivo/00-run.sh bin/daynivo-kiosk-x
# The test suite against the exact Python/Flask/argon2 versions that Raspberry Pi OS Bookworm ships.
test-bookworm:
@@ -22,7 +22,7 @@ test-bookworm:
apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends \
python3 python3-flask python3-argon2 python3-websocket python3-pytest shellcheck >/dev/null && \
cp -r /src /work && cd /work && rm -rf .venv && python3 -m pytest -p no:cacheprovider && \
shellcheck -S warning install.sh install-online.sh update.sh uninstall.sh tools/release-upload.sh image/build.sh bin/voxip-kiosk-x'
shellcheck -S warning install.sh install-online.sh update.sh uninstall.sh tools/release-upload.sh image/build.sh bin/daynivo-kiosk-x'
# Web interface on http://127.0.0.1:8080 with throwaway state in .dev/ (system actions fail gracefully).
dev:
+41 -16
View File
@@ -12,7 +12,7 @@ webinterface **Daynivo Kiosk Manager** op `http://kiosk.local:8080`.
een setupcode die op het scherm staat.
- Netwerkwijzigingen met automatische rollback.
- Geen klantspecifieke code: alles per klant staat in
`/etc/voxip-kiosk/config.json` en kan bij uitrol via de bootpartitie
`/etc/daynivo-kiosk/config.json` en kan bij uitrol via de bootpartitie
worden meegegeven.
De architectuur en de veiligheidskeuzes staan in
@@ -61,7 +61,7 @@ sudo ./install.sh --url "https://schermen-ijsselflat.voxip.nl" --name "IJsselfla
```
Alle opties zijn optioneel. Het script installeert de benodigde pakketten,
maakt de gebruikers `kiosk` en `voxip` aan, installeert de services en
maakt de gebruikers `kiosk` en `daynivo` aan, installeert de services en
start de kiosk. Aan het eind toont het de setupcode.
Draaide hier eerder de Daynivo-kiosk (`raspberry-pi/install-kiosk.sh` uit
@@ -72,14 +72,14 @@ de Daynivo-repository)? Dan neemt `install.sh` de display-URL (inclusief
In de webinterface onder **Systeem → Software-updates**: *Nu controleren*,
*Bijwerken naar …*, en het vinkje **Automatisch bijwerken**. Elke nacht
tussen 03:00 en 05:00 (`voxip-kiosk-update.timer`) kijkt de kiosk of er een
tussen 03:00 en 05:00 (`daynivo-kiosk-update.timer`) kijkt de kiosk of er een
nieuwe release is; met het vinkje aan installeert hij die meteen. Dat werkt
ook op een apparaat dat met de image of vanuit een clone is geïnstalleerd.
Vanaf de commandoregel:
```bash
sudo /usr/lib/voxip-kiosk/voxip-kiosk-updater --check # alleen kijken
sudo /usr/lib/voxip-kiosk/voxip-kiosk-updater --install # nieuwste release installeren
sudo /usr/lib/daynivo-kiosk/daynivo-kiosk-updater --check # alleen kijken
sudo /usr/lib/daynivo-kiosk/daynivo-kiosk-updater --install # nieuwste release installeren
```
De updater installeert alleen een nieuwere versie met een kloppende
@@ -89,15 +89,40 @@ het ook met git:
```bash
cd daynivo-kiosk-os
sudo ./update.sh # git pull --ff-only + back-up van /etc/voxip-kiosk + install.sh --upgrade
sudo ./update.sh # git pull --ff-only + back-up van /etc/daynivo-kiosk + install.sh --upgrade
```
Een update overschrijft nooit bestaande configuratie of het
beheerderswachtwoord. Back-ups staan in `/var/backups/voxip-kiosk-*.tar.gz`
beheerderswachtwoord. Back-ups staan in `/var/backups/daynivo-kiosk-*.tar.gz`
(de laatste vijf). Op een apparaat met de image staat de broncode in
`/usr/src/voxip-kiosk`. Clone de repository voor updates naar een eigen map,
`/usr/src/daynivo-kiosk`. Clone de repository voor updates naar een eigen map,
of pak een release uit en draai daar `sudo ./update.sh --no-pull`.
### Van de VoxIP-namen naar Daynivo
Kiosks tot en met versie 1.2.x gebruiken nog de oude namen: services
`voxip-kiosk-*`, mappen `/etc/voxip-kiosk`, `/var/lib/voxip-kiosk*` en
`/usr/lib/voxip-kiosk`, en de gebruiker `voxip`. De eerste update naar een
nieuwere versie zet zo'n kiosk eenmalig over, gewoon via de webinterface of
's nachts automatisch:
- services, programma's en instellingen krijgen de `daynivo-`-namen; de oude
services worden uitgeschakeld en verwijderd;
- configuratie, beheerderswachtwoord, browserprofiel (offline-cache), logs en
updatestatus verhuizen naar de `daynivo-`-mappen; de oude mapnamen blijven
als verwijzing bestaan;
- de gebruiker `voxip` wordt `daynivo`, met hetzelfde uid, zodat alle
bestanden hun eigenaar houden;
- de netwerkprofielen `voxip-eth0`/`voxip-wlan0` worden `daynivo-eth0`/
`daynivo-wlan0` (niet zolang er een netwerkwijziging op bevestiging wacht;
dan bij de volgende update);
- wie in de Kiosk Manager was ingelogd, moet één keer opnieuw inloggen.
Op de bootpartitie blijven `voxip-config.json`, `voxip-authorized_keys` en
`voxip-network-reset` werken, net als de opdracht `voxip-kiosk-ctl`. Bij het
bouwen van een image worden `VOXIP_DEFAULT_URL` en `VOXIP_DEVICE_NAME` nog
gelezen als de `DAYNIVO_`-varianten ontbreken.
### Verwijderen
```bash
@@ -109,7 +134,7 @@ sudo ./uninstall.sh --purge # ook configuratie, status, logs, netwerkprofielen
Het image wordt gebouwd met [pi-gen](https://github.com/RPi-Distro/pi-gen),
de officiële bouwtool van Raspberry Pi OS. De extra stage
`image/stage-voxip` voert hetzelfde `install.sh` uit als een handmatige
`image/stage-daynivo` voert hetzelfde `install.sh` uit als een handmatige
installatie.
Vereist: een Linux-buildmachine (x86_64 of arm64) met Docker en git. Op
@@ -131,14 +156,14 @@ Instelbaar via omgevingsvariabelen:
|---|---|---|
| `PI_GEN_REF` | `2024-11-19-raspios-bookworm-armhf` | pi-gen-tag (32-bit) |
| `RELEASE` | `bookworm` | `bookworm` of `trixie` (moet bij `PI_GEN_REF` passen) |
| `VOXIP_DEFAULT_URL` | `https://schermen-ijsselflat.voxip.nl` | standaard kiosk-URL |
| `VOXIP_DEVICE_NAME` | `Daynivo Kiosk` | standaard apparaatnaam |
| `DAYNIVO_DEFAULT_URL` | `https://schermen-ijsselflat.voxip.nl` | standaard kiosk-URL |
| `DAYNIVO_DEVICE_NAME` | `Daynivo Kiosk` | standaard apparaatnaam |
| `TARGET_HOSTNAME` | `kiosk` | hostname (`http://<hostname>.local:8080`) |
| `ALLOW_DIRTY` | `0` | `1` = bouw de werkmap in plaats van de laatste commit |
**Reproduceerbaarheid.** pi-gen staat vast op een tag en de broncode
gaat via `git archive` van één commit mee. Beide staan in `BUILD-INFO`
(ook in de image: `/usr/share/voxip-kiosk/BUILD-INFO`). De Debian- en
(ook in de image: `/usr/share/daynivo-kiosk/BUILD-INFO`). De Debian- en
Raspberry Pi-pakketten komen uit de archieven zoals die op het moment van
bouwen zijn. Er bestaat geen snapshot-archief voor Raspberry Pi OS, dus
twee builds op verschillende dagen kunnen nieuwere pakketversies bevatten.
@@ -199,10 +224,10 @@ sudo daynivo-kiosk-ctl reload # pagina herladen
sudo daynivo-kiosk-ctl restart-browser # Chromium herstarten
sudo daynivo-kiosk-ctl health # healthcheck-rapport
sudo daynivo-kiosk-ctl maintenance on|off # healthcheck tijdelijk niets laten herstellen
journalctl -u voxip-kiosk-browser -u voxip-kiosk-agent -f
journalctl -u daynivo-kiosk-browser -u daynivo-kiosk-agent -f
```
### Configuratie — `/etc/voxip-kiosk/config.json`
### Configuratie — `/etc/daynivo-kiosk/config.json`
| Sleutel | Standaard | Betekenis |
|---|---|---|
@@ -270,8 +295,8 @@ make dev # http://127.0.0.1:8080 met tijdelijke mappen in .dev/
| Symptoom | Oplossing |
|---|---|
| Zwart scherm na opstarten | `sudo daynivo-kiosk-ctl status`. Kijk in de journal van `voxip-kiosk-x`/`voxip-kiosk-browser`. Staat de monitor aan vóór de Pi? |
| Zwart scherm na opstarten | `sudo daynivo-kiosk-ctl status`. Kijk in de journal van `daynivo-kiosk-x`/`daynivo-kiosk-browser`. Staat de monitor aan vóór de Pi? |
| Beheerinterface onbereikbaar na netwerkwijziging | Wacht 3 minuten (automatische rollback) of zet `daynivo-network-reset` op de bootpartitie en herstart. |
| Wachtwoord vergeten | `sudo daynivo-kiosk-ctl reset-password` via SSH, of verwijder `/etc/voxip-kiosk/admin.json` met de SD-kaart in een Linux-pc. |
| Wachtwoord vergeten | `sudo daynivo-kiosk-ctl reset-password` via SSH, of verwijder `/etc/daynivo-kiosk/admin.json` met de SD-kaart in een Linux-pc. |
| `kiosk.local` werkt niet | Gebruik het IP-adres (staat op het setupscherm en in je router). Windows heeft soms Bonjour/mDNS nodig. Geef iedere kiosk een unieke hostname. |
| Onderspanning in het dashboard | Gebruik de officiële 5,1 V/2,5 A-voeding. Onderspanning veroorzaakt crashes en SD-kaartcorruptie. |
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/python3 -I
# Installed to /usr/lib/daynivo-kiosk/ by install.sh. -I: ignore PYTHON* env and user site-packages.
import sys
sys.path.insert(0, "/usr/lib/daynivo-kiosk/python")
from daynivo_kiosk.agent import main # noqa: E402
sys.exit(main())
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/python3 -I
# Installed to /usr/lib/daynivo-kiosk/ by install.sh. -I: ignore PYTHON* env and user site-packages.
import sys
sys.path.insert(0, "/usr/lib/daynivo-kiosk/python")
from daynivo_kiosk.browser import main # noqa: E402
sys.exit(main())
+2 -2
View File
@@ -1,8 +1,8 @@
#!/usr/bin/python3 -I
# Installed to /usr/lib/voxip-kiosk/ by install.sh. -I: ignore PYTHON* env and user site-packages.
# Installed to /usr/lib/daynivo-kiosk/ by install.sh. -I: ignore PYTHON* env and user site-packages.
import sys
sys.path.insert(0, "/usr/lib/voxip-kiosk/python")
sys.path.insert(0, "/usr/lib/daynivo-kiosk/python")
from daynivo_kiosk.ctl import main # noqa: E402
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/python3 -I
# Installed to /usr/lib/daynivo-kiosk/ by install.sh. -I: ignore PYTHON* env and user site-packages.
import sys
sys.path.insert(0, "/usr/lib/daynivo-kiosk/python")
from daynivo_kiosk.healthcheck import main # noqa: E402
sys.exit(main())
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/python3 -I
# Installed to /usr/lib/daynivo-kiosk/ by install.sh. -I: ignore PYTHON* env and user site-packages.
import sys
sys.path.insert(0, "/usr/lib/daynivo-kiosk/python")
from daynivo_kiosk.helper import main # noqa: E402
sys.exit(main())
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/python3 -I
# Installed to /usr/lib/daynivo-kiosk/ by install.sh. -I: ignore PYTHON* env and user site-packages.
import sys
sys.path.insert(0, "/usr/lib/daynivo-kiosk/python")
from daynivo_kiosk.manager import main # noqa: E402
sys.exit(main())
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/python3 -I
# Installed to /usr/lib/daynivo-kiosk/ by install.sh. -I: ignore PYTHON* env and user site-packages.
import sys
sys.path.insert(0, "/usr/lib/daynivo-kiosk/python")
from daynivo_kiosk.updater import main # noqa: E402
sys.exit(main())
+1 -1
View File
@@ -2,7 +2,7 @@
# Starts the kiosk X server on vt7 for user "kiosk" (via Xorg.wrap).
# A fresh MIT cookie per start; Chromium reads it via XAUTHORITY.
set -eu
AUTH="${RUNTIME_DIRECTORY:-/run/voxip-kiosk-x}/Xauthority"
AUTH="${RUNTIME_DIRECTORY:-/run/daynivo-kiosk-x}/Xauthority"
umask 077
: > "$AUTH"
xauth -q -f "$AUTH" add :0 . "$(mcookie)"
-9
View File
@@ -1,9 +0,0 @@
#!/usr/bin/python3 -I
# Installed to /usr/lib/voxip-kiosk/ by install.sh. -I: ignore PYTHON* env and user site-packages.
import sys
sys.path.insert(0, "/usr/lib/voxip-kiosk/python")
from daynivo_kiosk.agent import main # noqa: E402
sys.exit(main())
-9
View File
@@ -1,9 +0,0 @@
#!/usr/bin/python3 -I
# Installed to /usr/lib/voxip-kiosk/ by install.sh. -I: ignore PYTHON* env and user site-packages.
import sys
sys.path.insert(0, "/usr/lib/voxip-kiosk/python")
from daynivo_kiosk.browser import main # noqa: E402
sys.exit(main())
-9
View File
@@ -1,9 +0,0 @@
#!/usr/bin/python3 -I
# Installed to /usr/lib/voxip-kiosk/ by install.sh. -I: ignore PYTHON* env and user site-packages.
import sys
sys.path.insert(0, "/usr/lib/voxip-kiosk/python")
from daynivo_kiosk.healthcheck import main # noqa: E402
sys.exit(main())
-9
View File
@@ -1,9 +0,0 @@
#!/usr/bin/python3 -I
# Installed to /usr/lib/voxip-kiosk/ by install.sh. -I: ignore PYTHON* env and user site-packages.
import sys
sys.path.insert(0, "/usr/lib/voxip-kiosk/python")
from daynivo_kiosk.helper import main # noqa: E402
sys.exit(main())
-9
View File
@@ -1,9 +0,0 @@
#!/usr/bin/python3 -I
# Installed to /usr/lib/voxip-kiosk/ by install.sh. -I: ignore PYTHON* env and user site-packages.
import sys
sys.path.insert(0, "/usr/lib/voxip-kiosk/python")
from daynivo_kiosk.manager import main # noqa: E402
sys.exit(main())
-9
View File
@@ -1,9 +0,0 @@
#!/usr/bin/python3 -I
# Installed to /usr/lib/voxip-kiosk/ by install.sh. -I: ignore PYTHON* env and user site-packages.
import sys
sys.path.insert(0, "/usr/lib/voxip-kiosk/python")
from daynivo_kiosk.updater import main # noqa: E402
sys.exit(main())
File renamed without changes.
File renamed without changes.
+3 -3
View File
@@ -1,4 +1,4 @@
/var/log/voxip-kiosk/*.log {
/var/log/daynivo-kiosk/*.log {
weekly
rotate 8
maxsize 2M
@@ -6,6 +6,6 @@
delaycompress
missingok
notifempty
create 0640 voxip voxip
su voxip voxip
create 0640 daynivo daynivo
su daynivo daynivo
}
@@ -0,0 +1,8 @@
[Plymouth Theme]
Name=daynivo-kiosk
Description=Daynivo Kiosk boot splash (logo on white)
ModuleName=script
[script]
ImageDir=/usr/share/plymouth/themes/daynivo-kiosk
ScriptFile=/usr/share/plymouth/themes/daynivo-kiosk/daynivo-kiosk.script
File renamed without changes.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 111 KiB

@@ -1,8 +0,0 @@
[Plymouth Theme]
Name=voxip-kiosk
Description=Daynivo Kiosk boot splash (logo on white)
ModuleName=script
[script]
ImageDir=/usr/share/plymouth/themes/voxip-kiosk
ScriptFile=/usr/share/plymouth/themes/voxip-kiosk/voxip-kiosk.script
+3 -3
View File
@@ -1,4 +1,4 @@
# Daynivo Kiosk OS: the web interface (user voxip) may run exactly one program
# Daynivo Kiosk OS: the web interface (user daynivo) may run exactly one program
# as root: the restricted helper, which validates all of its input itself.
Defaults:voxip !requiretty
voxip ALL=(root) NOPASSWD: /usr/lib/voxip-kiosk/voxip-kiosk-helper
Defaults:daynivo !requiretty
daynivo ALL=(root) NOPASSWD: /usr/lib/daynivo-kiosk/daynivo-kiosk-helper
+26 -26
View File
@@ -11,7 +11,7 @@ samenwerken en waarom bepaalde keuzes zijn gemaakt.
- 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
`/etc/daynivo-kiosk/config.json` en kan bij uitrol via de bootpartitie
worden meegegeven (`daynivo-config.json`).
## Overzicht
@@ -19,25 +19,25 @@ samenwerken en waarom bepaalde keuzes zijn gemaakt.
```
┌──────────────────────── Raspberry Pi ────────────────────────┐
│ │
HDMI ◄────────┤ voxip-kiosk-x.service Xorg :0 op vt7 (user kiosk) │
HDMI ◄────────┤ daynivo-kiosk-x.service Xorg :0 op vt7 (user kiosk) │
│ ▲ │
│ │ DISPLAY=:0 │
│ voxip-kiosk-browser.service Chromium --kiosk (user kiosk) │
│ daynivo-kiosk-browser.service Chromium --kiosk (user kiosk) │
│ ▲ CDP 127.0.0.1:9222 │
│ │ ┌─────────────────────────────┐ │
│ ├──┤ voxip-kiosk-agent.service │ navigatie, offline-│
│ ├──┤ daynivo-kiosk-agent.service │ navigatie, offline-│
│ │ │ (user kiosk) │ pagina, verversen, │
│ │ └─────────────────────────────┘ hang-detectie │
│ │ │
LAN :8080 ───►│ voxip-kiosk-manager.service Flask + gunicorn (user voxip) │
LAN :8080 ───►│ daynivo-kiosk-manager.service Flask + gunicorn (user daynivo) │
│ │ sudo -n (één commando) │
│ ▼ │
│ voxip-kiosk-helper root, vaste werkwoorden, │
│ daynivo-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 │
│ 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 │
└──────────────────────────────────────────────────────────────┘
```
@@ -46,16 +46,16 @@ samenwerken en waarom bepaalde keuzes zijn gemaakt.
| 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. |
| `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/voxip-kiosk/voxip-kiosk-helper` toe. De helper:
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
@@ -69,8 +69,8 @@ uitsluitend `/usr/lib/voxip-kiosk/voxip-kiosk-helper` toe. De helper:
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.
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
@@ -86,7 +86,7 @@ webinterface gebruiken dit om:
### Offline-gedrag
Chromium start altijd op een lokale startpagina
(`/usr/share/voxip-kiosk/www/start.html`). De agent controleert met een
(`/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
@@ -111,13 +111,13 @@ draait) kan het apparaat dus claimen, iemand anders op het netwerk niet.
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
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
`voxip-network-guard.service` de snapshot terug bij het opstarten, nog
`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
@@ -125,12 +125,12 @@ draait) kan het apparaat dus claimen, iemand anders op het netwerk niet.
### Configuratie
- `/etc/voxip-kiosk/config.json`: eigenaar `voxip`, groep `kiosk`, 0640.
- `/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/voxip-kiosk/admin.json`: Argon2id-hash, 0600, alleen `voxip`.
- `/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
@@ -179,7 +179,7 @@ daynivo-kiosk-os/
├── 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/
├── bin/ dunne launchers → /usr/lib/daynivo-kiosk/
├── src/daynivo_kiosk/ Python-package (alle logica)
│ ├── config.py laden/valideren/atomair opslaan
│ ├── validation.py gedeelde invoervalidatie
@@ -197,7 +197,7 @@ daynivo-kiosk-os/
├── www/ lokale kioskpagina's (start, offline, setup)
├── systemd/ unit-bestanden
├── config/ standaardconfig, sudoers, journald, logrotate, …
├── image/ pi-gen image-builder (stage-voxip)
├── image/ pi-gen image-builder (stage-daynivo)
├── tests/ pytest-suite
└── docs/ architectuur, hardwaretest
```
+6 -6
View File
@@ -20,7 +20,7 @@ Noteer per stap: ✅ / ❌ + opmerking, de imageversie en het schermtype.
- [ ] Binnen 90 s na stroom aan verschijnt het setupscherm met de setupcode,
`http://kiosk.local:8080` en het IP-adres. Er is geen muiscursor en
er zijn geen dialoogvensters.
- [ ] SSH is uit (`ssh voxipadmin@kiosk.local` wordt geweigerd), en er is
- [ ] SSH is uit (`ssh daynivoadmin@kiosk.local` wordt geweigerd), en er is
geen bruikbaar standaardwachtwoord.
## 2. Initiële configuratie
@@ -47,7 +47,7 @@ Noteer per stap: ✅ / ❌ + opmerking, de imageversie en het schermtype.
(zie Logboek → Kiosk-agent).
- [ ] Screenshot op de Kiosk-pagina toont wat er op het scherm staat.
- [ ] Daynivo-URL met `?token=`: het token is overal gemaskeerd
(dashboard, `daynivo-kiosk-ctl status`, `journalctl -u voxip-kiosk-agent`).
(dashboard, `daynivo-kiosk-ctl status`, `journalctl -u daynivo-kiosk-agent`).
## 4. Herstel
@@ -61,9 +61,9 @@ Noteer per stap: ✅ / ❌ + opmerking, de imageversie en het schermtype.
Zet de URL daarna terug.
- [ ] Vastgelopen pagina: open via SSH-tunnel DevTools of zet een testpagina met
`while(true){}` in. Na ±15 s herlaadt de agent, na ±30 s herstart Chromium.
- [ ] `sudo systemctl stop voxip-kiosk-agent`: de healthcheck start hem binnen 2 minuten.
- [ ] `sudo systemctl stop daynivo-kiosk-agent`: de healthcheck start hem binnen 2 minuten.
- [ ] De webinterface blijft werken terwijl Chromium gestopt is
(`sudo systemctl stop voxip-kiosk-browser`).
(`sudo systemctl stop daynivo-kiosk-browser`).
## 5. Netwerk
@@ -123,11 +123,11 @@ Noteer per stap: ✅ / ❌ + opmerking, de imageversie en het schermtype.
op Gitea: "beschikbaar". *Bijwerken naar …*: binnen enkele minuten is de
nieuwe versie actief, de beheerinterface komt terug, configuratie en
wachtwoord ongewijzigd.
- [ ] *Automatisch bijwerken* aan, `sudo systemctl start voxip-kiosk-update.service`:
- [ ] *Automatisch bijwerken* aan, `sudo systemctl start daynivo-kiosk-update.service`:
de nieuwere release wordt geïnstalleerd. Met het vinkje uit: alleen gecontroleerd.
- [ ] `curl -fsSL https://daynivo.nl/kiosk/install.sh | sudo bash` op een verse
Raspberry Pi OS Lite: installeert de nieuwste release, setupcode op het scherm.
- [ ] `sudo ./uninstall.sh --purge` op een testkaart: geen Daynivo-services of
-bestanden meer (`systemctl list-unit-files | grep voxip` is leeg).
-bestanden meer (`systemctl list-unit-files | grep daynivo` is leeg).
- [ ] Installatie op een bestaande Raspberry Pi OS Lite (Bookworm) met de
Daynivo-kiosk erop: URL overgenomen, `display-kiosk.service` uitgeschakeld.
+2 -2
View File
@@ -8,7 +8,7 @@ een fout; de reden staat dan in daynivo-config.error.txt).
daynivo-config.json
Instellingen voor deze kiosk. Zie daynivo-config.example.json. Toegestaan:
alle sleutels uit /etc/voxip-kiosk/config.json plus "hostname" en
alle sleutels uit /etc/daynivo-kiosk/config.json plus "hostname" en
"wifi_country". Voorbeeld:
{
@@ -20,7 +20,7 @@ daynivo-config.json
daynivo-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
met sleutel-login, voor de beheerder (daynivoadmin of de gebruiker die je
in Raspberry Pi Imager hebt aangemaakt).
daynivo-network-reset
+13 -12
View File
@@ -8,8 +8,8 @@
# 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.
# DAYNIVO_DEFAULT_URL kiosk URL baked into the default config.
# DAYNIVO_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.
#
@@ -23,8 +23,9 @@ 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:-Daynivo Kiosk}"
# VOXIP_* are the names from before the rename and still work.
DAYNIVO_DEFAULT_URL="${DAYNIVO_DEFAULT_URL:-${VOXIP_DEFAULT_URL:-https://schermen-ijsselflat.voxip.nl}}"
DAYNIVO_DEVICE_NAME="${DAYNIVO_DEVICE_NAME:-${VOXIP_DEVICE_NAME:-Daynivo Kiosk}}"
TARGET_HOSTNAME="${TARGET_HOSTNAME:-kiosk}"
NATIVE=0
[[ ${1:-} == "--native" ]] && NATIVE=1
@@ -45,13 +46,13 @@ 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.
# Do not export the plain Lite image from stage2; ours is exported after stage-daynivo.
touch "$PIGEN/stage2/SKIP_IMAGES"
rm -f "$PIGEN/stage2/EXPORT_NOOBS"
echo "==> Daynivo-stage voorbereiden"
cp -a "$ROOT/image/stage-voxip" "$PIGEN/stage-voxip"
PAYLOAD="$PIGEN/stage-voxip/00-voxip/files/voxip-kiosk"
cp -a "$ROOT/image/stage-daynivo" "$PIGEN/stage-daynivo"
PAYLOAD="$PIGEN/stage-daynivo/00-daynivo/files/daynivo-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)"
@@ -67,9 +68,9 @@ else
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")
cat > "$PIGEN/stage-daynivo/00-daynivo/files/build.env" <<EOF
DAYNIVO_DEFAULT_URL=$(printf %q "$DAYNIVO_DEFAULT_URL")
DAYNIVO_DEVICE_NAME=$(printf %q "$DAYNIVO_DEVICE_NAME")
EOF
cat > "$PAYLOAD/BUILD-INFO" <<EOF
daynivo_kiosk_os=$VERSION
@@ -97,11 +98,11 @@ KEYBOARD_LAYOUT="English (US)"
TIMEZONE_DEFAULT=Europe/Amsterdam
WPA_COUNTRY=NL
TARGET_HOSTNAME=$TARGET_HOSTNAME
FIRST_USER_NAME=voxipadmin
FIRST_USER_NAME=daynivoadmin
FIRST_USER_PASS=$FIRST_USER_PASS
DISABLE_FIRST_BOOT_USER_RENAME=1
ENABLE_SSH=0
STAGE_LIST="stage0 stage1 stage2 stage-voxip"
STAGE_LIST="stage0 stage1 stage2 stage-daynivo"
EOF
echo "==> Image bouwen (dit duurt 30–90 minuten)"
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash -e
# Install Daynivo 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/daynivo-kiosk"
install -d "${ROOTFS_DIR}/usr/src/daynivo-kiosk"
cp -a files/daynivo-kiosk/. "${ROOTFS_DIR}/usr/src/daynivo-kiosk/"
on_chroot << EOF_CHROOT
/usr/src/daynivo-kiosk/install.sh --image --url $(printf %q "${DAYNIVO_DEFAULT_URL}") --name $(printf %q "${DAYNIVO_DEVICE_NAME}")
install -m 0644 /usr/src/daynivo-kiosk/BUILD-INFO /usr/share/daynivo-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/daynivo-kiosk/image/boot-README.txt "${ROOTFS_DIR}/boot/firmware/DAYNIVO-LEESMIJ.txt"
install -m 0644 files/daynivo-kiosk/image/daynivo-config.example.json "${ROOTFS_DIR}/boot/firmware/daynivo-config.example.json"
File renamed without changes.
File renamed without changes.
-23
View File
@@ -1,23 +0,0 @@
#!/bin/bash -e
# Install Daynivo 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/daynivo-config.example.json "${ROOTFS_DIR}/boot/firmware/daynivo-config.example.json"
+122 -53
View File
@@ -8,9 +8,9 @@
#
# 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)
# ./install.sh --image … # used by image/stage-daynivo (no service starts)
#
# Existing configuration in /etc/voxip-kiosk is never overwritten.
# Existing configuration in /etc/daynivo-kiosk is never overwritten.
set -euo pipefail
@@ -21,15 +21,15 @@ 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
voxip-kiosk-update.service voxip-kiosk-update.timer)
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 voxip-kiosk-update.timer)
LIB=/usr/lib/daynivo-kiosk
SHARE=/usr/share/daynivo-kiosk
DOC=/usr/share/doc/daynivo-kiosk
ETC=/etc/daynivo-kiosk
UNITS=(daynivo-kiosk-x.service daynivo-kiosk-browser.service daynivo-kiosk-agent.service daynivo-kiosk-manager.service
daynivo-kiosk-healthcheck.service daynivo-kiosk-healthcheck.timer daynivo-network-guard.service daynivo-boot-import.service
daynivo-kiosk-update.service daynivo-kiosk-update.timer)
ENABLE=(daynivo-kiosk-x.service daynivo-kiosk-browser.service daynivo-kiosk-agent.service daynivo-kiosk-manager.service
daynivo-kiosk-healthcheck.timer daynivo-network-guard.service daynivo-boot-import.service daynivo-kiosk-update.timer)
say() { printf '\033[1;34m==>\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33mLet op:\033[0m %s\n' "$*" >&2; }
@@ -106,6 +106,75 @@ pkg_available() { apt-cache policy "$1" 2>/dev/null | grep -q 'Candidate: [0-9]'
apt-get install -y --no-install-recommends "${PKGS[@]}"
}
# --- migration from the VoxIP names ---------------------------------------------------
# Kiosks installed before the rename (up to 1.2.x) use voxip-* services and folders
# and a "voxip" user. Move them over once, keeping the configuration, the password,
# the browser profile (offline cache) and the network settings. The old folder
# names stay as symlinks for whatever still runs from the old version, such as
# the updater that started this upgrade.
if [[ -d /etc/voxip-kiosk && ! -L /etc/voxip-kiosk && ! -e /etc/daynivo-kiosk ]]; then
say "Overstappen van de VoxIP-namen naar Daynivo"
if [[ $LIVE -eq 1 ]]; then
# Not voxip-kiosk-update.service: a nightly auto-update runs this very script in it.
systemctl stop voxip-kiosk-healthcheck.timer voxip-kiosk-update.timer voxip-kiosk-agent.service \
voxip-kiosk-browser.service voxip-kiosk-x.service voxip-kiosk-manager.service 2>/dev/null || true
fi
for unit in 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-kiosk-update.service \
voxip-kiosk-update.timer voxip-network-guard.service voxip-boot-import.service; do
systemctl disable "$unit" >/dev/null 2>&1 || true
rm -f "/etc/systemd/system/$unit"
done
if [[ $LIVE -eq 1 ]]; then systemctl daemon-reload; fi
for old in /etc/voxip-kiosk /var/lib/voxip-kiosk /var/lib/voxip-kiosk-network /var/lib/voxip-kiosk-browser \
/var/log/voxip-kiosk /var/cache/voxip-kiosk; do
new="${old/voxip/daynivo}"
if [[ -d $old && ! -L $old && ! -e $new ]]; then
mv "$old" "$new"
ln -s "$new" "$old"
fi
done
# Same uid and gid, so every file keeps the right owner.
if id voxip >/dev/null 2>&1 && ! id daynivo >/dev/null 2>&1; then
usermod -l daynivo -d /var/lib/daynivo-kiosk voxip
if getent group voxip >/dev/null; then groupmod -n daynivo voxip; fi
fi
# Network profiles written by the web interface; not while a change awaits confirmation.
if [[ ! -f /var/lib/daynivo-kiosk-network/pending.json ]]; then
renamed=0
for iface in eth0 wlan0; do
old=/etc/NetworkManager/system-connections/voxip-$iface.nmconnection
new=/etc/NetworkManager/system-connections/daynivo-$iface.nmconnection
if [[ -f $old && ! -e $new ]]; then
sed -i "s/^id=voxip-$iface\$/id=daynivo-$iface/" "$old"
mv "$old" "$new"
chmod 0600 "$new"
renamed=1
fi
done
if [[ $renamed -eq 1 && $LIVE -eq 1 ]]; then nmcli connection reload 2>/dev/null || true; fi
fi
rm -f /etc/sudoers.d/voxip-kiosk /etc/systemd/journald.conf.d/voxip-kiosk.conf \
/etc/systemd/system.conf.d/voxip-watchdog.conf /etc/logrotate.d/voxip-kiosk \
/etc/avahi/services/voxip-kiosk.service /etc/chromium/policies/managed/voxip-kiosk.json \
/etc/chromium-browser/policies/managed/voxip-kiosk.json /usr/local/sbin/voxip-kiosk-ctl
if [[ -f /etc/ssh/sshd_config.d/voxip-kiosk.conf ]]; then
mv /etc/ssh/sshd_config.d/voxip-kiosk.conf /etc/ssh/sshd_config.d/daynivo-kiosk.conf
fi
if [[ -f /etc/X11/Xwrapper.config.voxip-orig ]]; then
mv /etc/X11/Xwrapper.config.voxip-orig /etc/X11/Xwrapper.config.daynivo-orig
fi
if [[ -f /etc/systemd/system/display-kiosk.service.voxip-disabled ]]; then
mv /etc/systemd/system/display-kiosk.service.voxip-disabled /etc/systemd/system/display-kiosk.service.daynivo-disabled
fi
# The old programs: an updater that started this install has its code loaded already.
rm -rf /usr/lib/voxip-kiosk /usr/share/voxip-kiosk /usr/share/doc/voxip-kiosk /usr/share/plymouth/themes/voxip-kiosk
fi
# --- users ----------------------------------------------------------------------
say "Gebruikers aanmaken"
getent group kiosk >/dev/null || groupadd --system kiosk
@@ -114,11 +183,11 @@ id kiosk >/dev/null 2>&1 || useradd --system --gid kiosk --home-dir /home/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 "Daynivo Kiosk Manager" voxip
getent group daynivo >/dev/null || groupadd --system daynivo
id daynivo >/dev/null 2>&1 || useradd --system --gid daynivo --home-dir /var/lib/daynivo-kiosk --no-create-home \
--shell /usr/sbin/nologin --comment "Daynivo Kiosk Manager" daynivo
for g in systemd-journal video; do
getent group "$g" >/dev/null && usermod -aG "$g" voxip
getent group "$g" >/dev/null && usermod -aG "$g" daynivo
done
# --- application files --------------------------------------------------------------
@@ -135,7 +204,7 @@ rm -rf "$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-updater voxip-kiosk-x; do
for bin in daynivo-kiosk-agent daynivo-kiosk-browser daynivo-kiosk-healthcheck daynivo-kiosk-helper daynivo-kiosk-manager daynivo-kiosk-updater daynivo-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/daynivo-kiosk-ctl" /usr/local/sbin/daynivo-kiosk-ctl
@@ -151,10 +220,10 @@ 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
install -d -o daynivo -g kiosk -m 2750 "$ETC"
install -d -o daynivo -g daynivo -m 0750 /var/lib/daynivo-kiosk
install -d -o root -g root -m 0755 /var/lib/daynivo-kiosk-network
install -d -o daynivo -g daynivo -m 0750 /var/log/daynivo-kiosk
# Migrate from Daynivo's raspberry-pi/install-kiosk.sh: take over its URL, then disable it.
LEGACY_KIOSK_UNIT=/etc/systemd/system/display-kiosk.service
@@ -165,13 +234,13 @@ if [[ -f $LEGACY_KIOSK_UNIT ]]; then
say "URL overgenomen van de Daynivo-kioskinstallatie"
fi
if [[ $LIVE -eq 1 ]]; then systemctl disable --now display-kiosk.service 2>/dev/null || true; fi
mv "$LEGACY_KIOSK_UNIT" "$LEGACY_KIOSK_UNIT.voxip-disabled"
mv "$LEGACY_KIOSK_UNIT" "$LEGACY_KIOSK_UNIT.daynivo-disabled"
fi
PYRUN=(python3 -I -c)
"${PYRUN[@]}" '
import json, os, sys
sys.path.insert(0, "/usr/lib/voxip-kiosk/python")
sys.path.insert(0, "/usr/lib/daynivo-kiosk/python")
from daynivo_kiosk import config as c
path, default, url, name = sys.argv[1:5]
updates = {}
@@ -194,36 +263,36 @@ else:
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
chown daynivo: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"
touch "$ETC/.config.lock" && chown daynivo:kiosk "$ETC/.config.lock" && chmod 0640 "$ETC/.config.lock"
[[ -f $ETC/admin.json ]] && chown daynivo:daynivo "$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
mv "$TMP_SUDOERS" /etc/sudoers.d/daynivo-kiosk
chown root:root /etc/sudoers.d/daynivo-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
install -D -m 0644 "$SRC/config/journald.conf" /etc/systemd/journald.conf.d/daynivo-kiosk.conf
install -D -m 0644 "$SRC/config/watchdog.conf" /etc/systemd/system.conf.d/daynivo-watchdog.conf
install -D -m 0644 "$SRC/config/logrotate" /etc/logrotate.d/daynivo-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
if [[ -f /etc/X11/Xwrapper.config && ! -f /etc/X11/Xwrapper.config.daynivo-orig ]]; then
cp -a /etc/X11/Xwrapper.config /etc/X11/Xwrapper.config.daynivo-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"
install -D -m 0644 "$SRC/config/chromium/daynivo-kiosk.json" "$dir/daynivo-kiosk.json"
done
ADMIN_PORT="$(python3 -I -c 'import sys; sys.path.insert(0, "/usr/lib/voxip-kiosk/python"); from daynivo_kiosk.config import load_config; print(load_config(__import__("pathlib").Path(sys.argv[1]))["admin_port"])' "$ETC/config.json")"
ADMIN_PORT="$(python3 -I -c 'import sys; sys.path.insert(0, "/usr/lib/daynivo-kiosk/python"); from daynivo_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
sed "s#<port>8080</port>#<port>${ADMIN_PORT}</port>#" "$SRC/config/avahi-daynivo.service" > /etc/avahi/services/daynivo-kiosk.service
chmod 0644 /etc/avahi/services/daynivo-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
@@ -263,18 +332,18 @@ 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
SPLASH_DIR=/usr/share/plymouth/themes/daynivo-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
install -m 0644 "$SRC"/config/plymouth/daynivo-kiosk/* "$SPLASH_DIR/"
if [[ ! -f /var/lib/daynivo-kiosk/previous-plymouth-theme ]]; then
plymouth-set-default-theme > /var/lib/daynivo-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
if [[ "$(plymouth-set-default-theme 2>/dev/null)" != daynivo-kiosk || "$(cat /var/lib/daynivo-kiosk/splash.sha256 2>/dev/null)" != "$SPLASH_SUM" ]]; then
say "Opstartscherm instellen (initramfs bijwerken, dit duurt even)"
plymouth-set-default-theme voxip-kiosk
plymouth-set-default-theme daynivo-kiosk
if update-initramfs -u -k all >/dev/null 2>&1; then
echo "$SPLASH_SUM" > /var/lib/voxip-kiosk/splash.sha256
echo "$SPLASH_SUM" > /var/lib/daynivo-kiosk/splash.sha256
else
warn "initramfs bijwerken mislukt; het opstartscherm verschijnt mogelijk pas later tijdens het opstarten."
fi
@@ -286,8 +355,8 @@ 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
grep -qx "$dm" /var/lib/daynivo-kiosk/disabled-display-managers 2>/dev/null \
|| echo "$dm" >> /var/lib/daynivo-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."
@@ -302,15 +371,15 @@ for unit in "${UNITS[@]}"; do
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
if [[ ! -f /var/lib/daynivo-kiosk/previous-default-target ]]; then
systemctl get-default > /var/lib/daynivo-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"
"$LIB/daynivo-kiosk-helper" set-hostname "$NEW_HOSTNAME" >/dev/null || die "Ongeldige hostname: $NEW_HOSTNAME"
fi
if [[ $LIVE -eq 0 ]]; then
@@ -321,11 +390,11 @@ 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 voxip-kiosk-update.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
systemctl restart daynivo-kiosk-manager.service daynivo-kiosk-agent.service
systemctl start daynivo-kiosk-healthcheck.timer daynivo-kiosk-update.timer
systemctl reset-failed daynivo-kiosk-x.service daynivo-kiosk-browser.service 2>/dev/null || true
systemctl restart daynivo-kiosk-x.service
systemctl restart daynivo-kiosk-browser.service
HOST="$(hostname)"
SCHEME=http
+1 -1
View File
@@ -1,5 +1,5 @@
[project]
name = "voxip-kiosk"
name = "daynivo-kiosk"
dynamic = ["version"]
description = "Daynivo Kiosk OS — kiosk runtime and Daynivo Kiosk Manager"
requires-python = ">=3.11"
+2 -2
View File
@@ -1,4 +1,4 @@
"""voxip-kiosk-agent — keeps the kiosk page where it should be.
"""daynivo-kiosk-agent — keeps the kiosk page where it should be.
Runs as user ``kiosk`` and steers Chromium through the DevTools Protocol on
127.0.0.1:
@@ -33,7 +33,7 @@ from .config import admin_url, load_config
from .paths import Paths, load_paths
from .validation import mask_url
log = logging.getLogger("voxip-kiosk-agent")
log = logging.getLogger("daynivo-kiosk-agent")
TICK_SECONDS = 5
PROBE_MAX_AGE_LOADING = 10
+6 -6
View File
@@ -1,6 +1,6 @@
"""voxip-kiosk-browser — prepares the display and runs Chromium.
"""daynivo-kiosk-browser — prepares the display and runs Chromium.
Started by ``voxip-kiosk-browser.service`` as user ``kiosk`` once the X
Started by ``daynivo-kiosk-browser.service`` as user ``kiosk`` once the X
server is up. Exit status tells systemd whether to restart: 1 when
``browser_restart`` is enabled (``Restart=on-failure`` restarts it and the
start limit catches crash loops), 0 when it is disabled.
@@ -25,7 +25,7 @@ from .config import admin_url, load_config
from .paths import Paths, load_paths
from .validation import mask_url, parse_resolution
log = logging.getLogger("voxip-kiosk-browser")
log = logging.getLogger("daynivo-kiosk-browser")
CDP_PORT = 9222
CHROMIUM_CANDIDATES = ("/usr/bin/chromium-browser", "/usr/bin/chromium")
@@ -212,14 +212,14 @@ def clean_profile(profile_dir: Path) -> None:
profile = data.setdefault("profile", {})
profile["exit_type"] = "Normal"
profile["exited_cleanly"] = True
tmp = prefs.with_suffix(".voxip-tmp")
tmp = prefs.with_suffix(".daynivo-tmp")
tmp.write_text(json.dumps(data), encoding="utf-8")
os.replace(tmp, prefs)
def profile_dirs(config: dict[str, Any]) -> tuple[Path, Path]:
runtime = Path(os.environ.get("RUNTIME_DIRECTORY", "/run/voxip-kiosk-browser"))
state = Path(os.environ.get("STATE_DIRECTORY", "/var/lib/voxip-kiosk-browser"))
runtime = Path(os.environ.get("RUNTIME_DIRECTORY", "/run/daynivo-kiosk-browser"))
state = Path(os.environ.get("STATE_DIRECTORY", "/var/lib/daynivo-kiosk-browser"))
profile = state / "profile" if config.get("browser_profile") == "persistent" else runtime / "profile"
if config.get("browser_profile") != "persistent" and (state / "profile").exists():
shutil.rmtree(state / "profile", ignore_errors=True)
+1 -1
View File
@@ -236,7 +236,7 @@ def save_config(path: Path, updates: dict[str, Any]) -> dict[str, Any]:
lock_path = path.with_name(".config.lock")
with _thread_lock:
# Read-only is enough for flock and works for every writer (the web
# interface as voxip, the helper as root), whoever created the file.
# interface as daynivo, the helper as root), whoever created the file.
lock_fd = os.open(str(lock_path), os.O_RDONLY | os.O_CREAT, 0o640)
try:
fcntl.flock(lock_fd, fcntl.LOCK_EX)
+3 -3
View File
@@ -48,7 +48,7 @@ def cmd_setup_code(_args) -> int:
return 1
code = setupcode.read(paths.setup_code_file)
if not code:
print("Nog geen setupcode; start de beheerinterface: sudo systemctl restart voxip-kiosk-manager")
print("Nog geen setupcode; start de beheerinterface: sudo systemctl restart daynivo-kiosk-manager")
return 1
print(code)
return 0
@@ -65,10 +65,10 @@ def cmd_reset_password(args) -> int:
code = setupcode.generate()
setupcode.write(paths.setup_code_file, code)
try:
os.chown(paths.setup_code_file, pwd.getpwnam("voxip").pw_uid, grp.getgrnam("kiosk").gr_gid)
os.chown(paths.setup_code_file, pwd.getpwnam("daynivo").pw_uid, grp.getgrnam("kiosk").gr_gid)
except KeyError:
pass
subprocess.run(["systemctl", "restart", "voxip-kiosk-manager.service"], check=False)
subprocess.run(["systemctl", "restart", "daynivo-kiosk-manager.service"], check=False)
print(f"Wachtwoord verwijderd. Nieuwe setupcode: {code}")
print("Open de beheerinterface en stel een nieuw wachtwoord in; de code staat ook op het kioskscherm.")
return 0
+3 -3
View File
@@ -1,4 +1,4 @@
"""voxip-kiosk-healthcheck — runs every minute (timer) as root.
"""daynivo-kiosk-healthcheck — runs every minute (timer) as root.
Keeps every kiosk service alive without hiding problems: each recovery is
logged, and a crash loop that hit systemd's start limit is retried only
@@ -21,7 +21,7 @@ from .cdp import CDP, CDPError
from .config import load_config
from .paths import Paths, load_paths
log = logging.getLogger("voxip-kiosk-healthcheck")
log = logging.getLogger("daynivo-kiosk-healthcheck")
ALWAYS_ON = (services.AGENT_UNIT, services.MANAGER_UNIT)
DISPLAY_UNITS = (services.X_UNIT, services.BROWSER_UNIT)
@@ -147,7 +147,7 @@ class HealthCheck:
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(prog="voxip-kiosk-healthcheck")
parser = argparse.ArgumentParser(prog="daynivo-kiosk-healthcheck")
parser.add_argument("--check-only", action="store_true", help="alleen rapporteren, niets herstellen")
parser.add_argument("--json", action="store_true", help="rapport als JSON")
args = parser.parse_args(argv)
+19 -19
View File
@@ -1,11 +1,11 @@
"""voxip-kiosk-helper — the only privileged entry point of Daynivo Kiosk OS.
"""daynivo-kiosk-helper — the only privileged entry point of Daynivo Kiosk OS.
Runs as root, either through ``sudo -n`` from the web interface (user
``voxip``) or from systemd units. It accepts a fixed set of verbs, validates
``daynivo``) or from systemd units. It accepts a fixed set of verbs, validates
every argument itself, never invokes a shell and prints a single JSON
object on stdout.
Usage: voxip-kiosk-helper <verb> [args] (network-apply reads JSON on stdin)
Usage: daynivo-kiosk-helper <verb> [args] (network-apply reads JSON on stdin)
"""
from __future__ import annotations
@@ -27,13 +27,13 @@ from . import netconfig, updater
from . import validation as v
from .paths import Paths, default_paths
HELPER_PATH = "/usr/lib/voxip-kiosk/voxip-kiosk-helper"
ROLLBACK_UNIT = "voxip-network-rollback"
UPDATE_UNIT = "voxip-kiosk-update-now"
UPDATER_PATH = "/usr/lib/voxip-kiosk/voxip-kiosk-updater"
ACTIVATE_UNIT = "voxip-network-activate"
HELPER_PATH = "/usr/lib/daynivo-kiosk/daynivo-kiosk-helper"
ROLLBACK_UNIT = "daynivo-network-rollback"
UPDATE_UNIT = "daynivo-kiosk-update-now"
UPDATER_PATH = "/usr/lib/daynivo-kiosk/daynivo-kiosk-updater"
ACTIVATE_UNIT = "daynivo-network-activate"
BOOT_DIRS = (Path("/boot/firmware"), Path("/boot"))
# Files on the boot partition. The voxip-* names are from before the rename to
# Files on the boot partition. The daynivo-* names are from before the rename to
# Daynivo and keep working, so existing SD cards and instructions stay valid.
RESET_TRIGGERS = ("daynivo-network-reset", "voxip-network-reset")
IMPORT_CONFIGS = ("voxip-config.json", "daynivo-config.json") # applied in this order: the Daynivo name wins
@@ -83,24 +83,24 @@ class Helper:
# -- power & browser -------------------------------------------------
def reboot(self) -> dict[str, Any]:
self.run(["systemd-run", "--on-active=3", "--unit=voxip-power-reboot", "/bin/systemctl", "reboot"])
self.run(["systemd-run", "--on-active=3", "--unit=daynivo-power-reboot", "/bin/systemctl", "reboot"])
log(syslog.LOG_NOTICE, "Herstart aangevraagd via beheerinterface")
return {"message": "De Raspberry Pi wordt over enkele seconden herstart."}
def poweroff(self) -> dict[str, Any]:
self.run(["systemd-run", "--on-active=3", "--unit=voxip-power-off", "/bin/systemctl", "poweroff"])
self.run(["systemd-run", "--on-active=3", "--unit=daynivo-power-off", "/bin/systemctl", "poweroff"])
log(syslog.LOG_NOTICE, "Uitschakelen aangevraagd via beheerinterface")
return {"message": "De Raspberry Pi wordt over enkele seconden uitgeschakeld."}
def restart_browser(self) -> dict[str, Any]:
self.run(["systemctl", "reset-failed", "voxip-kiosk-browser.service", "voxip-kiosk-x.service"], check=False)
self.run(["systemctl", "restart", "--no-block", "voxip-kiosk-browser.service"])
self.run(["systemctl", "reset-failed", "daynivo-kiosk-browser.service", "daynivo-kiosk-x.service"], check=False)
self.run(["systemctl", "restart", "--no-block", "daynivo-kiosk-browser.service"])
log(syslog.LOG_NOTICE, "Chromium wordt herstart")
return {"message": "Chromium wordt herstart."}
def restart_manager(self) -> dict[str, Any]:
# Delayed so the web interface can still answer the request that asked for it.
self.run(["systemd-run", "--on-active=2", "--unit=voxip-manager-restart", "/bin/systemctl", "restart", "voxip-kiosk-manager.service"])
self.run(["systemd-run", "--on-active=2", "--unit=daynivo-manager-restart", "/bin/systemctl", "restart", "daynivo-kiosk-manager.service"])
log(syslog.LOG_NOTICE, "Beheerinterface wordt herstart")
return {"message": "De beheerinterface wordt herstart."}
@@ -326,7 +326,7 @@ class Helper:
def _chown_config(self, path: Path) -> None:
try:
uid = pwd.getpwnam("voxip").pw_uid
uid = pwd.getpwnam("daynivo").pw_uid
gid = grp.getgrnam("kiosk").gr_gid
except KeyError:
return
@@ -392,7 +392,7 @@ class Helper:
os.chown(ssh_dir, user.pw_uid, user.pw_gid)
sshd_dir = self.etc / "ssh" / "sshd_config.d"
sshd_dir.mkdir(parents=True, exist_ok=True)
(sshd_dir / "voxip-kiosk.conf").write_text(
(sshd_dir / "daynivo-kiosk.conf").write_text(
"# Beheerd door Daynivo Kiosk OS: alleen inloggen met SSH-sleutels.\n"
"PasswordAuthentication no\nKbdInteractiveAuthentication no\nPermitRootLogin no\n",
encoding="utf-8",
@@ -449,9 +449,9 @@ def _parse_json(text: str) -> Any:
def main(argv: list[str] | None = None, helper: Helper | None = None, environ: dict[str, str] | None = None) -> int:
argv = sys.argv[1:] if argv is None else argv
environ = os.environ if environ is None else environ
syslog.openlog("voxip-kiosk-helper", 0, syslog.LOG_DAEMON)
syslog.openlog("daynivo-kiosk-helper", 0, syslog.LOG_DAEMON)
if not argv:
print(json.dumps({"ok": False, "error": "Gebruik: voxip-kiosk-helper <opdracht> [argumenten]"}))
print(json.dumps({"ok": False, "error": "Gebruik: daynivo-kiosk-helper <opdracht> [argumenten]"}))
return 2
verb, args = argv[0], argv[1:]
caller = environ.get("SUDO_USER")
@@ -460,7 +460,7 @@ def main(argv: list[str] | None = None, helper: Helper | None = None, environ: d
print(json.dumps({"ok": False, "error": "Opdracht niet toegestaan"}))
return 1
if helper is None and os.geteuid() != 0:
print(json.dumps({"ok": False, "error": "voxip-kiosk-helper moet als root draaien"}))
print(json.dumps({"ok": False, "error": "daynivo-kiosk-helper moet als root draaien"}))
return 1
helper = helper or Helper()
stdin = sys.stdin.read(16385) if verb == "network-apply" else ""
+3 -3
View File
@@ -1,4 +1,4 @@
"""voxip-kiosk-manager — starts the web interface under gunicorn.
"""daynivo-kiosk-manager — starts the web interface under gunicorn.
Reads port and HTTPS settings from the kiosk configuration, creates a
self-signed certificate when HTTPS is enabled and none exists yet, then
@@ -19,8 +19,8 @@ from . import logutil
from .config import load_config
from .paths import Paths, load_paths
log = logging.getLogger("voxip-kiosk-manager")
PYTHON_LIB = "/usr/lib/voxip-kiosk/python"
log = logging.getLogger("daynivo-kiosk-manager")
PYTHON_LIB = "/usr/lib/daynivo-kiosk/python"
def ensure_certificate(paths: Paths) -> tuple[Path, Path]:
+1 -1
View File
@@ -15,7 +15,7 @@ from typing import Any
from . import validation as v
INTERFACES = {"eth0": "ethernet", "wlan0": "wifi"}
PROFILE_PREFIX = "voxip-"
PROFILE_PREFIX = "daynivo-"
ROLLBACK_SECONDS = 180
+19 -19
View File
@@ -1,7 +1,7 @@
"""Filesystem locations used by every Daynivo Kiosk component.
The defaults match the installed layout. Unprivileged components may
override them through ``VOXIP_*`` environment variables (used by the test
override them through ``DAYNIVO_*`` environment variables (used by the test
suite and for local development). The root helper always uses
:func:`default_paths` and never reads the environment.
"""
@@ -15,15 +15,15 @@ from pathlib import Path
@dataclass(frozen=True)
class Paths:
config_dir: Path = Path("/etc/voxip-kiosk")
state_dir: Path = Path("/var/lib/voxip-kiosk")
network_state_dir: Path = Path("/var/lib/voxip-kiosk-network")
agent_run_dir: Path = Path("/run/voxip-kiosk-agent")
browser_run_dir: Path = Path("/run/voxip-kiosk-browser")
health_run_dir: Path = Path("/run/voxip-kiosk-health")
share_dir: Path = Path("/usr/share/voxip-kiosk")
log_dir: Path = Path("/var/log/voxip-kiosk")
helper: Path = Path("/usr/lib/voxip-kiosk/voxip-kiosk-helper")
config_dir: Path = Path("/etc/daynivo-kiosk")
state_dir: Path = Path("/var/lib/daynivo-kiosk")
network_state_dir: Path = Path("/var/lib/daynivo-kiosk-network")
agent_run_dir: Path = Path("/run/daynivo-kiosk-agent")
browser_run_dir: Path = Path("/run/daynivo-kiosk-browser")
health_run_dir: Path = Path("/run/daynivo-kiosk-health")
share_dir: Path = Path("/usr/share/daynivo-kiosk")
log_dir: Path = Path("/var/log/daynivo-kiosk")
helper: Path = Path("/usr/lib/daynivo-kiosk/daynivo-kiosk-helper")
nm_connections_dir: Path = Path("/etc/NetworkManager/system-connections")
@property
@@ -90,15 +90,15 @@ def default_paths() -> Paths:
_ENV = {
"config_dir": "VOXIP_CONFIG_DIR",
"state_dir": "VOXIP_STATE_DIR",
"network_state_dir": "VOXIP_NETWORK_STATE_DIR",
"agent_run_dir": "VOXIP_AGENT_RUN_DIR",
"browser_run_dir": "VOXIP_BROWSER_RUN_DIR",
"health_run_dir": "VOXIP_HEALTH_RUN_DIR",
"share_dir": "VOXIP_SHARE_DIR",
"log_dir": "VOXIP_LOG_DIR",
"helper": "VOXIP_HELPER",
"config_dir": "DAYNIVO_CONFIG_DIR",
"state_dir": "DAYNIVO_STATE_DIR",
"network_state_dir": "DAYNIVO_NETWORK_STATE_DIR",
"agent_run_dir": "DAYNIVO_AGENT_RUN_DIR",
"browser_run_dir": "DAYNIVO_BROWSER_RUN_DIR",
"health_run_dir": "DAYNIVO_HEALTH_RUN_DIR",
"share_dir": "DAYNIVO_SHARE_DIR",
"log_dir": "DAYNIVO_LOG_DIR",
"helper": "DAYNIVO_HELPER",
}
+6 -6
View File
@@ -5,11 +5,11 @@ from __future__ import annotations
import subprocess
from typing import Any
BROWSER_UNIT = "voxip-kiosk-browser.service"
X_UNIT = "voxip-kiosk-x.service"
AGENT_UNIT = "voxip-kiosk-agent.service"
MANAGER_UNIT = "voxip-kiosk-manager.service"
HEALTH_UNIT = "voxip-kiosk-healthcheck.service"
BROWSER_UNIT = "daynivo-kiosk-browser.service"
X_UNIT = "daynivo-kiosk-x.service"
AGENT_UNIT = "daynivo-kiosk-agent.service"
MANAGER_UNIT = "daynivo-kiosk-manager.service"
HEALTH_UNIT = "daynivo-kiosk-healthcheck.service"
# Units whose logs the web interface may show (label → journalctl -u args).
LOG_SOURCES: dict[str, tuple[str, list[str]]] = {
@@ -18,7 +18,7 @@ LOG_SOURCES: dict[str, tuple[str, list[str]]] = {
"agent": ("Kiosk-agent", [AGENT_UNIT]),
"health": ("Healthcheck", [HEALTH_UNIT]),
"manager": ("Beheerinterface", [MANAGER_UNIT]),
"network": ("Netwerk", ["NetworkManager.service", "voxip-network-guard.service", "voxip-network-rollback.service", "voxip-network-activate.service"]),
"network": ("Netwerk", ["NetworkManager.service", "daynivo-network-guard.service", "daynivo-network-rollback.service", "daynivo-network-activate.service"]),
"system": ("Volledig systeem", []),
}
LOG_LINES = (50, 100, 200, 500)
+7 -7
View File
@@ -1,4 +1,4 @@
"""voxip-kiosk-updater — installs new Daynivo Kiosk OS releases (runs as root).
"""daynivo-kiosk-updater — installs new Daynivo Kiosk OS releases (runs as root).
A release is a tag ``vX.Y.Z`` on the Gitea repository with two assets:
``daynivo-kiosk-os-X.Y.Z.tar.gz`` and its ``.sha256``. The updater
@@ -11,9 +11,9 @@ A release is a tag ``vX.Y.Z`` on the Gitea repository with two assets:
It never installs an older or equal version. Usage::
voxip-kiosk-updater --check # only check
voxip-kiosk-updater --auto # check; install when auto_update is on (daily timer)
voxip-kiosk-updater --install # check and install when newer (button in the web interface)
daynivo-kiosk-updater --check # only check
daynivo-kiosk-updater --auto # check; install when auto_update is on (daily timer)
daynivo-kiosk-updater --install # check and install when newer (button in the web interface)
"""
from __future__ import annotations
@@ -39,12 +39,12 @@ from . import logutil
from .config import atomic_write_json, load_config
from .paths import Paths, default_paths, read_version
log = logging.getLogger("voxip-kiosk-updater")
log = logging.getLogger("daynivo-kiosk-updater")
RELEASE_API = "https://git.rothman.nl/api/v1/repos/rothman/daynivo-kiosk-os/releases/latest"
ASSET_PREFIX = "daynivo-kiosk-os-"
RELEASE_DIR = Path("/opt/daynivo-kiosk-os")
CACHE_DIR = Path("/var/cache/voxip-kiosk")
CACHE_DIR = Path("/var/cache/daynivo-kiosk")
KEEP_RELEASES = 2
MAX_DOWNLOAD = 64 * 1024 * 1024
USER_AGENT = "Daynivo-Kiosk-OS/updater"
@@ -219,7 +219,7 @@ class Updater:
def main(argv: list[str] | None = None, updater: Updater | None = None) -> int:
parser = argparse.ArgumentParser(prog="voxip-kiosk-updater", description="Daynivo Kiosk OS bijwerken.")
parser = argparse.ArgumentParser(prog="daynivo-kiosk-updater", description="Daynivo Kiosk OS bijwerken.")
mode = parser.add_mutually_exclusive_group(required=True)
mode.add_argument("--check", action="store_true", help="alleen controleren")
mode.add_argument("--auto", action="store_true", help="controleren en bijwerken als automatisch bijwerken aan staat")
+2 -2
View File
@@ -48,7 +48,7 @@ def create_app(paths: Paths | None = None, *, helper: Any = None, cdp: Any = Non
app = Flask(__name__)
app.secret_key = _secret_key(paths)
app.config.update(
SESSION_COOKIE_NAME="voxip_session",
SESSION_COOKIE_NAME="daynivo_session",
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE="Strict",
SESSION_COOKIE_SECURE=config["admin_https"],
@@ -58,7 +58,7 @@ def create_app(paths: Paths | None = None, *, helper: Any = None, cdp: Any = Non
)
credentials = CredentialStore(paths.admin_file, paths.setup_code_file)
credentials.ensure_setup_code()
app.extensions["voxip"] = Services(
app.extensions["daynivo"] = Services(
paths=paths,
credentials=credentials,
throttle=LoginThrottle(),
+1 -1
View File
@@ -21,7 +21,7 @@ HASHER = PasswordHasher(time_cost=3, memory_cost=65536, parallelism=2, hash_len=
MIN_LENGTH = 10
MAX_LENGTH = 128
WEAK = {"admin", "administrator", "password", "wachtwoord", "raspberry", "voxip", "kiosk", "welkom", "welcome", "qwerty", "123456"}
WEAK = {"admin", "administrator", "password", "wachtwoord", "raspberry", "daynivo", "kiosk", "welkom", "welcome", "qwerty", "123456"}
def password_problem(password: str, confirm: str | None = None) -> str | None:
+2 -2
View File
@@ -22,14 +22,14 @@ from . import security
from .auth import password_problem
bp = Blueprint("ui", __name__)
audit_log = logging.getLogger("voxip-kiosk-audit")
audit_log = logging.getLogger("daynivo-kiosk-audit")
IDLE_TIMEOUT = 3600
PUBLIC_ENDPOINTS = {"ui.login", "ui.setup", "static"}
def svc():
return current_app.extensions["voxip"]
return current_app.extensions["daynivo"]
def cfg() -> dict[str, Any]:
@@ -1,8 +1,8 @@
[Unit]
Description=Daynivo configuratie en SSH-sleutels importeren vanaf de bootpartitie
Documentation=file:///usr/share/doc/voxip-kiosk/README.md
Documentation=file:///usr/share/doc/daynivo-kiosk/README.md
After=local-fs.target
Before=voxip-kiosk-manager.service voxip-kiosk-browser.service ssh.service
Before=daynivo-kiosk-manager.service daynivo-kiosk-browser.service ssh.service
ConditionPathExistsGlob=|/boot/firmware/daynivo-config.json
ConditionPathExistsGlob=|/boot/firmware/daynivo-authorized_keys
ConditionPathExistsGlob=|/boot/daynivo-config.json
@@ -15,7 +15,7 @@ ConditionPathExistsGlob=|/boot/voxip-authorized_keys
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/lib/voxip-kiosk/voxip-kiosk-helper boot-import
ExecStart=/usr/lib/daynivo-kiosk/daynivo-kiosk-helper boot-import
[Install]
WantedBy=multi-user.target
@@ -1,18 +1,18 @@
[Unit]
Description=Daynivo Kiosk agent (navigatie, offlinepagina, verversen)
Documentation=file:///usr/share/doc/voxip-kiosk/README.md
After=voxip-kiosk-browser.service
Documentation=file:///usr/share/doc/daynivo-kiosk/README.md
After=daynivo-kiosk-browser.service
[Service]
Type=simple
User=kiosk
Group=kiosk
RuntimeDirectory=voxip-kiosk-agent
RuntimeDirectory=daynivo-kiosk-agent
RuntimeDirectoryMode=0755
ExecStart=/usr/lib/voxip-kiosk/voxip-kiosk-agent
ExecStart=/usr/lib/daynivo-kiosk/daynivo-kiosk-agent
Restart=always
RestartSec=5
SyslogIdentifier=voxip-kiosk-agent
SyslogIdentifier=daynivo-kiosk-agent
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
+36
View File
@@ -0,0 +1,36 @@
[Unit]
Description=Daynivo Kiosk Chromium
Documentation=file:///usr/share/doc/daynivo-kiosk/README.md
Requires=daynivo-kiosk-x.service
After=daynivo-kiosk-x.service daynivo-boot-import.service
# Restarting or stopping X restarts/stops the browser as well.
PartOf=daynivo-kiosk-x.service
# A crash loop stops after 6 starts in 10 minutes; the healthcheck logs it
# and retries after a cool-down (see daynivo-kiosk-healthcheck).
StartLimitIntervalSec=600
StartLimitBurst=6
[Service]
Type=simple
User=kiosk
Group=kiosk
Environment=DISPLAY=:0
Environment=XAUTHORITY=/run/daynivo-kiosk-x/Xauthority
Environment=HOME=/home/kiosk
RuntimeDirectory=daynivo-kiosk-browser
RuntimeDirectoryMode=0755
StateDirectory=daynivo-kiosk-browser
StateDirectoryMode=0700
ExecStart=/usr/lib/daynivo-kiosk/daynivo-kiosk-browser
# daynivo-kiosk-browser exits 1 after a crash when browser_restart is on, 0 when it is off.
Restart=on-failure
RestartSec=5
TimeoutStopSec=15
KillMode=control-group
OOMScoreAdjust=-100
LogRateLimitIntervalSec=30s
LogRateLimitBurst=300
SyslogIdentifier=daynivo-kiosk-browser
[Install]
WantedBy=multi-user.target
+11
View File
@@ -0,0 +1,11 @@
[Unit]
Description=Daynivo Kiosk healthcheck
Documentation=file:///usr/share/doc/daynivo-kiosk/README.md
[Service]
Type=oneshot
RuntimeDirectory=daynivo-kiosk-health
RuntimeDirectoryPreserve=yes
ExecStart=/usr/lib/daynivo-kiosk/daynivo-kiosk-healthcheck
SyslogIdentifier=daynivo-kiosk-healthcheck
TimeoutStartSec=60
@@ -1,27 +1,27 @@
[Unit]
Description=Daynivo Kiosk Manager (webbeheer)
Documentation=file:///usr/share/doc/voxip-kiosk/README.md
After=network.target voxip-boot-import.service
Documentation=file:///usr/share/doc/daynivo-kiosk/README.md
After=network.target daynivo-boot-import.service
StartLimitIntervalSec=300
StartLimitBurst=10
[Service]
Type=simple
User=voxip
Group=voxip
User=daynivo
Group=daynivo
# systemd-journal: read logs; video: vcgencmd (voltage/throttling)
SupplementaryGroups=systemd-journal video
ExecStart=/usr/lib/voxip-kiosk/voxip-kiosk-manager
ExecStart=/usr/lib/daynivo-kiosk/daynivo-kiosk-manager
Restart=always
RestartSec=3
UMask=0027
LogsDirectory=voxip-kiosk
LogsDirectory=daynivo-kiosk
LogsDirectoryMode=0750
SyslogIdentifier=voxip-kiosk-manager
SyslogIdentifier=daynivo-kiosk-manager
# No NoNewPrivileges/ProtectSystem/ProtectHome: the helper started through
# "sudo -n voxip-kiosk-helper" inherits this unit's mount namespace and must
# "sudo -n daynivo-kiosk-helper" inherits this unit's mount namespace and must
# be able to write /etc (hostname, NetworkManager profiles). The web process
# itself runs unprivileged as user voxip and can only write its own dirs.
# itself runs unprivileged as user daynivo and can only write its own dirs.
# Also no ProtectKernelTunables/ProtectKernelModules/LockPersonality,
# RestrictSUIDSGID, SystemCall*, ...: for a non-root unit those imply
# NoNewPrivileges, and sudo then refuses to run (tests/test_units.py).
@@ -1,11 +1,11 @@
[Unit]
Description=Daynivo Kiosk OS: controleren op updates (en installeren als dat aan staat)
Documentation=file:///usr/share/doc/voxip-kiosk/README.md
Documentation=file:///usr/share/doc/daynivo-kiosk/README.md
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/lib/voxip-kiosk/voxip-kiosk-updater --auto
ExecStart=/usr/lib/daynivo-kiosk/daynivo-kiosk-updater --auto
TimeoutStartSec=1h
SyslogIdentifier=voxip-kiosk-updater
SyslogIdentifier=daynivo-kiosk-updater
File renamed without changes.
@@ -1,6 +1,6 @@
[Unit]
Description=Daynivo Kiosk X-server (vt7)
Documentation=file:///usr/share/doc/voxip-kiosk/README.md
Documentation=file:///usr/share/doc/daynivo-kiosk/README.md
After=systemd-user-sessions.service systemd-logind.service
Conflicts=getty@tty7.service
StartLimitIntervalSec=600
@@ -20,10 +20,10 @@ StandardOutput=journal
StandardError=journal
UtmpIdentifier=tty7
UtmpMode=user
RuntimeDirectory=voxip-kiosk-x
RuntimeDirectory=daynivo-kiosk-x
RuntimeDirectoryMode=0700
ExecStartPre=+/usr/bin/chvt 7
ExecStart=/usr/lib/voxip-kiosk/voxip-kiosk-x
ExecStart=/usr/lib/daynivo-kiosk/daynivo-kiosk-x
Restart=always
RestartSec=3
@@ -1,6 +1,6 @@
[Unit]
Description=Daynivo netwerkherstel bij opstarten (rollback en noodreset)
Documentation=file:///usr/share/doc/voxip-kiosk/README.md
Documentation=file:///usr/share/doc/daynivo-kiosk/README.md
DefaultDependencies=no
After=local-fs.target
Before=NetworkManager.service network-pre.target shutdown.target
@@ -10,7 +10,7 @@ Conflicts=shutdown.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/lib/voxip-kiosk/voxip-kiosk-helper network-boot-guard
ExecStart=/usr/lib/daynivo-kiosk/daynivo-kiosk-helper network-boot-guard
[Install]
WantedBy=multi-user.target
-36
View File
@@ -1,36 +0,0 @@
[Unit]
Description=Daynivo Kiosk Chromium
Documentation=file:///usr/share/doc/voxip-kiosk/README.md
Requires=voxip-kiosk-x.service
After=voxip-kiosk-x.service voxip-boot-import.service
# Restarting or stopping X restarts/stops the browser as well.
PartOf=voxip-kiosk-x.service
# A crash loop stops after 6 starts in 10 minutes; the healthcheck logs it
# and retries after a cool-down (see voxip-kiosk-healthcheck).
StartLimitIntervalSec=600
StartLimitBurst=6
[Service]
Type=simple
User=kiosk
Group=kiosk
Environment=DISPLAY=:0
Environment=XAUTHORITY=/run/voxip-kiosk-x/Xauthority
Environment=HOME=/home/kiosk
RuntimeDirectory=voxip-kiosk-browser
RuntimeDirectoryMode=0755
StateDirectory=voxip-kiosk-browser
StateDirectoryMode=0700
ExecStart=/usr/lib/voxip-kiosk/voxip-kiosk-browser
# voxip-kiosk-browser exits 1 after a crash when browser_restart is on, 0 when it is off.
Restart=on-failure
RestartSec=5
TimeoutStopSec=15
KillMode=control-group
OOMScoreAdjust=-100
LogRateLimitIntervalSec=30s
LogRateLimitBurst=300
SyslogIdentifier=voxip-kiosk-browser
[Install]
WantedBy=multi-user.target
-11
View File
@@ -1,11 +0,0 @@
[Unit]
Description=Daynivo Kiosk healthcheck
Documentation=file:///usr/share/doc/voxip-kiosk/README.md
[Service]
Type=oneshot
RuntimeDirectory=voxip-kiosk-health
RuntimeDirectoryPreserve=yes
ExecStart=/usr/lib/voxip-kiosk/voxip-kiosk-healthcheck
SyslogIdentifier=voxip-kiosk-healthcheck
TimeoutStartSec=60
+2 -2
View File
@@ -96,7 +96,7 @@ def quiet_system(monkeypatch):
"available": True, "wifi_country": "NL",
"devices": {
iface: {"interface": iface, "kind": kind, "present": True, "state": "connected", "state_label": "Verbonden",
"connected": True, "connection": f"voxip-{iface}", "addresses": ["192.168.10.20/24"], "gateway": "192.168.10.1",
"connected": True, "connection": f"daynivo-{iface}", "addresses": ["192.168.10.20/24"], "gateway": "192.168.10.1",
"dns": ["192.168.10.1"], "hwaddr": "B8:27:EB:00:00:01",
"profile": {"exists": True, "method": "dhcp", "address": "", "gateway": "", "dns": "", "ssid": "Kantoor", "hidden": False}}
for iface, kind in (("eth0", "ethernet"), ("wlan0", "wifi"))
@@ -130,7 +130,7 @@ PASSWORD = "Correct-Paard-Batterij-42"
@pytest.fixture
def logged_in(app, client, paths):
"""A client with a configured admin password and an active session."""
app.extensions["voxip"].credentials.set_password(PASSWORD)
app.extensions["daynivo"].credentials.set_password(PASSWORD)
page = client.get("/login")
token = csrf_from(page)
resp = client.post("/login", data={"password": PASSWORD, "csrf_token": token})
+18 -18
View File
@@ -35,7 +35,7 @@ def helper(paths, tmp_path, boot):
etc.mkdir()
(etc / "hosts").write_text("127.0.0.1\tlocalhost\n127.0.1.1\tkiosk\n")
run = Recorder({"nmcli -t -f": "aaaa-1111\n"})
return h.Helper(paths=paths, run=run, etc=etc, boot_dirs=(boot,), helper_path="/usr/lib/voxip-kiosk/voxip-kiosk-helper")
return h.Helper(paths=paths, run=run, etc=etc, boot_dirs=(boot,), helper_path="/usr/lib/daynivo-kiosk/daynivo-kiosk-helper")
def run_main(helper, argv, env=None, capsys=None):
@@ -45,17 +45,17 @@ def run_main(helper, argv, env=None, capsys=None):
def test_internal_verbs_refused_via_sudo(helper, capsys):
code, out = run_main(helper, ["network-restore"], {"SUDO_USER": "voxip"}, capsys)
code, out = run_main(helper, ["network-restore"], {"SUDO_USER": "daynivo"}, capsys)
assert code == 1 and out["ok"] is False
code, out = run_main(helper, ["boot-import"], {"SUDO_USER": "voxip"}, capsys)
code, out = run_main(helper, ["boot-import"], {"SUDO_USER": "daynivo"}, capsys)
assert code == 1
assert helper.run.commands == []
def test_public_verb_via_sudo(helper, capsys):
code, out = run_main(helper, ["restart-browser"], {"SUDO_USER": "voxip"}, capsys)
code, out = run_main(helper, ["restart-browser"], {"SUDO_USER": "daynivo"}, capsys)
assert code == 0 and out["ok"] is True
assert helper.run.ran("systemctl", "restart", "--no-block", "voxip-kiosk-browser.service")
assert helper.run.ran("systemctl", "restart", "--no-block", "daynivo-kiosk-browser.service")
def test_unknown_verb_and_bad_arity(helper, capsys):
@@ -99,7 +99,7 @@ ETH_STATIC = {"interface": "eth0", "method": "static", "address": "192.168.5.10/
def test_network_apply_snapshot_pending_and_timers(helper, paths):
(paths.nm_connections_dir / "Wired connection 1.nmconnection").write_text("[connection]\nid=Wired connection 1\n")
result = helper.network_apply(ETH_STATIC)
keyfile = paths.nm_connections_dir / "voxip-eth0.nmconnection"
keyfile = paths.nm_connections_dir / "daynivo-eth0.nmconnection"
assert keyfile.exists() and oct(keyfile.stat().st_mode & 0o777) == "0o600"
assert "address1=192.168.5.10/24,192.168.5.1" in keyfile.read_text()
assert (paths.network_backup_dir / "Wired connection 1.nmconnection").exists()
@@ -108,8 +108,8 @@ def test_network_apply_snapshot_pending_and_timers(helper, paths):
assert pending["deadline"] - pending["created"] == 180
assert result["pending"]["interface"] == "eth0"
runs = [c for c in helper.run.commands if c[0] == "systemd-run"]
assert runs[0][1:3] == ["--unit=voxip-network-rollback", "--on-active=180"] and runs[0][-1] == "network-restore"
assert runs[1][1:3] == ["--unit=voxip-network-activate", "--on-active=2"] and runs[1][-2:] == ["network-activate", "eth0"]
assert runs[0][1:3] == ["--unit=daynivo-network-rollback", "--on-active=180"] and runs[0][-1] == "network-restore"
assert runs[1][1:3] == ["--unit=daynivo-network-activate", "--on-active=2"] and runs[1][-2:] == ["network-activate", "eth0"]
def test_second_apply_refused_while_pending(helper):
@@ -123,8 +123,8 @@ def test_confirm_clears_pending(helper, paths):
helper.network_confirm()
assert not paths.network_pending_file.exists()
assert not paths.network_backup_dir.exists()
assert (paths.nm_connections_dir / "voxip-eth0.nmconnection").exists()
assert helper.run.ran("systemctl", "stop", "voxip-network-rollback.timer")
assert (paths.nm_connections_dir / "daynivo-eth0.nmconnection").exists()
assert helper.run.ran("systemctl", "stop", "daynivo-network-rollback.timer")
def test_restore_puts_old_profiles_back(helper, paths):
@@ -134,7 +134,7 @@ def test_restore_puts_old_profiles_back(helper, paths):
old.unlink()
helper.network_restore()
assert old.exists()
assert not (paths.nm_connections_dir / "voxip-eth0.nmconnection").exists()
assert not (paths.nm_connections_dir / "daynivo-eth0.nmconnection").exists()
assert not paths.network_pending_file.exists()
assert helper.run.ran("nmcli", "--wait", "30", "connection", "up", "uuid", "aaaa-1111")
@@ -152,7 +152,7 @@ def test_wifi_keeps_existing_password_for_same_ssid(helper, paths):
helper.network_confirm()
helper.network_apply({"interface": "wlan0", "method": "static", "address": "10.0.0.9/24", "gateway": "10.0.0.1", "dns": "10.0.0.1",
"wifi": {"ssid": "Kantoor", "psk": ""}})
assert "psk=geheim-wachtwoord" in (paths.nm_connections_dir / "voxip-wlan0.nmconnection").read_text()
assert "psk=geheim-wachtwoord" in (paths.nm_connections_dir / "daynivo-wlan0.nmconnection").read_text()
helper.network_confirm()
with pytest.raises(h.HelperError, match="wachtwoord"):
helper.network_apply({"interface": "wlan0", "method": "dhcp", "wifi": {"ssid": "Ander", "psk": ""}})
@@ -165,22 +165,22 @@ def test_boot_guard_rolls_back_and_honours_reset(helper, paths, boot):
helper.run.commands.clear()
result = helper.network_boot_guard()
assert result == {"rolled_back": True}
assert not (paths.nm_connections_dir / "voxip-eth0.nmconnection").exists()
assert not (paths.nm_connections_dir / "daynivo-eth0.nmconnection").exists()
assert not any(c[0] == "nmcli" for c in helper.run.commands), "NetworkManager is not running yet at boot"
(paths.nm_connections_dir / "voxip-wlan0.nmconnection").write_text("x")
(paths.nm_connections_dir / "daynivo-wlan0.nmconnection").write_text("x")
(boot / "daynivo-network-reset").write_text("")
assert helper.network_boot_guard() == {"reset": True}
assert not (paths.nm_connections_dir / "voxip-wlan0.nmconnection").exists()
assert not (paths.nm_connections_dir / "daynivo-wlan0.nmconnection").exists()
assert not (boot / "daynivo-network-reset").exists()
assert old.exists()
def test_boot_guard_still_honours_the_old_reset_name(helper, paths, boot):
(paths.nm_connections_dir / "voxip-eth0.nmconnection").write_text("x")
(paths.nm_connections_dir / "daynivo-eth0.nmconnection").write_text("x")
(boot / "voxip-network-reset").write_text("")
assert helper.network_boot_guard() == {"reset": True}
assert not (paths.nm_connections_dir / "voxip-eth0.nmconnection").exists()
assert not (paths.nm_connections_dir / "daynivo-eth0.nmconnection").exists()
assert not (boot / "voxip-network-reset").exists()
@@ -230,7 +230,7 @@ def test_update_verbs_are_public_and_install_runs_in_its_own_unit(helper):
def test_update_install_reports_a_running_update(helper):
def busy(args, timeout=30, check=True, input=None):
return subprocess.CompletedProcess(args, 1, stdout="", stderr="Unit voxip-kiosk-update-now.service already exists.")
return subprocess.CompletedProcess(args, 1, stdout="", stderr="Unit daynivo-kiosk-update-now.service already exists.")
helper.run = busy
with pytest.raises(h.HelperError, match="al een update"):
+1 -1
View File
@@ -27,7 +27,7 @@ def test_keyfile_static_ethernet():
req = n.validate_request({"interface": "eth0", "method": "static", "address": "10.1.2.3/16", "gateway": "10.1.0.1", "dns": ["10.1.0.1"]})
text = n.build_keyfile(req, uuid="11111111-2222-3333-4444-555555555555")
kf = n.read_keyfile(text)
assert kf.get("connection", "id") == "voxip-eth0"
assert kf.get("connection", "id") == "daynivo-eth0"
assert kf.get("connection", "type") == "ethernet"
assert kf.get("connection", "autoconnect-priority") == "100"
assert kf.get("ipv4", "method") == "manual"
+2 -2
View File
@@ -22,8 +22,8 @@ def settings(unit):
def test_manager_can_use_sudo_for_the_helper():
unit = settings("voxip-kiosk-manager.service")
assert unit["User"] == "voxip"
unit = settings("daynivo-kiosk-manager.service")
assert unit["User"] == "daynivo"
for key in IMPLY_NO_NEW_PRIVILEGES:
assert unit.get(key, "no") in ("no", "false", ""), key
for key in ("ProtectSystem", "ProtectHome", "PrivateTmp"):
+4 -4
View File
@@ -52,7 +52,7 @@ def test_setup_without_csrf_is_rejected(client, paths):
# --- login & session -------------------------------------------------------------
def test_login_logout(app, client):
app.extensions["voxip"].credentials.set_password(PASSWORD)
app.extensions["daynivo"].credentials.set_password(PASSWORD)
assert client.get("/").status_code == 302
tok = csrf_from(client.get("/login"))
assert "Onjuist" in client.post("/login", data={"csrf_token": tok, "password": "fout-wachtwoord"}).get_data(as_text=True)
@@ -63,7 +63,7 @@ def test_login_logout(app, client):
def test_session_cookie_flags(app, client):
app.extensions["voxip"].credentials.set_password(PASSWORD)
app.extensions["daynivo"].credentials.set_password(PASSWORD)
tok = csrf_from(client.get("/login"))
resp = client.post("/login", data={"csrf_token": tok, "password": PASSWORD})
cookie = resp.headers["Set-Cookie"]
@@ -71,7 +71,7 @@ def test_session_cookie_flags(app, client):
def test_login_throttling(app, client):
app.extensions["voxip"].credentials.set_password(PASSWORD)
app.extensions["daynivo"].credentials.set_password(PASSWORD)
tok = csrf_from(client.get("/login"))
for _ in range(5):
client.post("/login", data={"csrf_token": tok, "password": "verkeerd-wachtwoord"})
@@ -156,7 +156,7 @@ def test_security_headers(client):
def test_api_requires_login(app, client):
app.extensions["voxip"].credentials.set_password(PASSWORD)
app.extensions["daynivo"].credentials.set_password(PASSWORD)
resp = client.get("/api/status")
assert resp.status_code == 401 and resp.json["error"]
+31 -26
View File
@@ -15,16 +15,16 @@ PURGE=0
[[ ${1:-} == "-h" || ${1:-} == "--help" ]] && { sed -n '3,10p' "$0" | sed 's/^# \{0,1\}//'; exit 0; }
[[ $EUID -eq 0 ]] || { echo "Voer dit script uit met sudo." >&2; exit 1; }
UNITS=(voxip-kiosk-healthcheck.timer voxip-kiosk-healthcheck.service voxip-kiosk-agent.service
voxip-kiosk-browser.service voxip-kiosk-x.service voxip-kiosk-manager.service
voxip-network-guard.service voxip-boot-import.service voxip-kiosk-update.timer voxip-kiosk-update.service)
UNITS=(daynivo-kiosk-healthcheck.timer daynivo-kiosk-healthcheck.service daynivo-kiosk-agent.service
daynivo-kiosk-browser.service daynivo-kiosk-x.service daynivo-kiosk-manager.service
daynivo-network-guard.service daynivo-boot-import.service daynivo-kiosk-update.timer daynivo-kiosk-update.service)
PREVIOUS_TARGET="$(cat /var/lib/voxip-kiosk/previous-default-target 2>/dev/null || true)"
PREVIOUS_SPLASH="$(cat /var/lib/voxip-kiosk/previous-plymouth-theme 2>/dev/null || true)"
mapfile -t DISABLED_DMS < <(cat /var/lib/voxip-kiosk/disabled-display-managers 2>/dev/null)
PREVIOUS_TARGET="$(cat /var/lib/daynivo-kiosk/previous-default-target 2>/dev/null || true)"
PREVIOUS_SPLASH="$(cat /var/lib/daynivo-kiosk/previous-plymouth-theme 2>/dev/null || true)"
mapfile -t DISABLED_DMS < <(cat /var/lib/daynivo-kiosk/disabled-display-managers 2>/dev/null)
echo "==> Services stoppen en verwijderen"
systemctl stop voxip-network-rollback.timer 2>/dev/null || true
systemctl stop daynivo-network-rollback.timer 2>/dev/null || true
for unit in "${UNITS[@]}"; do
systemctl disable --now "$unit" 2>/dev/null || true
rm -f "/etc/systemd/system/$unit"
@@ -32,42 +32,47 @@ done
systemctl daemon-reload
echo "==> Bestanden verwijderen"
rm -rf /usr/lib/voxip-kiosk /usr/share/voxip-kiosk /usr/share/doc/voxip-kiosk
rm -rf /usr/lib/daynivo-kiosk /usr/share/daynivo-kiosk /usr/share/doc/daynivo-kiosk
rm -f /usr/local/sbin/daynivo-kiosk-ctl /usr/local/sbin/voxip-kiosk-ctl
rm -f /etc/sudoers.d/voxip-kiosk
rm -f /etc/systemd/journald.conf.d/voxip-kiosk.conf /etc/systemd/system.conf.d/voxip-watchdog.conf
rm -f /etc/logrotate.d/voxip-kiosk /etc/avahi/services/voxip-kiosk.service
rm -f /etc/chromium/policies/managed/voxip-kiosk.json /etc/chromium-browser/policies/managed/voxip-kiosk.json
# Symlinks left by the move from the VoxIP names
for link in /etc/voxip-kiosk /var/lib/voxip-kiosk /var/lib/voxip-kiosk-network /var/lib/voxip-kiosk-browser \
/var/log/voxip-kiosk /var/cache/voxip-kiosk; do
if [[ -L $link ]]; then rm -f "$link"; fi
done
rm -f /etc/sudoers.d/daynivo-kiosk
rm -f /etc/systemd/journald.conf.d/daynivo-kiosk.conf /etc/systemd/system.conf.d/daynivo-watchdog.conf
rm -f /etc/logrotate.d/daynivo-kiosk /etc/avahi/services/daynivo-kiosk.service
rm -f /etc/chromium/policies/managed/daynivo-kiosk.json /etc/chromium-browser/policies/managed/daynivo-kiosk.json
rm -f /etc/systemd/zram-generator.conf
rm -f /etc/ssh/sshd_config.d/voxip-kiosk.conf
if [[ -f /etc/X11/Xwrapper.config.voxip-orig ]]; then
mv /etc/X11/Xwrapper.config.voxip-orig /etc/X11/Xwrapper.config
rm -f /etc/ssh/sshd_config.d/daynivo-kiosk.conf
if [[ -f /etc/X11/Xwrapper.config.daynivo-orig ]]; then
mv /etc/X11/Xwrapper.config.daynivo-orig /etc/X11/Xwrapper.config
else
rm -f /etc/X11/Xwrapper.config
fi
if [[ -d /usr/share/plymouth/themes/voxip-kiosk ]]; then
if [[ -d /usr/share/plymouth/themes/daynivo-kiosk ]]; then
echo "==> Opstartscherm terugzetten (${PREVIOUS_SPLASH:-text})"
rm -rf /usr/share/plymouth/themes/voxip-kiosk
rm -rf /usr/share/plymouth/themes/daynivo-kiosk
plymouth-set-default-theme -R "${PREVIOUS_SPLASH:-text}" >/dev/null 2>&1 || true
fi
if [[ -f /etc/systemd/system/display-kiosk.service.voxip-disabled ]]; then
echo " (de eerdere Daynivo-kioskservice staat nog als display-kiosk.service.voxip-disabled)"
if [[ -f /etc/systemd/system/display-kiosk.service.daynivo-disabled ]]; then
echo " (de eerdere Daynivo-kioskservice staat nog als display-kiosk.service.daynivo-disabled)"
fi
if [[ $PURGE -eq 1 ]]; then
echo "==> Configuratie, status, logs en gebruikers verwijderen"
rm -rf /etc/voxip-kiosk /var/lib/voxip-kiosk /var/lib/voxip-kiosk-network /var/lib/voxip-kiosk-browser /var/log/voxip-kiosk
rm -rf /etc/daynivo-kiosk /var/lib/daynivo-kiosk /var/lib/daynivo-kiosk-network /var/lib/daynivo-kiosk-browser /var/log/daynivo-kiosk
# Downloaded releases (install-online.sh and the updater); safe even when running from there.
rm -rf /opt/daynivo-kiosk-os /var/cache/voxip-kiosk
rm -f /etc/NetworkManager/system-connections/voxip-eth0.nmconnection /etc/NetworkManager/system-connections/voxip-wlan0.nmconnection
rm -rf /opt/daynivo-kiosk-os /var/cache/daynivo-kiosk
rm -f /etc/NetworkManager/system-connections/daynivo-eth0.nmconnection /etc/NetworkManager/system-connections/daynivo-wlan0.nmconnection
nmcli connection reload 2>/dev/null || true
userdel -r kiosk 2>/dev/null || true
userdel voxip 2>/dev/null || true
userdel daynivo 2>/dev/null || true
groupdel kiosk 2>/dev/null || true
groupdel voxip 2>/dev/null || true
groupdel daynivo 2>/dev/null || true
else
echo " Configuratie bewaard in /etc/voxip-kiosk (verwijderen met --purge)."
echo " Daynivo-netwerkprofielen (voxip-eth0/voxip-wlan0) blijven actief."
echo " Configuratie bewaard in /etc/daynivo-kiosk (verwijderen met --purge)."
echo " Daynivo-netwerkprofielen (daynivo-eth0/daynivo-wlan0) blijven actief."
fi
if [[ -n $PREVIOUS_TARGET ]]; then systemctl set-default "$PREVIOUS_TARGET" >/dev/null 2>&1 || true; fi
+10 -7
View File
@@ -5,7 +5,7 @@
# sudo ./update.sh # git pull --ff-only, back up config, install --upgrade
# sudo ./update.sh --no-pull # install the code as it is in this directory
#
# The configuration in /etc/voxip-kiosk is backed up first and never overwritten.
# The configuration in /etc/daynivo-kiosk is backed up first and never overwritten.
set -euo pipefail
@@ -15,7 +15,7 @@ PULL=1
[[ ${1:-} == "-h" || ${1:-} == "--help" ]] && { sed -n '3,9p' "$0" | sed 's/^# \{0,1\}//'; exit 0; }
[[ $EUID -eq 0 ]] || { echo "Voer dit script uit met sudo." >&2; exit 1; }
BEFORE="$(cat /usr/share/voxip-kiosk/VERSION 2>/dev/null || echo 'niet geïnstalleerd')"
BEFORE="$(cat /usr/share/daynivo-kiosk/VERSION 2>/dev/null || cat /usr/share/voxip-kiosk/VERSION 2>/dev/null || echo 'niet geïnstalleerd')"
if [[ $PULL -eq 1 && -d "$SRC/.git" ]]; then
echo "==> Laatste versie ophalen"
@@ -23,16 +23,19 @@ if [[ $PULL -eq 1 && -d "$SRC/.git" ]]; then
sudo -u "$OWNER" git -C "$SRC" pull --ff-only
fi
if [[ -d /etc/voxip-kiosk ]]; then
BACKUP="/var/backups/voxip-kiosk-$(date +%Y%m%d-%H%M%S).tar.gz"
# Before the rename to Daynivo (up to 1.2.x) the configuration lived in /etc/voxip-kiosk.
CONF_DIR=daynivo-kiosk
if [[ ! -d /etc/daynivo-kiosk && -d /etc/voxip-kiosk ]]; then CONF_DIR=voxip-kiosk; fi
if [[ -d /etc/$CONF_DIR ]]; then
BACKUP="/var/backups/daynivo-kiosk-$(date +%Y%m%d-%H%M%S).tar.gz"
echo "==> Configuratie back-uppen naar $BACKUP"
install -d -m 0700 /var/backups
tar -C /etc -czf "$BACKUP" voxip-kiosk
tar -C /etc -czf "$BACKUP" "$CONF_DIR"
chmod 0600 "$BACKUP"
# keep the five most recent backups
ls -1t /var/backups/voxip-kiosk-*.tar.gz 2>/dev/null | tail -n +6 | xargs -r rm -f
ls -1t /var/backups/daynivo-kiosk-*.tar.gz 2>/dev/null | tail -n +6 | xargs -r rm -f
fi
"$SRC/install.sh" --upgrade
echo "==> Bijgewerkt: $BEFORE → $(cat /usr/share/voxip-kiosk/VERSION)"
echo "==> Bijgewerkt: $BEFORE → $(cat /usr/share/daynivo-kiosk/VERSION)"