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

Services (daynivo-kiosk-*, daynivo-network-guard, daynivo-boot-import),
programs in /usr/lib/daynivo-kiosk, folders /etc, /var/lib, /var/log and
/var/cache daynivo-kiosk*, the system user daynivo, DAYNIVO_* settings,
the plymouth theme, network profiles daynivo-eth0/wlan0, the image stage
and its first user daynivoadmin.

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

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rothmanandClaude Opus 5.5 committed 2026-11-05 23:58:03 +01:00
1 parent 6733aacb6e
commit eb0242c182
68 files changed
+527 -424

No files matched your search

+2 -2
View File
@@ -96,7 +96,7 @@ def quiet_system(monkeypatch):
"available": True, "wifi_country": "NL",
"devices": {
iface: {"interface": iface, "kind": kind, "present": True, "state": "connected", "state_label": "Verbonden",
"connected": True, "connection": f"voxip-{iface}", "addresses": ["192.168.10.20/24"], "gateway": "192.168.10.1",
"connected": True, "connection": f"daynivo-{iface}", "addresses": ["192.168.10.20/24"], "gateway": "192.168.10.1",
"dns": ["192.168.10.1"], "hwaddr": "B8:27:EB:00:00:01",
"profile": {"exists": True, "method": "dhcp", "address": "", "gateway": "", "dns": "", "ssid": "Kantoor", "hidden": False}}
for iface, kind in (("eth0", "ethernet"), ("wlan0", "wifi"))
@@ -130,7 +130,7 @@ PASSWORD = "Correct-Paard-Batterij-42"
@pytest.fixture
def logged_in(app, client, paths):
"""A client with a configured admin password and an active session."""
app.extensions["voxip"].credentials.set_password(PASSWORD)
app.extensions["daynivo"].credentials.set_password(PASSWORD)
page = client.get("/login")
token = csrf_from(page)
resp = client.post("/login", data={"password": PASSWORD, "csrf_token": token})
+18 -18
View File
@@ -35,7 +35,7 @@ def helper(paths, tmp_path, boot):
etc.mkdir()
(etc / "hosts").write_text("127.0.0.1\tlocalhost\n127.0.1.1\tkiosk\n")
run = Recorder({"nmcli -t -f": "aaaa-1111\n"})
return h.Helper(paths=paths, run=run, etc=etc, boot_dirs=(boot,), helper_path="/usr/lib/voxip-kiosk/voxip-kiosk-helper")
return h.Helper(paths=paths, run=run, etc=etc, boot_dirs=(boot,), helper_path="/usr/lib/daynivo-kiosk/daynivo-kiosk-helper")
def run_main(helper, argv, env=None, capsys=None):
@@ -45,17 +45,17 @@ def run_main(helper, argv, env=None, capsys=None):
def test_internal_verbs_refused_via_sudo(helper, capsys):
code, out = run_main(helper, ["network-restore"], {"SUDO_USER": "voxip"}, capsys)
code, out = run_main(helper, ["network-restore"], {"SUDO_USER": "daynivo"}, capsys)
assert code == 1 and out["ok"] is False
code, out = run_main(helper, ["boot-import"], {"SUDO_USER": "voxip"}, capsys)
code, out = run_main(helper, ["boot-import"], {"SUDO_USER": "daynivo"}, capsys)
assert code == 1
assert helper.run.commands == []
def test_public_verb_via_sudo(helper, capsys):
code, out = run_main(helper, ["restart-browser"], {"SUDO_USER": "voxip"}, capsys)
code, out = run_main(helper, ["restart-browser"], {"SUDO_USER": "daynivo"}, capsys)
assert code == 0 and out["ok"] is True
assert helper.run.ran("systemctl", "restart", "--no-block", "voxip-kiosk-browser.service")
assert helper.run.ran("systemctl", "restart", "--no-block", "daynivo-kiosk-browser.service")
def test_unknown_verb_and_bad_arity(helper, capsys):
@@ -99,7 +99,7 @@ ETH_STATIC = {"interface": "eth0", "method": "static", "address": "192.168.5.10/
def test_network_apply_snapshot_pending_and_timers(helper, paths):
(paths.nm_connections_dir / "Wired connection 1.nmconnection").write_text("[connection]\nid=Wired connection 1\n")
result = helper.network_apply(ETH_STATIC)
keyfile = paths.nm_connections_dir / "voxip-eth0.nmconnection"
keyfile = paths.nm_connections_dir / "daynivo-eth0.nmconnection"
assert keyfile.exists() and oct(keyfile.stat().st_mode & 0o777) == "0o600"
assert "address1=192.168.5.10/24,192.168.5.1" in keyfile.read_text()
assert (paths.network_backup_dir / "Wired connection 1.nmconnection").exists()
@@ -108,8 +108,8 @@ def test_network_apply_snapshot_pending_and_timers(helper, paths):
assert pending["deadline"] - pending["created"] == 180
assert result["pending"]["interface"] == "eth0"
runs = [c for c in helper.run.commands if c[0] == "systemd-run"]
assert runs[0][1:3] == ["--unit=voxip-network-rollback", "--on-active=180"] and runs[0][-1] == "network-restore"
assert runs[1][1:3] == ["--unit=voxip-network-activate", "--on-active=2"] and runs[1][-2:] == ["network-activate", "eth0"]
assert runs[0][1:3] == ["--unit=daynivo-network-rollback", "--on-active=180"] and runs[0][-1] == "network-restore"
assert runs[1][1:3] == ["--unit=daynivo-network-activate", "--on-active=2"] and runs[1][-2:] == ["network-activate", "eth0"]
def test_second_apply_refused_while_pending(helper):
@@ -123,8 +123,8 @@ def test_confirm_clears_pending(helper, paths):
helper.network_confirm()
assert not paths.network_pending_file.exists()
assert not paths.network_backup_dir.exists()
assert (paths.nm_connections_dir / "voxip-eth0.nmconnection").exists()
assert helper.run.ran("systemctl", "stop", "voxip-network-rollback.timer")
assert (paths.nm_connections_dir / "daynivo-eth0.nmconnection").exists()
assert helper.run.ran("systemctl", "stop", "daynivo-network-rollback.timer")
def test_restore_puts_old_profiles_back(helper, paths):
@@ -134,7 +134,7 @@ def test_restore_puts_old_profiles_back(helper, paths):
old.unlink()
helper.network_restore()
assert old.exists()
assert not (paths.nm_connections_dir / "voxip-eth0.nmconnection").exists()
assert not (paths.nm_connections_dir / "daynivo-eth0.nmconnection").exists()
assert not paths.network_pending_file.exists()
assert helper.run.ran("nmcli", "--wait", "30", "connection", "up", "uuid", "aaaa-1111")
@@ -152,7 +152,7 @@ def test_wifi_keeps_existing_password_for_same_ssid(helper, paths):
helper.network_confirm()
helper.network_apply({"interface": "wlan0", "method": "static", "address": "10.0.0.9/24", "gateway": "10.0.0.1", "dns": "10.0.0.1",
"wifi": {"ssid": "Kantoor", "psk": ""}})
assert "psk=geheim-wachtwoord" in (paths.nm_connections_dir / "voxip-wlan0.nmconnection").read_text()
assert "psk=geheim-wachtwoord" in (paths.nm_connections_dir / "daynivo-wlan0.nmconnection").read_text()
helper.network_confirm()
with pytest.raises(h.HelperError, match="wachtwoord"):
helper.network_apply({"interface": "wlan0", "method": "dhcp", "wifi": {"ssid": "Ander", "psk": ""}})
@@ -165,22 +165,22 @@ def test_boot_guard_rolls_back_and_honours_reset(helper, paths, boot):
helper.run.commands.clear()
result = helper.network_boot_guard()
assert result == {"rolled_back": True}
assert not (paths.nm_connections_dir / "voxip-eth0.nmconnection").exists()
assert not (paths.nm_connections_dir / "daynivo-eth0.nmconnection").exists()
assert not any(c[0] == "nmcli" for c in helper.run.commands), "NetworkManager is not running yet at boot"
(paths.nm_connections_dir / "voxip-wlan0.nmconnection").write_text("x")
(paths.nm_connections_dir / "daynivo-wlan0.nmconnection").write_text("x")
(boot / "daynivo-network-reset").write_text("")
assert helper.network_boot_guard() == {"reset": True}
assert not (paths.nm_connections_dir / "voxip-wlan0.nmconnection").exists()
assert not (paths.nm_connections_dir / "daynivo-wlan0.nmconnection").exists()
assert not (boot / "daynivo-network-reset").exists()
assert old.exists()
def test_boot_guard_still_honours_the_old_reset_name(helper, paths, boot):
(paths.nm_connections_dir / "voxip-eth0.nmconnection").write_text("x")
(paths.nm_connections_dir / "daynivo-eth0.nmconnection").write_text("x")
(boot / "voxip-network-reset").write_text("")
assert helper.network_boot_guard() == {"reset": True}
assert not (paths.nm_connections_dir / "voxip-eth0.nmconnection").exists()
assert not (paths.nm_connections_dir / "daynivo-eth0.nmconnection").exists()
assert not (boot / "voxip-network-reset").exists()
@@ -230,7 +230,7 @@ def test_update_verbs_are_public_and_install_runs_in_its_own_unit(helper):
def test_update_install_reports_a_running_update(helper):
def busy(args, timeout=30, check=True, input=None):
return subprocess.CompletedProcess(args, 1, stdout="", stderr="Unit voxip-kiosk-update-now.service already exists.")
return subprocess.CompletedProcess(args, 1, stdout="", stderr="Unit daynivo-kiosk-update-now.service already exists.")
helper.run = busy
with pytest.raises(h.HelperError, match="al een update"):
+1 -1
View File
@@ -27,7 +27,7 @@ def test_keyfile_static_ethernet():
req = n.validate_request({"interface": "eth0", "method": "static", "address": "10.1.2.3/16", "gateway": "10.1.0.1", "dns": ["10.1.0.1"]})
text = n.build_keyfile(req, uuid="11111111-2222-3333-4444-555555555555")
kf = n.read_keyfile(text)
assert kf.get("connection", "id") == "voxip-eth0"
assert kf.get("connection", "id") == "daynivo-eth0"
assert kf.get("connection", "type") == "ethernet"
assert kf.get("connection", "autoconnect-priority") == "100"
assert kf.get("ipv4", "method") == "manual"
+2 -2
View File
@@ -22,8 +22,8 @@ def settings(unit):
def test_manager_can_use_sudo_for_the_helper():
unit = settings("voxip-kiosk-manager.service")
assert unit["User"] == "voxip"
unit = settings("daynivo-kiosk-manager.service")
assert unit["User"] == "daynivo"
for key in IMPLY_NO_NEW_PRIVILEGES:
assert unit.get(key, "no") in ("no", "false", ""), key
for key in ("ProtectSystem", "ProtectHome", "PrivateTmp"):
+4 -4
View File
@@ -52,7 +52,7 @@ def test_setup_without_csrf_is_rejected(client, paths):
# --- login & session -------------------------------------------------------------
def test_login_logout(app, client):
app.extensions["voxip"].credentials.set_password(PASSWORD)
app.extensions["daynivo"].credentials.set_password(PASSWORD)
assert client.get("/").status_code == 302
tok = csrf_from(client.get("/login"))
assert "Onjuist" in client.post("/login", data={"csrf_token": tok, "password": "fout-wachtwoord"}).get_data(as_text=True)
@@ -63,7 +63,7 @@ def test_login_logout(app, client):
def test_session_cookie_flags(app, client):
app.extensions["voxip"].credentials.set_password(PASSWORD)
app.extensions["daynivo"].credentials.set_password(PASSWORD)
tok = csrf_from(client.get("/login"))
resp = client.post("/login", data={"csrf_token": tok, "password": PASSWORD})
cookie = resp.headers["Set-Cookie"]
@@ -71,7 +71,7 @@ def test_session_cookie_flags(app, client):
def test_login_throttling(app, client):
app.extensions["voxip"].credentials.set_password(PASSWORD)
app.extensions["daynivo"].credentials.set_password(PASSWORD)
tok = csrf_from(client.get("/login"))
for _ in range(5):
client.post("/login", data={"csrf_token": tok, "password": "verkeerd-wachtwoord"})
@@ -156,7 +156,7 @@ def test_security_headers(client):
def test_api_requires_login(app, client):
app.extensions["voxip"].credentials.set_password(PASSWORD)
app.extensions["daynivo"].credentials.set_password(PASSWORD)
resp = client.get("/api/status")
assert resp.status_code == 401 and resp.json["error"]