5 Commits
Author SHA1 Message Date
rothmanandClaude Opus 5.5 88e60f9bf9 Release 1.3.0
CI / test-bookworm (push) Successful in 28s
CI / test-latest (push) Successful in 32s
Release / package (push) Successful in 32s
Release / image (push) Successful in 45m9s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 23:59:04 +01:00
rothmanandClaude Opus 5.5 eb0242c182 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>
2026-11-05 23:58:03 +01:00
rothmanandClaude Opus 5.5 6733aacb6e Rename the Python package to daynivo_kiosk
CI / test-bookworm (push) Successful in 29s
CI / test-latest (push) Successful in 33s
src/voxip_kiosk -> src/daynivo_kiosk, with every import, the launchers,
install.sh, the Makefile and the manager's gunicorn app path. An upgrade
replaces /usr/lib/voxip-kiosk/python as a whole, so the old package
disappears; tested by upgrading an installation of the previous commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 23:52:39 +01:00
rothman d0f3206ff4 Release 1.2.0
CI / test-bookworm (push) Successful in 28s
CI / test-latest (push) Successful in 33s
Release / package (push) Successful in 29s
Release / image (push) Successful in 46m7s
2026-11-05 23:26:41 +01:00
rothmanandClaude Opus 5.5 b0840d240e Daynivo names for the files on the SD card and the command line tool
CI / test-bookworm (push) Successful in 25s
CI / test-latest (push) Successful in 31s
- daynivo-config.json, daynivo-authorized_keys and daynivo-network-reset
  on the boot partition; errors go to daynivo-config.error.txt.
- daynivo-kiosk-ctl, with voxip-kiosk-ctl as a symlink to it.
- The voxip-* names keep working, so existing SD cards and instructions
  stay valid; when both config files are present the Daynivo one is
  applied last.
- Internal names (services, /etc/voxip-kiosk, user voxip) are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 23:25:42 +01:00
106 changed files with 685 additions and 550 deletions

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
+7 -7
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:
@@ -30,7 +30,7 @@ dev:
test -f $(DEV)/etc/config.json || cp config/config.default.json $(DEV)/etc/config.json
cp VERSION $(DEV)/share/VERSION
@echo "Setupcode: zie $(DEV)/etc/setup-code (wordt aangemaakt bij de start)"
$(DEV_ENV) $(PY) -m flask --app 'voxip_kiosk.web:create_app()' run --host 127.0.0.1 --port 8080
$(DEV_ENV) $(PY) -m flask --app 'daynivo_kiosk.web:create_app()' run --host 127.0.0.1 --port 8080
image:
./image/build.sh
+56 -31
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
@@ -31,7 +31,7 @@ hardware staat in [`docs/HARDWARE-TEST.md`](docs/HARDWARE-TEST.md).
3. Optioneel, in Raspberry Pi Imager → *OS-aanpassingen*: wifi, gebruiker
met wachtwoord en/of SSH-sleutel, tijdzone. Zonder aanpassingen heeft de
image **geen** bruikbaar wachtwoord en staat SSH uit.
4. Optioneel: zet een `voxip-config.json` op de bootpartitie (zie
4. Optioneel: zet een `daynivo-config.json` op de bootpartitie (zie
[Uitrol bij meerdere klanten](#uitrol-bij-meerdere-klanten)).
5. Start de Pi. Bij de eerste boot wordt de rootpartitie automatisch
vergroot tot de hele SD-kaart. Daarna verschijnt een setupscherm met de
@@ -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.
@@ -191,18 +216,18 @@ browser blijft draaien. *Chromium herstarten* herstart alleen de browser.
### Commandoregel (via SSH)
```bash
sudo voxip-kiosk-ctl status # overzicht
sudo voxip-kiosk-ctl setup-code # setupcode voor de eerste configuratie
sudo voxip-kiosk-ctl reset-password # wachtwoord vergeten: nieuwe setupcode
sudo voxip-kiosk-ctl set-url https://… # URL wijzigen
sudo voxip-kiosk-ctl reload # pagina herladen
sudo voxip-kiosk-ctl restart-browser # Chromium herstarten
sudo voxip-kiosk-ctl health # healthcheck-rapport
sudo voxip-kiosk-ctl maintenance on|off # healthcheck tijdelijk niets laten herstellen
journalctl -u voxip-kiosk-browser -u voxip-kiosk-agent -f
sudo daynivo-kiosk-ctl status # overzicht
sudo daynivo-kiosk-ctl setup-code # setupcode voor de eerste configuratie
sudo daynivo-kiosk-ctl reset-password # wachtwoord vergeten: nieuwe setupcode
sudo daynivo-kiosk-ctl set-url https://… # URL wijzigen
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 daynivo-kiosk-browser -u daynivo-kiosk-agent -f
```
### Configuratie — `/etc/voxip-kiosk/config.json`
### Configuratie — `/etc/daynivo-kiosk/config.json`
| Sleutel | Standaard | Betekenis |
|---|---|---|
@@ -221,7 +246,7 @@ journalctl -u voxip-kiosk-browser -u voxip-kiosk-agent -f
| `admin_allow_public` | `false` | ook toegang vanaf publieke IP-adressen (afgeraden) |
| `auto_update` | `false` | elke nacht een nieuwe release installeren |
Wijzigingen via de webinterface of `voxip-kiosk-ctl` worden atomair
Wijzigingen via de webinterface of `daynivo-kiosk-ctl` worden atomair
opgeslagen, met een `.bak` van de vorige versie. Een URL-wijziging gaat in
zonder dat Chromium herstart. Scherminstellingen gelden na een herstart van
Chromium (een vinkje in de webinterface doet dat direct).
@@ -231,15 +256,15 @@ Chromium (een vinkje in de webinterface doet dat direct).
Eén image voor alle klanten. Per apparaat zet je na het flashen op de
bootpartitie (`bootfs`, zichtbaar op Windows en macOS):
- **`voxip-config.json`**: instellingen voor deze kiosk, plus optioneel
- **`daynivo-config.json`**: instellingen voor deze kiosk, plus optioneel
`hostname` en `wifi_country`. Een voorbeeld staat als
`voxip-config.example.json` op de bootpartitie.
- **`voxip-authorized_keys`**: openbare SSH-sleutels. SSH gaat dan aan, alleen
`daynivo-config.example.json` op de bootpartitie.
- **`daynivo-authorized_keys`**: openbare SSH-sleutels. SSH gaat dan aan, alleen
met sleutel-login.
- Wifi en een eigen gebruiker stel je in via Raspberry Pi Imager.
De bestanden worden bij het opstarten verwerkt en hernoemd naar
`*.applied`, of naar `*.rejected` met de reden in `voxip-config.error.txt`.
`*.applied`, of naar `*.rejected` met de reden in `daynivo-config.error.txt`.
### Daynivo-schermen
@@ -270,8 +295,8 @@ make dev # http://127.0.0.1:8080 met tijdelijke mappen in .dev/
| Symptoom | Oplossing |
|---|---|
| Zwart scherm na opstarten | `sudo voxip-kiosk-ctl status`. Kijk in de journal van `voxip-kiosk-x`/`voxip-kiosk-browser`. Staat de monitor aan vóór de Pi? |
| Beheerinterface onbereikbaar na netwerkwijziging | Wacht 3 minuten (automatische rollback) of zet `voxip-network-reset` op de bootpartitie en herstart. |
| Wachtwoord vergeten | `sudo voxip-kiosk-ctl reset-password` via SSH, of verwijder `/etc/voxip-kiosk/admin.json` met de SD-kaart in een Linux-pc. |
| 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/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. |
+1 -1
View File
@@ -1 +1 @@
1.1.0
1.3.0
+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())
+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.ctl 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.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 voxip_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 voxip_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 voxip_kiosk.ctl 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 voxip_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 voxip_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 voxip_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 voxip_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
+32 -32
View File
@@ -11,33 +11,33 @@ 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
worden meegegeven (`voxip-config.json`).
`/etc/daynivo-kiosk/config.json` en kan bij uitrol via de bootpartitie
worden meegegeven (`daynivo-config.json`).
## Overzicht
```
┌──────────────────────── Raspberry Pi ────────────────────────┐
│ │
HDMI ◄────────┤ voxip-kiosk-x.service Xorg :0 op vt7 (user kiosk) │
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
@@ -104,33 +104,33 @@ Zolang er geen beheerderswachtwoord is:
adressen van de beheerinterface;
3. kan een wachtwoord alleen ingesteld worden met die code.
Wie fysiek bij het scherm staat (of via SSH `voxip-kiosk-ctl setup-code`
Wie fysiek bij het scherm staat (of via SSH `daynivo-kiosk-ctl setup-code`
draait) kan het apparaat dus claimen, iemand anders op het netwerk niet.
### Netwerkwijzigingen met rollback
1. De helper maakt een snapshot van `/etc/NetworkManager/system-connections`
en van de actieve verbindingen.
2. Hij schrijft het nieuwe profiel (`voxip-eth0` / `voxip-wlan0`, prioriteit
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 `voxip-network-reset` op de bootpartitie
6. Noodherstel: een leeg bestand `daynivo-network-reset` op de bootpartitie
verwijdert bij de volgende boot alle Daynivo-netwerkprofielen (terug naar
DHCP).
### Configuratie
- `/etc/voxip-kiosk/config.json`: eigenaar `voxip`, groep `kiosk`, 0640.
- `/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
@@ -165,7 +165,7 @@ draait) kan het apparaat dus claimen, iemand anders op het netwerk niet.
(toegestaan: privé, link-local, loopback, CGNAT/VPN 100.64.0.0/10).
- Optioneel HTTPS (zelfondertekend certificaat, automatisch aangemaakt).
- SSH staat in de image standaard uit en wordt alleen ingeschakeld met
credentials uit Raspberry Pi Imager of `voxip-authorized_keys` op de
credentials uit Raspberry Pi Imager of `daynivo-authorized_keys` op de
bootpartitie (dan alleen sleutel-login).
- Strikte security-headers en CSP (`default-src 'self'`).
@@ -179,8 +179,8 @@ 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/
├── src/voxip_kiosk/ Python-package (alle logica)
├── bin/ dunne launchers → /usr/lib/daynivo-kiosk/
├── src/daynivo_kiosk/ Python-package (alle logica)
│ ├── config.py laden/valideren/atomair opslaan
│ ├── validation.py gedeelde invoervalidatie
│ ├── cdp.py Chrome DevTools Protocol-client
@@ -192,12 +192,12 @@ daynivo-kiosk-os/
│ ├── netinfo.py netwerkstatus (zonder rechten)
│ ├── sysinfo.py CPU, temperatuur, geheugen, opslag, IP
│ ├── services.py systemd- en journal-status
│ ├── ctl.py CLI voor SSH-beheer (voxip-kiosk-ctl)
│ ├── ctl.py CLI voor SSH-beheer (daynivo-kiosk-ctl)
│ └── web/ Flask-app, templates, statische bestanden
├── www/ lokale kioskpagina's (start, offline, setup)
├── systemd/ unit-bestanden
├── config/ standaardconfig, sudoers, journald, logrotate, …
├── image/ pi-gen image-builder (stage-voxip)
├── image/ pi-gen image-builder (stage-daynivo)
├── tests/ pytest-suite
└── docs/ architectuur, hardwaretest
```
+13 -13
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,23 +47,23 @@ 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, `voxip-kiosk-ctl status`, `journalctl -u voxip-kiosk-agent`).
(dashboard, `daynivo-kiosk-ctl status`, `journalctl -u daynivo-kiosk-agent`).
## 4. Herstel
- [ ] `sudo pkill -9 chromium`: binnen 15 s is de pagina terug. Het logboek
toont de herstart.
- [ ] `sudo pkill -9 Xorg`: X en Chromium komen binnen 20 s terug.
- [ ] Crashloop: `sudo voxip-kiosk-ctl set-url https://localhost:1/` en
- [ ] Crashloop: `sudo daynivo-kiosk-ctl set-url https://localhost:1/` en
daarna 7× `sudo pkill -9 chromium` binnen 2 minuten. De service komt
in de fouttoestand (start-limit). De healthcheck logt een fout en start
hem na ongeveer 5 minuten opnieuw. Er is geen eindeloze stille lus.
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
@@ -82,7 +82,7 @@ Noteer per stap: ✅ / ❌ + opmerking, de imageversie en het schermtype.
- [ ] **Rollback na stroomuitval:** herhaal, trek binnen 3 minuten de stroom
eruit. Na het opstarten zijn de oude instellingen actief.
- [ ] **Noodreset:** stel iets onbereikbaars in en bevestig het. Zet
`voxip-network-reset` op de bootpartitie en start: weer DHCP.
`daynivo-network-reset` op de bootpartitie en start: weer DHCP.
## 6. Systeem
@@ -104,7 +104,7 @@ Noteer per stap: ✅ / ❌ + opmerking, de imageversie en het schermtype.
zonder ingrijpen terug in de kiosk, zonder "herstellen"-melding.
- [ ] 72 uur continu draaien met verversinterval 5 min. Daarna: geheugen
< 80% (dashboard), geen onderspanning, `journalctl -p err -b` zonder
terugkerende fouten, `voxip-kiosk-ctl status` alles *Actief*.
terugkerende fouten, `daynivo-kiosk-ctl status` alles *Actief*.
- [ ] SD-kaartschrijfacties: `cat /proc/diskstats | grep mmcblk0` op t=0 en
na 1 uur stationair. Het verschil in geschreven sectoren (kolom 10) is
minder dan 50 MB per uur.
@@ -112,22 +112,22 @@ Noteer per stap: ✅ / ❌ + opmerking, de imageversie en het schermtype.
## 8. Uitrol en updates
- [ ] Nieuwe kaart met `voxip-config.json` (url, device_name, hostname,
rotation) en `voxip-authorized_keys` op de bootpartitie: bij de eerste
- [ ] Nieuwe kaart met `daynivo-config.json` (url, device_name, hostname,
rotation) en `daynivo-authorized_keys` op de bootpartitie: bij de eerste
boot toegepast, hernoemd naar `.applied`, SSH alleen met sleutel.
- [ ] Ongeldige `voxip-config.json` (http-URL): `.rejected` en
`voxip-config.error.txt` met de reden. De kiosk start met de standaardconfiguratie.
- [ ] Ongeldige `daynivo-config.json` (http-URL): `.rejected` en
`daynivo-config.error.txt` met de reden. De kiosk start met de standaardconfiguratie.
- [ ] `sudo ./update.sh` vanuit een nieuwere checkout: versie in het dashboard
verhoogd, configuratie en wachtwoord ongewijzigd, back-up in `/var/backups`.
- [ ] Systeem → Software-updates → *Nu controleren* met een nieuwere release
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.
+7 -7
View File
@@ -4,11 +4,11 @@ Daynivo Kiosk OS — bestanden op deze bootpartitie
Je kunt een kiosk vooraf instellen door vóór de eerste start bestanden op
deze partitie te zetten (bijvoorbeeld direct na het flashen). Ze worden bij
het opstarten verwerkt en daarna hernoemd naar *.applied (of *.rejected bij
een fout; de reden staat dan in voxip-config.error.txt).
een fout; de reden staat dan in daynivo-config.error.txt).
voxip-config.json
Instellingen voor deze kiosk. Zie voxip-config.example.json. Toegestaan:
alle sleutels uit /etc/voxip-kiosk/config.json plus "hostname" en
daynivo-config.json
Instellingen voor deze kiosk. Zie daynivo-config.example.json. Toegestaan:
alle sleutels uit /etc/daynivo-kiosk/config.json plus "hostname" en
"wifi_country". Voorbeeld:
{
@@ -18,12 +18,12 @@ voxip-config.json
"rotation": 90
}
voxip-authorized_keys
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).
voxip-network-reset
daynivo-network-reset
Leeg bestand. Verwijdert bij de volgende start alle via de
beheerinterface ingestelde netwerkprofielen (terug naar DHCP). Gebruik
dit als de kiosk na een netwerkwijziging onbereikbaar is.
+14 -13
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,12 +68,12 @@ 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
voxip_kiosk_os=$VERSION
daynivo_kiosk_os=$VERSION
source_revision=$SOURCE_REV
pi_gen_ref=$PI_GEN_REF
pi_gen_commit=$PI_GEN_COMMIT
@@ -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)"
File renamed without changes.
+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/voxip-config.example.json "${ROOTFS_DIR}/boot/firmware/voxip-config.example.json"
+129 -58
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; }
@@ -54,7 +54,7 @@ while [[ $# -gt 0 ]]; do
done
[[ $EUID -eq 0 ]] || die "Voer dit script uit met sudo."
[[ -f "$SRC/src/voxip_kiosk/config.py" ]] || die "Start install.sh vanuit de Daynivo Kiosk OS-repository."
[[ -f "$SRC/src/daynivo_kiosk/config.py" ]] || die "Start install.sh vanuit de Daynivo Kiosk OS-repository."
in_chroot() {
if command -v systemd-detect-virt >/dev/null 2>&1 && systemd-detect-virt --chroot --quiet; then
@@ -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 --------------------------------------------------------------
@@ -126,7 +195,7 @@ say "Programmabestanden installeren"
install -d -m 0755 "$LIB" "$SHARE" "$DOC"
rm -rf "$LIB/python.new"
install -d -m 0755 "$LIB/python.new"
tar -C "$SRC/src" --exclude='__pycache__' --exclude='*.pyc' -cf - voxip_kiosk | tar -C "$LIB/python.new" -xf -
tar -C "$SRC/src" --exclude='__pycache__' --exclude='*.pyc' -cf - daynivo_kiosk | tar -C "$LIB/python.new" -xf -
chown -R root:root "$LIB/python.new"
chmod -R u=rwX,go=rX "$LIB/python.new"
python3 -m compileall -q "$LIB/python.new" >/dev/null
@@ -135,10 +204,12 @@ 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/voxip-kiosk-ctl" /usr/local/sbin/voxip-kiosk-ctl
install -m 0755 -o root -g root "$SRC/bin/daynivo-kiosk-ctl" /usr/local/sbin/daynivo-kiosk-ctl
# The name from before the rename to Daynivo keeps working.
ln -sfn daynivo-kiosk-ctl /usr/local/sbin/voxip-kiosk-ctl
rm -rf "$SHARE/www"
install -d -m 0755 "$SHARE/www"
@@ -149,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
@@ -163,14 +234,14 @@ 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")
from voxip_kiosk import config as c
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 = {}
if url: updates["url"] = url
@@ -192,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 voxip_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
@@ -261,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
@@ -284,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."
@@ -300,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
@@ -319,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
@@ -340,4 +411,4 @@ if [[ ! -f $ETC/admin.json ]]; then
echo " Setupcode: $(cat "$ETC/setup-code") (staat ook op het scherm)"
fi
fi
echo " Status: sudo voxip-kiosk-ctl status"
echo " Status: sudo daynivo-kiosk-ctl status"
+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"
File renamed without changes.
@@ -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
@@ -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)
File renamed without changes.
@@ -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)
@@ -1,4 +1,4 @@
"""voxip-kiosk-ctl — command-line management over SSH (run with sudo)."""
"""daynivo-kiosk-ctl — command-line management over SSH (run with sudo)."""
from __future__ import annotations
@@ -33,7 +33,7 @@ def cmd_status(args) -> int:
print(f"Apparaat: {config['device_name']}")
print(f"URL: {config['url'] if args.show_secrets else mask_url(config['url'])}")
print(f"Beheer: {'https' if config['admin_https'] else 'http'}://{os.uname().nodename}.local:{config['admin_port']}")
print(f"Wachtwoord: {'ingesteld' if paths.admin_file.exists() else 'NOG NIET INGESTELD (setupcode: sudo voxip-kiosk-ctl setup-code)'}")
print(f"Wachtwoord: {'ingesteld' if paths.admin_file.exists() else 'NOG NIET INGESTELD (setupcode: sudo daynivo-kiosk-ctl setup-code)'}")
for key, info in services.summary().items():
print(f"{info['unit']:<34} {info['label']} (herstarts: {info['restarts']})")
if agent:
@@ -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
@@ -138,7 +138,7 @@ def cmd_version(_args) -> int:
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(prog="voxip-kiosk-ctl", description="Beheer van Daynivo Kiosk OS vanaf de commandoregel.")
parser = argparse.ArgumentParser(prog="daynivo-kiosk-ctl", description="Beheer van Daynivo Kiosk OS vanaf de commandoregel.")
sub = parser.add_subparsers(dest="command", required=True)
p = sub.add_parser("status", help="toon de status")
p.add_argument("--show-secrets", action="store_true", help="toon de volledige URL inclusief token")
@@ -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)
@@ -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,15 +27,17 @@ 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"))
RESET_TRIGGER = "voxip-network-reset"
IMPORT_CONFIG = "voxip-config.json"
IMPORT_KEYS = "voxip-authorized_keys"
# 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
IMPORT_KEYS = ("voxip-authorized_keys", "daynivo-authorized_keys")
SSH_KEY_TYPES = ("ssh-ed25519", "ssh-rsa", "ecdsa-sha2-", "sk-ssh-ed25519@openssh.com", "sk-ecdsa-sha2-")
# Verbs the web interface may call through sudo. Everything else is only
@@ -81,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."}
@@ -306,15 +308,16 @@ class Helper:
self.network_restore(boot=True)
result["rolled_back"] = True
boot = self._boot_dir()
trigger = boot / RESET_TRIGGER if boot else None
if trigger and trigger.exists():
triggers = [boot / name for name in RESET_TRIGGERS if (boot / name).exists()] if boot else []
if triggers:
stash = self.paths.network_state_dir / f"disabled-{int(time.time())}"
stash.mkdir(parents=True, mode=0o700)
for iface in netconfig.INTERFACES:
src = self._keyfile_path(iface)
if src.exists():
shutil.move(str(src), str(stash / src.name))
trigger.unlink()
for trigger in triggers:
trigger.unlink()
log(syslog.LOG_WARNING, f"Netwerk-reset uitgevoerd; Daynivo-profielen verplaatst naar {stash}")
result["reset"] = True
return result
@@ -323,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
@@ -335,12 +338,14 @@ class Helper:
if boot is None:
return {"imported": []}
imported = []
conf = boot / IMPORT_CONFIG
if conf.exists():
for name in IMPORT_CONFIGS:
conf = boot / name
if not conf.exists():
continue
try:
data = json.loads(conf.read_text(encoding="utf-8-sig"))
if not isinstance(data, dict):
raise HelperError("voxip-config.json moet een JSON-object zijn")
raise HelperError(f"{name} moet een JSON-object zijn")
data = dict(data)
host = data.pop("hostname", None)
country = data.pop("wifi_country", None)
@@ -351,22 +356,24 @@ class Helper:
self.set_hostname(str(host))
if country:
self.set_wifi_country(str(country))
conf.rename(conf.with_name(IMPORT_CONFIG + ".applied"))
imported.append(IMPORT_CONFIG)
log(syslog.LOG_NOTICE, "Configuratie geïmporteerd vanaf de bootpartitie")
conf.rename(conf.with_name(name + ".applied"))
imported.append(name)
log(syslog.LOG_NOTICE, f"Configuratie geïmporteerd vanaf de bootpartitie ({name})")
except (HelperError, cfg.ConfigError, v.ValidationError, ValueError, OSError) as exc:
(boot / "voxip-config.error.txt").write_text(f"{exc}\n", encoding="utf-8")
conf.rename(conf.with_name(IMPORT_CONFIG + ".rejected"))
log(syslog.LOG_ERR, f"voxip-config.json geweigerd: {exc}")
keys = boot / IMPORT_KEYS
if keys.exists():
(boot / name.replace(".json", ".error.txt")).write_text(f"{exc}\n", encoding="utf-8")
conf.rename(conf.with_name(name + ".rejected"))
log(syslog.LOG_ERR, f"{name} geweigerd: {exc}")
for name in IMPORT_KEYS:
keys = boot / name
if not keys.exists():
continue
try:
self._import_ssh_keys(keys.read_text(encoding="utf-8-sig"))
keys.rename(keys.with_name(IMPORT_KEYS + ".applied"))
imported.append(IMPORT_KEYS)
keys.rename(keys.with_name(name + ".applied"))
imported.append(name)
except (HelperError, OSError) as exc:
keys.rename(keys.with_name(IMPORT_KEYS + ".rejected"))
log(syslog.LOG_ERR, f"SSH-sleutels geweigerd: {exc}")
keys.rename(keys.with_name(name + ".rejected"))
log(syslog.LOG_ERR, f"SSH-sleutels geweigerd ({name}): {exc}")
return {"imported": imported}
def _import_ssh_keys(self, text: str) -> None:
@@ -385,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",
@@ -442,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")
@@ -453,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 ""
File renamed without changes.
File renamed without changes.
@@ -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]:
@@ -55,7 +55,7 @@ def gunicorn_argv(paths: Paths, config: dict) -> list[str]:
if config["admin_https"]:
cert, key = ensure_certificate(paths)
argv += ["--certfile", str(cert), "--keyfile", str(key)]
argv.append("voxip_kiosk.web:create_app()")
argv.append("daynivo_kiosk.web:create_app()")
return argv
@@ -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
File renamed without changes.
@@ -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",
}
@@ -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)
File renamed without changes.
File renamed without changes.
@@ -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")
File renamed without changes.
@@ -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(),
@@ -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:
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

@@ -12,6 +12,6 @@
</label>
<button type="submit" class="btn btn-primary btn-block">Inloggen</button>
</form>
<p class="muted small">Wachtwoord vergeten? Log via SSH in en voer <code>sudo voxip-kiosk-ctl reset-password</code> uit.</p>
<p class="muted small">Wachtwoord vergeten? Log via SSH in en voer <code>sudo daynivo-kiosk-ctl reset-password</code> uit.</p>
</section>
{% endblock %}
@@ -94,7 +94,7 @@
<h2>Herstel bij een onbereikbare kiosk</h2>
<ul class="plain">
<li>Iedere wijziging wordt na {{ rollback_seconds // 60 }} minuten automatisch teruggedraaid als je hem niet bevestigt, ook als de stroom tussendoor uitvalt.</li>
<li>Noodherstel: zet een leeg bestand <code>voxip-network-reset</code> op de bootpartitie van de SD-kaart en start de Pi. Alle Daynivo-netwerkprofielen worden dan verwijderd (terug naar DHCP).</li>
<li>Noodherstel: zet een leeg bestand <code>daynivo-network-reset</code> op de bootpartitie van de SD-kaart en start de Pi. Alle Daynivo-netwerkprofielen worden dan verwijderd (terug naar DHCP).</li>
</ul>
</section>
{% endblock %}
@@ -4,7 +4,7 @@
<section class="card card-narrow">
<h1>Welkom bij Daynivo Kiosk Manager</h1>
<p>Stel eerst een uniek beheerderswachtwoord in. Je hebt daarvoor de <strong>setupcode</strong> nodig die op het
scherm van de kiosk staat (of via SSH: <code>sudo voxip-kiosk-ctl setup-code</code>).</p>
scherm van de kiosk staat (of via SSH: <code>sudo daynivo-kiosk-ctl setup-code</code>).</p>
{% if error %}<div class="alert alert-error" role="alert">{{ error }}</div>{% endif %}
<form method="post" class="form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
@@ -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]:
+21
View File
@@ -0,0 +1,21 @@
[Unit]
Description=Daynivo configuratie en SSH-sleutels importeren vanaf de bootpartitie
Documentation=file:///usr/share/doc/daynivo-kiosk/README.md
After=local-fs.target
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
ConditionPathExistsGlob=|/boot/daynivo-authorized_keys
ConditionPathExistsGlob=|/boot/firmware/voxip-config.json
ConditionPathExistsGlob=|/boot/firmware/voxip-authorized_keys
ConditionPathExistsGlob=|/boot/voxip-config.json
ConditionPathExistsGlob=|/boot/voxip-authorized_keys
[Service]
Type=oneshot
RemainAfterExit=yes
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
-17
View File
@@ -1,17 +0,0 @@
[Unit]
Description=Daynivo configuratie en SSH-sleutels importeren vanaf de bootpartitie
Documentation=file:///usr/share/doc/voxip-kiosk/README.md
After=local-fs.target
Before=voxip-kiosk-manager.service voxip-kiosk-browser.service ssh.service
ConditionPathExistsGlob=|/boot/firmware/voxip-config.json
ConditionPathExistsGlob=|/boot/firmware/voxip-authorized_keys
ConditionPathExistsGlob=|/boot/voxip-config.json
ConditionPathExistsGlob=|/boot/voxip-authorized_keys
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/lib/voxip-kiosk/voxip-kiosk-helper boot-import
[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
+7 -7
View File
@@ -4,7 +4,7 @@ from pathlib import Path
import pytest
from voxip_kiosk.paths import Paths
from daynivo_kiosk.paths import Paths
ROOT = Path(__file__).resolve().parents[1]
@@ -39,7 +39,7 @@ class FakeHelper:
self.responses = {}
def call(self, verb, *args, payload=None, timeout=60):
from voxip_kiosk.helper_client import HelperError
from daynivo_kiosk.helper_client import HelperError
self.calls.append((verb, args, payload))
if self.fail:
@@ -53,7 +53,7 @@ class FakeCDP:
self.fail = None
def _do(self, name, *args):
from voxip_kiosk.cdp import CDPError
from daynivo_kiosk.cdp import CDPError
self.calls.append((name, args))
if self.fail:
@@ -80,7 +80,7 @@ def fake_cdp():
@pytest.fixture
def quiet_system(monkeypatch):
"""Keep the web tests independent of the machine they run on."""
from voxip_kiosk import netinfo, services, sysinfo
from daynivo_kiosk import netinfo, services, sysinfo
monkeypatch.setattr(services, "summary", lambda: {
"browser": {"unit": services.BROWSER_UNIT, "status": "ok", "label": "Actief", "restarts": "2", "since": ""},
@@ -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"))
@@ -106,7 +106,7 @@ def quiet_system(monkeypatch):
@pytest.fixture
def app(paths, fake_helper, fake_cdp, quiet_system):
from voxip_kiosk.web import create_app
from daynivo_kiosk.web import create_app
application = create_app(paths, helper=fake_helper, cdp=fake_cdp)
application.config["TESTING"] = True
@@ -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})
+3 -3
View File
@@ -2,9 +2,9 @@ import json
import pytest
from voxip_kiosk.agent import Agent, HUNG_RELOAD_AFTER, HUNG_RESTART_AFTER
from voxip_kiosk.cdp import CDPError
from voxip_kiosk.config import save_config
from daynivo_kiosk.agent import Agent, HUNG_RELOAD_AFTER, HUNG_RESTART_AFTER
from daynivo_kiosk.cdp import CDPError
from daynivo_kiosk.config import save_config
class Clock:
+2 -2
View File
@@ -2,8 +2,8 @@ import json
from pathlib import Path
from urllib.parse import parse_qs, urlsplit
from voxip_kiosk import browser as b
from voxip_kiosk.config import DEFAULTS
from daynivo_kiosk import browser as b
from daynivo_kiosk.config import DEFAULTS
XRANDR = """Screen 0: minimum 320 x 200, current 1920 x 1080, maximum 7680 x 7680
HDMI-1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 531mm x 299mm
+1 -1
View File
@@ -4,7 +4,7 @@ import stat
import pytest
from voxip_kiosk import config as c
from daynivo_kiosk import config as c
def test_default_file_matches_defaults(paths):
+4 -4
View File
@@ -1,9 +1,9 @@
import pytest
from voxip_kiosk import services
from voxip_kiosk.cdp import CDPError
from voxip_kiosk.config import save_config
from voxip_kiosk.healthcheck import CDP_FAILURES_BEFORE_RESTART, DISPLAY_RETRY_AFTER, HealthCheck
from daynivo_kiosk import services
from daynivo_kiosk.cdp import CDPError
from daynivo_kiosk.config import save_config
from daynivo_kiosk.healthcheck import CDP_FAILURES_BEFORE_RESTART, DISPLAY_RETRY_AFTER, HealthCheck
class World:
+46 -27
View File
@@ -3,8 +3,8 @@ import subprocess
import pytest
from voxip_kiosk import helper as h
from voxip_kiosk.config import load_config
from daynivo_kiosk import helper as h
from daynivo_kiosk.config import load_config
class Recorder:
@@ -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,39 +165,58 @@ 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")
(boot / "voxip-network-reset").write_text("")
(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 (boot / "voxip-network-reset").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 / "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 / "daynivo-eth0.nmconnection").exists()
assert not (boot / "voxip-network-reset").exists()
def test_boot_import_applies_config(helper, paths, boot):
(boot / "voxip-config.json").write_text(json.dumps({"device_name": "Hal", "url": "https://klant.example.nl/display/hal?token=x",
"hostname": "hal", "rotation": 90}))
assert helper.boot_import() == {"imported": ["voxip-config.json"]}
(boot / "daynivo-config.json").write_text(json.dumps({"device_name": "Hal", "url": "https://klant.example.nl/display/hal?token=x",
"hostname": "hal", "rotation": 90}))
assert helper.boot_import() == {"imported": ["daynivo-config.json"]}
conf = load_config(paths.config_file)
assert conf["device_name"] == "Hal" and conf["rotation"] == 90 and conf["url"].startswith("https://klant")
assert (helper.etc / "hostname").read_text() == "hal\n"
assert (boot / "daynivo-config.json.applied").exists()
def test_boot_import_still_accepts_the_old_names(helper, paths, boot):
(boot / "voxip-config.json").write_text(json.dumps({"device_name": "Oud", "rotation": 180}))
(boot / "daynivo-config.json").write_text(json.dumps({"device_name": "Nieuw"}))
assert helper.boot_import() == {"imported": ["voxip-config.json", "daynivo-config.json"]}
conf = load_config(paths.config_file)
assert conf["device_name"] == "Nieuw" and conf["rotation"] == 180 # the Daynivo file is applied last
assert (boot / "voxip-config.json.applied").exists()
def test_boot_import_rejects_invalid_config(helper, paths, boot):
before = paths.config_file.read_text()
(boot / "voxip-config.json").write_text(json.dumps({"url": "http://onveilig.example"}))
(boot / "daynivo-config.json").write_text(json.dumps({"url": "http://onveilig.example"}))
helper.boot_import()
assert paths.config_file.read_text() == before
assert (boot / "voxip-config.json.rejected").exists()
assert "HTTPS" in (boot / "voxip-config.error.txt").read_text()
assert (boot / "daynivo-config.json.rejected").exists()
assert "HTTPS" in (boot / "daynivo-config.error.txt").read_text()
def test_boot_import_rejects_non_key_lines(helper, boot):
(boot / "voxip-authorized_keys").write_text("dit is geen sleutel\n")
(boot / "daynivo-authorized_keys").write_text("dit is geen sleutel\n")
(boot / "voxip-authorized_keys").write_text("ook geen sleutel\n")
helper.boot_import()
assert (boot / "daynivo-authorized_keys.rejected").exists()
assert (boot / "voxip-authorized_keys.rejected").exists()
@@ -211,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"):
+3 -3
View File
@@ -1,7 +1,7 @@
import pytest
from voxip_kiosk import netconfig as n
from voxip_kiosk.validation import ValidationError
from daynivo_kiosk import netconfig as n
from daynivo_kiosk.validation import ValidationError
def test_validate_static_ethernet():
@@ -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"
+1 -1
View File
@@ -1,4 +1,4 @@
from voxip_kiosk import services, sysinfo
from daynivo_kiosk import services, sysinfo
def test_parse_throttled():
+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"):
Loaded 100 of 106 files, more files were not shown because too many files have changed in this diff. Show more