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>
This commit is contained in:
rothmanandClaude Opus 5.5 committed 2026-11-05 23:25:42 +01:00
1 parent 348644a48a
commit b0840d240e
17 files changed
+108 -76

No files matched your search

+3 -3
View File
@@ -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:
@@ -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")
+28 -21
View File
@@ -33,9 +33,11 @@ UPDATE_UNIT = "voxip-kiosk-update-now"
UPDATER_PATH = "/usr/lib/voxip-kiosk/voxip-kiosk-updater"
ACTIVATE_UNIT = "voxip-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 voxip-* 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
@@ -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
@@ -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:
+1 -1
View File
@@ -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 %}
+1 -1
View File
@@ -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 %}
+1 -1
View File
@@ -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() }}">