Angelehnt an die bestehende USB Tankstelle, um Gottesdienste und andere Veranstaltungen per Auswahl auf einen USB Speicher zu kopieren.
  • Rust 86.6%
  • Slint 8.4%
  • Shell 3.8%
  • PowerShell 1.2%
Find a file
audioprog 749a563823
All checks were successful
test / test (push) Successful in 10m13s
release / test (push) Successful in 7m24s
release / build-rpm (push) Successful in 30m30s
release / build-windows (push) Successful in 16m28s
release / build-deb (push) Successful in 16m46s
release / release (push) Successful in 24s
v0.0.52: Kiosk-Shell im Eigenbetrieb, wenn winkioskshell fehlt
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 12:49:10 +00:00
.forgejo/workflows v0.0.51: Forgejo-Releases mit allen Artefakten 2026-07-29 08:08:10 +00:00
.vscode feat: Implement experimental Servo integration for browser tab 2026-07-15 07:23:52 +00:00
deploy Phase 3: Kiosk-Shell im Eigenbetrieb, wenn winkioskshell fehlt 2026-07-29 12:35:59 +00:00
examples Version 0.0.18: Cache-Aktualitaet (Last-Modified-Abgleich) 2026-06-26 17:40:55 +02:00
packaging v0.0.45: Updates nur noch über die Paketverwaltung + Paket-Korrekturen 2026-07-27 10:52:06 +00:00
scripts v0.0.45: Updates nur noch über die Paketverwaltung + Paket-Korrekturen 2026-07-27 10:52:06 +00:00
src Phase 3: Kiosk-Shell im Eigenbetrieb, wenn winkioskshell fehlt 2026-07-29 12:35:59 +00:00
ui Phase 3: Kiosk-Shell im Eigenbetrieb, wenn winkioskshell fehlt 2026-07-29 12:35:59 +00:00
vendor release: v0.0.32 — Browser-Tab-Doku, Public-Key-Log, --locked-Fix 2026-07-16 11:41:10 +00:00
.gitignore Refactor code structure for improved readability and maintainability 2026-07-21 12:07:23 +00:00
build.rs v0.0.45: Updates nur noch über die Paketverwaltung + Paket-Korrekturen 2026-07-27 10:52:06 +00:00
Cargo.lock v0.0.52: Kiosk-Shell im Eigenbetrieb, wenn winkioskshell fehlt 2026-07-29 12:49:10 +00:00
Cargo.toml v0.0.52: Kiosk-Shell im Eigenbetrieb, wenn winkioskshell fehlt 2026-07-29 12:49:10 +00:00
LICENSE Initial commit 2026-06-22 13:33:02 +00:00
Plan.md GPG-Paketsignatur-Scaffold (verifiziert) + CI-/Kiosk-Verdrahtung 2026-06-27 08:05:17 +02:00
PLAN_WINDOWS.md Phase 3: Kiosk-Shell im Eigenbetrieb, wenn winkioskshell fehlt 2026-07-29 12:35:59 +00:00
README.md v0.0.46: CI-Test-Stufe + Zugriffskontrolle auf den IPC-Socket 2026-07-27 11:41:29 +00:00
VERBESSERUNGEN.md Update .gitignore to include deployment keys and add Verbesserungsideen document for improvement suggestions 2026-06-30 14:10:48 +02:00

ngenSermon2Usb

Angelehnt an die bestehende USB Tankstelle, um Gottesdienste und andere Veranstaltungen per Auswahl auf einen USB Speicher zu kopieren.

Konzept & Umsetzungsstand

Hinweis: Dieses Dokument beschreibt Konzept und Zielbild. Große Teile sind inzwischen umgesetzt (Stand v0.0.38 — u. a. Slint-GUI, Kopier-/Cache-/Nextcloud-Logik, Hintergrunddienst, HTTPS-Server, Update-Mechanismus, Browser-Tab). Einzelne Punkte beschreiben noch Geplantes und sind im Text als solche markiert („eingeplant", „vorerst", „zu prüfen").

  • Umgesetzt in Rust mit Slint als GUI-Toolkit.
  • Quellen für die Gottesdienste und Veranstaltungen: ein Nextcloud-Ordner.
  • Pro Gottesdienst bzw. Veranstaltung gibt es einen Ordner mit MP3-Dateien und einer JSON- oder TXT-Datei.
  • Ein Ordner wird erst dann in die dem Benutzer angezeigte Liste aufgenommen, wenn die zugehörige JSON- oder TXT-Datei vorhanden ist.
  • Die GUI zeigt auf der linken Seite eine Übersicht über den USB-Speicher an und reagiert dabei auch auf das An- und Abstöpseln des Speichers.
  • Im Hauptbereich gibt es eine Auswahl, welchen Veranstaltungstyp man möchte. Diese Liste lässt sich in einem passwortgeschützten Settings-Bereich verwalten.
  • Zusätzlich soll es eine HTTPS-Einstellungsseite und eine HTTPS-Statusseite geben, die ebenfalls passwortgeschützt sind.

Technische Entscheidungen

Nextcloud / Datenquelle

  • Zugriff ohne lokalen Nextcloud-Client, direkt per API/WebDAV.
  • App-Passwort wird automatisch über den Login Flow v2 angelegt (einmalige Browser-Bestätigung); das echte Nutzerpasswort wird nicht gespeichert.
  • Lokaler Cache für Offline-Betrieb und schnellere Anzeige (s. Abschnitt Lokaler Cache).
  • Aktualisierung primär per Push (kein dauerndes Pollen):
    • notify_push (App „Client Push") — der Client öffnet ausgehend eine WebSocket-Verbindung (wss://<server>/push/ws, Endpunkt aus der Capabilities-API notify_push.endpoints.websocket), authentifiziert mit Benutzername + App-Passwort und empfängt Ereignisse (notify_file, notify_activity, …).
    • Das Ereignis ist nur ein Signal „etwas hat sich geändert" (in neueren Versionen mit Pfad-Präfix), nicht der Diff → daraufhin ein gezielter, kurzer Scan des betroffenen Ordners statt Vollabgleich.
    • Vorteil: Client verbindet sich nach außen → funktioniert hinter NAT/Firewall (LAN-Kiosk + externe Nextcloud). Voraussetzung serverseitig: App notify_push aktiv (seit ~NC 21 gebündelt) + Redis.
  • Polling alle 3 Minuten nur als Fallback, falls notify_push nicht verfügbar ist oder der Socket abreißt (Reconnect mit Backoff).
  • (Webhooks-App webhook_listeners, NC 30+, scheidet meist aus: Nextcloud müsste den Kiosk erreichen können — bei LAN-Kiosk + externer Cloud nicht gegeben.)

Gleichzeitige Anfragen begrenzen

Mehrere gleichzeitige Aufrufe an Nextcloud werden bewusst eingeschränkt (verhindert Server-Überlast/Drosselung und schont die Kiosk-Ressourcen; glättet auch die notify_push-getriggerten Nachladungen):

  • Eine gemeinsame HTTP-Client-Instanz mit Connection-Pool/Keep-Alive (z. B. reqwest) — kein neuer Connect pro Datei.
  • Begrenzte Parallelität per Semaphore, kleines konfigurierbares Limit (Default 24 gleichzeitige Requests); überschüssige Arbeit in eine Queue (FIFO).
  • Backoff/Retry bei 429/503, dabei den Retry-After-Header respektieren.
  • Ggf. getrennte Limits für leichte Metadaten-/List-Requests vs. große Datei-Downloads.
  • Beim Kopieren seriell oder max. 2 parallel — der USB-Schreibvorgang ist meist ohnehin der Flaschenhals.

Lokaler Cache

  • Einstellbare Schwelle, ab der der Cache reduziert wird — z. B. als maximale Cache-Größe oder als minimal freizuhaltender Speicher. Mit Hysterese: beim Überschreiten der oberen Schwelle wird bis zu einer unteren Schwelle aufgeräumt, damit nicht ständig nachgeräumt wird.
  • Verdrängungsstrategie als Kompromiss zwischen „ältestes zuerst löschen" und „am wenigsten gebraucht löschen": pro Eintrag ein Score aus Alter (letzter Zugriff/Download) und Nutzungshäufigkeit; Einträge mit dem niedrigsten Score zuerst entfernen. Gewichtung der beiden Faktoren konfigurierbar.
  • Geschützt vor Löschung: gerade angezeigte/markierte oder aktuell kopierte Veranstaltungen.
  • Cache-Pfad in den Settings einstellbar: Der Speicherort des lokalen Caches lässt sich konfigurieren (z. B. auf eine größere/andere Platte legen).
  • Unkritisch, da die Quelle Nextcloud bleibt — verworfene Cache-Daten lassen sich bei Bedarf jederzeit neu laden.
  • Cache-Pfad, Schwelle und Gewichtung werden im passwortgeschützten Settings-Bereich eingestellt.

Gottesdienst-Struktur (Quelle: ngenAudioRecord)

  • Die Gottesdienste werden vom Schwesterprojekt ngenAudioRecord erzeugt. Nur der MP3-Export wird nach Nextcloud gespeichert — der Aufnahme-Ordner mit Rohdaten (session.raw, WAV, Peaks) nicht.
  • Pro Gottesdienst ein Unterordner mit den MP3-Dateien.
  • ngenAudioRecord legt beim Fertigstellen die zugehörige JSON- oder TXT-Datei im Export-Ordner an. Dieses Vorhandensein ist genau der Trigger, dass der Ordner „fertig" ist und in der Liste erscheint.
  • Ordnername trägt das Datum am Anfang im Schema YYYY-MM-DD … (aus ngenAudioRecords path_template "{date} {sorthelper} {daytime}", z. B. 2026-06-18 c Mittag). Daraus lassen sich Jahr und Monat zuverlässig ableiten.
    • Im Produktivsystem erzeugt die Export-Pfad-Vorlage in ngenAudioRecord diesen datierten Pro-Event-Unterordner bereits. (Nur im Testsystem ist export_path_template leer/flach.)

Zusatzdatei (JSON/TXT)

  • Für die Anzeige ist zunächst nur wichtig, dass eine Datei existiert (s. o. Trigger).
  • Anzeige-Text des Albums = Dateiname der JSON/TXT ohne Endung (kein Parsen des Inhalts nötig). Beitragstitel = MP3-Dateiname ohne Endung.
  • Datum und Tageszeit kommen weiterhin aus dem Ordnernamen (YYYY-MM-DD …).
  • Parsen der Inhalte ggf. später (für reichere Titel/Metadaten).

Veranstaltungstypen & Ordner-Zuordnung

  • Je Veranstaltungstyp ein eigener Nextcloud-Oberordner (z. B. Gottesdienste/, Konferenzen/, Seminare/).
  • Die Zuordnung Typ → Oberordner erfolgt manuell in den Settings (Anzeigename ⇄ Nextcloud-Pfad). Die Veranstaltungstyp-Liste im Hauptbereich speist sich daraus.

USB-Speicher

  • Plug/Unplug-Erkennung: unter Linux via udev, unter Windows selbst ermittelt.
  • Mehrere Sticks werden vorerst physikalisch verhindert (Mehrfach-Unterstützung evtl. später).
  • Ziel-Layout: Album-Ordner direkt in den Wurzelordner des Sticks.

Kopier-Modi

  • Standard (ohne Autoradio): kompletter Veranstaltungsordner mit Struktur, ohne Umbenennung.
  • Autoradio-Optimierung (per GUI-Option) — wirkt nur auf die Dateinamen:
    • Umbenennung der MP3s, Albumname vorangestellt.
    • FAT32-Sanitizing (Umlaute/Sonderzeichen ersetzen).
    • Flache Struktur (keine Unterordner).
    • Track-Nummern-Präfix für die richtige Reihenfolge.

Verhalten bei vorhandenem Album

  • Bereits vorhandenes Album wird einfach überschrieben, mit Meldung „schon vorhanden".

Kopiert-Status

  • Erkennung zuerst nur nach Ordnername (Album liegt auf dem Stick → „✓ kopiert").
  • Ein Button „Vollständigkeit" ist eingeplant (späterer Abgleich von Dateianzahl/-größe, um unvollständige Kopien zu erkennen).

Sortierung

  • Liste nach Datum absteigend (neueste oben), innerhalb eines Tages nach Tageszeit (sorthelper af).
  • Tracks im Album nach Dateiname / Track-Nummer.

Abbrechen

  • Während des Kopierens gibt es einen Abbrechen-Button. Beim Abbrechen wird gefragt:
    • aktuellen Beitrag noch fertig kopieren und dann stoppen, oder
    • alles bereits Kopierte der Auswahl wieder löschen (Rollback).

Speicher freigeben (Aufräum-Optionen)

Wählbare Möglichkeiten, um Platz auf dem USB-Speicher zu schaffen (z. B. wenn der Platz nicht reicht). Jeweils mit Bestätigung, da löschend:

  • Gezielt löschen (Ordner/Album auswählen): einen oder mehrere bestimmte Ordner bzw. Alben auf dem Stick auswählen (Mehrfachauswahl) und gezielt entfernen.
  • Alles löschen (Formatieren): kompletten Stick als FAT32 formatieren — entfernt alle Daten.
  • Älteste löschen (nach Album/Ordnername): Veranstaltungsordner mit dem ältesten Datum im Ordnernamen (YYYY-MM-DD-Präfix, = MP3-Album) zuerst entfernen.
  • Zuerst gespeicherte löschen (nach Dateizugriffsdatum): nach dem Zeitpunkt der Speicherung auf dem Stick (Datei-Zeitstempel) — die zuerst kopierten Dateien zuerst entfernen.

Bei den automatischen „Löschen"-Varianten (Älteste / Zuerst gespeicherte) wird so lange entfernt, bis genug Platz frei ist.

Verfügbarkeit: Das gezielte Löschen wird jederzeit als eigene Aktion angeboten und zusätzlich, wenn der Platz auf dem USB-Speicher knapp wird (zusammen mit den übrigen Aufräum-Optionen).

  • Vorschau vor dem Löschen: Bevor tatsächlich gelöscht wird, zeigt eine Vorschau genau, welche Ordner (Alben) entfernt würden — bzw. einzelne Dateien, falls lose Dateien direkt im Wurzelverzeichnis des Sticks liegen. Erst nach Bestätigung wird gelöscht.

GUI: Der Unterschied zwischen Variante 2 und 3 wird dem Nutzer angezeigt — Variante 2 richtet sich nach dem Veranstaltungsdatum (Ordnername), Variante 3 danach, wann auf den Stick kopiert wurde (Dateizeitstempel). Diese kurze Erklärung steht direkt bei den beiden Optionen (s. GUI-Skizze: „Speicher freigeben").

Settings & Passwörter

  • Passwörter möglichst sicher, als Hash gespeichert.
  • Getrennte Passwörter für GUI-Settings und HTTPS-Seiten; jeweils vorerst nur ein Passwort.
  • GUI-Settings und HTTPS-Settings sind inhaltlich identisch (gleicher Funktions-/Einstellungsumfang, dieselbe darunterliegende Konfiguration) — nur die Oberfläche unterscheidet sich (lokale Touch-GUI vs. Browser im LAN).

    Anweisung für AI/Implementierung: GUI-Settings und HTTPS-Settings immer gleich/synchron halten. Wird eine Einstellung an einer Stelle hinzugefügt oder geändert, ist sie an der anderen mitzuführen; beide greifen auf dieselbe Konfigurationsquelle zu.

  • Der Nextcloud-Login (Login Flow v2) lässt sich daher von beiden Oberflächen anstoßen (lokal mit Touch-Tastatur oder über die HTTPS-Seite von einem anderen Gerät).
  • Die Veranstaltungstyp-Liste wird lokal geführt, mit Backup in Nextcloud.
  • Auswahl bei Gottesdiensten nach Jahr und Monat (abgeleitet aus dem YYYY-MM-DD-Präfix des Ordnernamens, s. Gottesdienst-Struktur).

Konfigurations-Ebenen — wo welche Einstellung

Der Kiosk hat mehrere Konfigurations-Ebenen; nicht jede Einstellung gehört in dieselbe.

Die Ebenen:

  1. Runtime-Settings — GUI = HTTPS, passwortgeschützt, dieselbe Konfigquelle (s. Settings & Passwörter). Was ein Betreuer im Betrieb ohne Root/Neuinstallation ändern soll.
  2. Lokale Konfig/Zustand — von der App selbst geschrieben, nicht von Hand gepflegt (Hashes, Token, Cache-Index, aktuelle Version).
  3. Nextcloud-Backup — Teilmenge der Runtime-Settings, für Wiederherstellung.
  4. Install-Zeit — Agama-Profil + First-Boot-Script, einmalig beim Aufsetzen (OS-/Root-Ebene; s. Kiosk-Installation (openSUSE Leap 16)).
  5. Paket/RPM — technische Systemintegration, kommt mit dem Update mit (via .spec), nicht von Hand (s. Build-Script & Paketierung).
  6. Build/CI-Zeit — Geheimnisse + Versionierung in Forgejo Actions.

Zuordnung:

Einstellung Ebene Begründung
Veranstaltungstyp ↔ Nextcloud-Oberordner Runtime + NC-Backup fachlich, ändert sich oft; Liste lokal geführt, Backup in NC
Nextcloud-Login (Login Flow v2), App-Passwort Runtime (anstoßen) + Lokal (Token sicher/Hash) von beiden Oberflächen auslösbar; Geheimnis nie im Klartext
Cache: Pfad, Schwellen, Score-Gewichtung Runtime fachliche Tuning-Größen (s. Lokaler Cache)
Autoradio-Standard, USB-Verhalten Runtime Bedien-Default
Feiertage (Liste/Auswahl/Import) Runtime + NC-Backup inhaltlich, pflegbar (s. Feiertage)
Passwörter (GUI/HTTPS) Runtime (setzen) → Lokal als Hash s. Settings & Passwörter
Concurrency-Limit (24) Runtime bewusst konfigurierbar (s. Gleichzeitige Anfragen begrenzen)
Update-Kanal, Prüfintervall/Zeitfenster, Auto-Install Runtime s. Automatischer Update-Mechanismus
HTTPS-Port Runtime (mit OS-Kopplung, s. u.) Port wählbar
Home-Assistant-Browser: Start-URL, Dashboard, Benutzer, Passwort, Kiosk, Button-Beschriftung (homeassistant-Sektion) Runtime (GUI = HTTPS) s. Browser-Tab (Home-Assistant-Seite, Servo); Passwort vom NC-Backup ausgenommen
Sprache/Tastatur/Zeitzone (de_DE) Install-Zeit „vorerst nur Deutsch" → fix, kein Runtime-Umschalter
Hostname, Netzwerk (DHCP/statisch/WLAN) Install-Zeit OS-Grundlage, selten geändert
Kiosk-User + Autologin, graphical.target Install-Zeit einmalige Provisionierung
Pakete (cage, App), Forgejo-.repo + Token Install-Zeit Bootstrapping der Update-Quelle
transactional-update/btrfs-Snapshots Install-Zeit Rollback-Fundament
cage@.service, Daemon-Unit, udev-/polkit-Regel, Update-Timer Paket/RPM gehört zur App-Version, kommt mit Updates mit
GPG-Signaturschlüssel, Registry-Upload-Token Build/CI Secrets in Forgejo Actions, nie ins Image
Version/Release Build/CI aus Git-Tag

Faustregel:

  • Fachlich / ändert sich im Betrieb → Runtime (GUI=HTTPS).
  • Braucht Root, einmalig beim Aufsetzen → Install-Zeit (Agama).
  • Technisch + versionsgebunden → ins RPM (kommt mit Updates).
  • Geheim → CI-Secret bzw. lokaler Schlüsselspeicher, nie in Settings/Image.

Kopplungen (Runtime-Setting mit OS-Wirkung): Diese sind Runtime-Settings, wirken aber auf OS-Ebene — deshalb setzt sie der privilegierte Hintergrunddienst um (s. Hintergrunddienst):

  • HTTPS-Port ändern → Daemon passt die firewalld-Regel an.
  • Cache-Pfad auf andere Platte → Ziel muss gemountet sein (fstab = Install-Zeit) → Daemon prüft/meldet, wenn es fehlt.
  • Update-Zeitfenster → der systemd-Timer feuert regelmäßig, aber der Daemon entscheidet anhand von Zeitfenster und „kein Kopiervorgang aktiv" → OS-Ebene bleibt statisch, nur die App-Logik kennt den Zustand.

Feiertage

  • In den Settings wird eine Namensliste der Feiertage angezeigt; einzelne Feiertage sind auswählbar (welche markiert/berücksichtigt werden).
  • Eine deutschlandweite Feiertagsliste kann aus dem Internet geholt werden.
  • Von Hand lassen sich weitere Feiertage hinzufügen.
  • Feiertage steuern die abweichende Zeilen-Markierung in der Gottesdienst-Liste (s. Listendarstellung).

HTTPS-Server (Einstellungs-/Statusseite)

  • Erreichbar nur im LAN / lokal.
  • Nicht öffentliches Zertifikat (selbstsigniert / lokal).
  • Statusseite zeigt Kopiervorgänge und Logs.
  • Integrierter Dateibrowser (Cache + USB): zeigt den lokalen Cache und den USB-Inhalt zur Diagnose. In den eigenen HTTPS-Server integriert (z. B. axum mit tower-http ServeDir bzw. actix-files), hinter dem bestehenden HTTPS-Passwort — also kein zweites Login. (Nextcloud-Inhalt bleibt über die Nextcloud-Web-UI browsbar.)
  • HTTPS-Passwort beim ersten Aufruf anlegen: Beim ersten Aufruf der HTTPS-Seite muss zuerst ein Passwort festgelegt werden, bevor Zugriff gewährt wird.
  • Nextcloud-Verbindung über die HTTPS-Einstellungsseite einrichtbar: Die Verbindung zu Nextcloud (Login Flow v2) lässt sich auch über die HTTPS-Einstellungsseite einrichten (GUI- und HTTPS-Settings sind identisch).
  • Der Server soll die GUI möglichst gar nicht beeinträchtigen: läuft in einem eigenen Thread / eigener Runtime (z. B. tokio auf einem Hintergrund-Thread), getrennt vom Slint-UI-Thread. Kein Blockieren des UI-Threads, niedrigere Priorität; Datenaustausch nur über entkoppelte, nicht-blockierende Kanäle (z. B. geteilter Zustand/Channel statt direkter UI-Aufrufe).

Browser-Tab (Home-Assistant-Seite, Servo)

Über den Button 🌐 Browser öffnet sich ein Tab mit Adressleiste („‹ Zurück", URL-Zeile, „Öffnen") und Seiten-Ansicht; die Start-URL wird beim Öffnen automatisch geladen. Gedacht für das Home-Assistant-Dashboard (passwortgeschützt, s. u.); Hintergrund und Windows-Plan: PLAN_WINDOWS.md, Phase 4.

Warum Servo: Die Browser-Engine (Servo, Crate servo 0.4 von crates.io) läuft im Prozess und rendert in einen von uns kontrollierten Kontext. Unter cage/Wayland ist das der einzige Weg zu einer Browser-Ansicht im Slint-Fenster (eingebettete Webviews à la wry brauchen Child-Windows, die es unter Wayland nicht gibt) — und derselbe Code trägt später die Windows-Variante.

Bauen & Entwickeln:

  • Nur mit cargo build --features browser-servo einkompiliert; ohne das Feature zeigt der Tab einen Status-Platzhalter. Erstbau zieht ~1100 zusätzliche Crates.
  • SpiderMonkey kommt automatisch als Prebuilt von den servo/mozjs-GitHub-Releases (Build-Script lädt per curl) — kein clang/llvm nötig; nur bei Download-Fehlschlag wird aus Quellen gebaut (MOZJS_ARCHIVE kann eine lokale Kopie vorgeben).
  • primeorder ist in Cargo.lock auf 0.14.0-rc.14 festgehalten. servo-script pinnt die Kurven-Crates hart auf p256/p521 =0.14.0-rc.14; das stabile primeorder 0.14.0 verlangt neu Scalar: WnafSize und bricht die RC-Crates (error[E0277]). Wird der Lock neu erzeugt, den Pin wieder setzen: cargo update -p primeorder --precise 0.14.0-rc.14. Deshalb wird Cargo.lock eingecheckt (Release-Builds laufen mit --locked).
  • Debug-Profil ist spürbar langsam (Servo/Layout unoptimiert) — für echte Seiten-Tests --release bzw. die VS-Code-Task „cargo build --release (browser-servo)". VS Code: Launch-Konfiguration „GUI + Daemon (Servo-Browser)"; .vscode/settings.json aktiviert das Feature für rust-analyzer (sonst gilt src/browser.rs als inaktiver Code).
  • Die LLDB-Meldung „no plugin for the language rust" beim Debuggen ist harmlos (nur eingeschränkte Variablen-Anzeige mancher Typen).

Architektur (src/browser.rs, Feature browser-servo):

  • Rendern: Servo zeichnet per WebRender in einen SoftwareRenderingContext (CPU/llvmpipe); bei notify_new_frame_ready wird der Puffer mit read_to_image ausgelesen und als slint::Image in die browser-frame-Property gelegt. Renderer-unabhängig (femtovg heute, Skia für Windows) — bewusst kein GL-Textur-Sharing. Wichtig: kein present() (der Software-SwapChain verwirft dabei den Puffer).
  • Ereignisschleife: Servos EventLoopWaker stößt über slint::invoke_from_event_loop spin_event_loop an; ein 33-ms-Pump-Timer treibt Animationen und gleicht die Viewport-Größe mit der Sichtfläche ab.
  • Eingaben: Maus/Scrollrad/Tastatur laufen aus ui/app-window.slint (TouchArea/FocusScope) über die Callbacks browser-pointer/browser-scroll/browser-key nach Servo (Koordinaten: logische Pixel × Skalierungsfaktor).
  • Schließen verwirft die WebView (Sitzung/Speicher frei, kein persistentes Profil); die Engine bleibt für erneutes Öffnen bestehen.

Automatische HA-Anmeldung: Konfig-Sektion homeassistant (JSON-Konfigdatei):

"homeassistant": {
  "url": "http://homeassistant.fritz.box",
  "dashboard": "dashboard-bg",
  "username": "kiosk",
  "password": "…",
  "kiosk": true,
  "button_label": "🌐 Browser"
}

Alle Felder sind in beiden Settings-Oberflächen editierbar (lokale Touch-GUI wie HTTPS-Seite, gemäß der GUI=HTTPS-Synchronität):

  • url — Basis-URL der HA-Instanz (Host der Anmeldung).
  • dashboardurl_path des Ziel-Dashboards (z. B. dashboard-bg, lovelace); leer = HA-Standard-Dashboard. Die Start-URL des Tabs ist url + / + dashboard. Ein Button „Dashboards von HA laden" listet die vorhandenen Dashboards (per HA-Login-Flow + WebSocket lovelace/dashboards/list) zum direkten Auswählen auf (s. src/homeassistant.rs).
  • username/password — für den Auto-Login; leerer Benutzer/Passwort = kein Auto-Login. Sicherheitsunterschied: Die lokale GUI zeigt das gespeicherte Passwort (maskiert) im Feld; die HTTPS-Seite rendert das Passwortfeld leer (das Geheimnis geht nicht über die Leitung in den Seitenquelltext) — dort bedeutet ein leeres Feld beim Speichern „unverändert lassen".
  • kiosk (Standard true) — app-seitiger Kiosk-Modus: Im Tab wird die Adressleiste ausgeblendet (nur „‹ Zurück" bleibt), nach dem Laden blendet ein injiziertes JavaScript HA-Kopfzeile und Seitenleiste aus (über die Shadow-DOM-Bäume, analog zum Community-Plugin kiosk-mode), und ein Navigations-Guard leitet auf die Dashboard-URL zurück, sobald eine Seite den HA-Host verlassen würde. So zeigt der Kiosk nur das eine Dashboard, ohne serverseitige HA-Änderung. (Best effort; die Chrome-Ausblendung hängt an HA-internen Selektoren — Gerätetest s. „Offene Punkte".)
  • button_label (Standard 🌐 Browser) — Beschriftung des Buttons, der den Browser-Tab öffnet.

Passwort und Dashboard/Kiosk gelten für den HA-Tab; das Passwort ist vom Nextcloud-Backup ausgenommen.

Nach LoadStatus::Complete wird — nur wenn der Host der geladenen Seite dem Host der Config-URL entspricht — ein JavaScript injiziert, das das Login-Formular sucht (rekursiv durch alle Shadow-DOM-Bäume; die HA-Oberfläche ist eine Lit-App, normales querySelector findet die Felder nicht), Benutzer/Passwort mit input-Events einträgt und abschickt; Wiederholung alle 500 ms bis max. 20 s (SPA rendert das Formular erst nach load). Empfohlen: eigener nicht-administrativer HA-Benutzer mit nur dem einen Dashboard. Passwort wird nur ins Formular injiziert, nie angezeigt, und ist vom Nextcloud-Backup ausgenommen. Leerer Benutzer/Passwort = keine automatische Anmeldung.

vendor/surfman (gepatchtes surfman 0.12.3, derzeit deaktiviert): Seit servo 0.4 löst Cargo ohne den vendored surfman auf — der [patch.crates-io]-Eintrag in Cargo.toml ist daher auskommentiert und der Ordner wird für den Build nicht mehr benötigt. Er bleibt als Referenz erhalten, falls das GL-Problem wieder auftritt. Inhalt des Patches: ein kleiner Fix in create_context (x11/wayland/generic) — schlägt make_current direkt nach der Kontext-Erzeugung fehl, wird der halbfertige EGL-Kontext explizit zerstört und der echte Fehler zurückgegeben, statt dass surfmans Drop-Guard panict und ihn verschluckt. So landet ein GL-Problem als Statusmeldung im Tab statt als App-Absturz. Kandidat für einen Upstream-PR. Vollständige Erklärung des Patches (Diff, Begründung, Rekonstruktion): vendor/surfman-patch.md.

GL-Zusammenspiel Slint ↔ Servo (X11-Entwicklung vs. Wayland-Kiosk): Unter X11 (z. B. SSH-Forwarding) bindet Slint/glutin einen GLX-Kontext, Servo/surfman arbeitet mit EGL — Mesa sperrt beide Richtungen gegeneinander: eglMakeCurrent liefert BadAccess, solange GLX current ist (Symptom: Servo-Banner „Could not create any GL context"), und umgekehrt panict Slint beim Freigeben von GPU-Ressourcen zerstörter Teilbäume, wenn Servos EGL-Kontext gebunden bleibt („Fatal error … freeing graphics resources"). Lösung: gl_interop::ForeignGlGuard (RAII) um jeden Servo-GL-Zugriff — GLX-Zustand sichern und lösen, Servo arbeiten lassen, EGL lösen, GLX wiederherstellen. Unter Wayland/cage (Kiosk) existiert kein GLX → No-Op; andere Plattformen kompilieren leer.

Offene Punkte:

  • Test auf dem Kiosk-Gerät: Scroll-Richtung (Vorzeichen-Konvention von notify_scroll_event), llvmpipe-Performance.
  • Auto-Login, Kiosk-Chrome-Ausblendung, Dashboard-Auflistung sind gegen HA 2026.7.2 verifiziert (Anmeldung läuft, linke Spalte/Kopfzeile weg, Fensterschließen ohne Absturz). Sie hängen aber an HA-internen Shadow-DOM-Strukturen: Login klickt den ha-button „Anmelden" und füllt die Felder per nativem Value-Setter; die Chrome-Ausblendung blendet ha-drawer.sidebar-shell aus und setzt --ha-sidebar-width: 0 sowie hui-root .header. Bei größeren HA-Updates können die Selektoren nachzuziehen sein (s. LOGIN_JS/KIOSK_JS in src/browser.rs).
  • PIN-Schutz (pin_hash) und Zeitlimit (max_minutes) aus PLAN_WINDOWS.md Phase 4 sind noch nicht umgesetzt. Laut „Offene Entscheidungen" dort ist das eine bewusste Wahl: Empfehlung ist der HA-Login als einzige Passwort-Hürde, PIN optional — deshalb vorerst nicht eingebaut.

Betrieb, Kiosk & Deployment

Zielplattform

  • Kiosk-Modus unter Linux oder Windows.
  • Bedienung per Touch: allgemein eine Touch-Tastatur anbieten (für Passwörter, Eingaben).
  • Sprache: vorerst nur Deutsch.

Kiosk-Installation (openSUSE Leap 16)

  • Zielbild: Das Gerät bootet direkt in die Vollbild-App — ohne Desktop/Login, mit Touch-Bedienung (s. Zielplattform). Wiederanlauf nach Absturz/Update automatisch.
  • Unbeaufsichtigte OS-Installation per Agama (Leap 16 hat den YaST-Installer durch Agama ersetzt; klassisches AutoYaST entfällt): Start mit Kernel-Option inst.auto=<URL-zum-Profil>. Das JSON-Profil beschreibt Partitionierung, Netzwerk, Lokalisierung (de_DE.UTF-8, Tastatur de, Zeitzone Europe/Berlin), genau einen Benutzer mit Autologin, die Software-Auswahl (Minimal-Basis + cage + das App-RPM) sowie init/post-Skripte für die restliche Einrichtung.
  • Update-Quelle gleich mit einrichten: Das Install-Skript trägt die Forgejo-RPM-Registry als .repo ein (s. Automatischer Update-Mechanismus), sodass die App von dort installiert und später aktualisiert wird.
  • Kiosk-Laufzeit mit cage (Wayland-Kiosk-Compositor: zeigt genau eine maximierte App und blockiert alles andere) — passt zu Slint (läuft nativ auf Wayland). Betrieb als systemd-Service (cage@tty1.service) mit dem Autologin-User und Restart=always → deckt zugleich den Watchdog/Autostart ab (vgl. Fehlerfall „Absturz der Anwendung"). Default-Target graphical.target. (Alternativen: weston mit kiosk-shell oder labwc.)
  • Touch-Tastatur: bringt die App selbst mit (s. GUI-Skizze, Einstellungen) → keine separate Wayland-Bildschirmtastatur nötig.
  • Systemintegration kommt aus dem RPM (via .spec/%post, s. Build-Script & Paketierung) und wird beim Install/Update automatisch gesetzt:
    • systemd-Units: cage@.service (App) und der privilegierte Hintergrunddienst (s. Hintergrunddienst).
    • udev-Regel für die USB-Plug/Unplug-Erkennung (s. USB-Speicher).
    • polkit-/sudo-Regel für Formatieren und Mount/Unmount/Eject (gebündelt im Dienst, nicht in der GUI).
    • firewalld: HTTPS-Port nur im LAN freigeben (s. HTTPS-Server; eine Port-Änderung zur Laufzeit setzt der Dienst um, s. Konfigurations-Ebenen).
  • Rollback-Fundament: Dateisystem auf btrfs mit transactional-update/Snapshots, damit fehlerhafte Updates automatisch zurückrollen (s. Automatischer Update-Mechanismus).
  • Alternative — fertiges Image mit KIWI: Statt „installieren + nachkonfigurieren" lässt sich ein vorkonfiguriertes Kiosk-Image bauen (sinnvoll bei vielen identischen Geräten; höherer Build-Aufwand). Für ein bis wenige Geräte ist der Agama-Weg einfacher.

Anleitung: Minimal-Kiosk von der ISO an (Leap 16)

Konkrete Schritte von der ISO bis zum laufenden Kiosk nur das Nötigste. Für viele Geräte gibt es die unbeaufsichtigte Agama-Variante (s. Kasten am Ende).

1 · ISO laden. Von https://get.opensuse.org/leap/16.0/ das Network- oder Offline-Installer-Image (x86_64). Prüfsumme verifizieren (die Dateien *.iso.sha256 / .asc liegen daneben):

sha256sum -c openSUSE-Leap-16.0-*.iso.sha256

2 · Boot-Stick schreiben (⚠ /dev/sdX wird überschrieben Zielgerät vorher mit lsblk prüfen):

sudo dd if=openSUSE-Leap-16.0-*.iso of=/dev/sdX bs=4M status=progress oflag=sync

3 · Minimal installieren (ohne Desktop). Vom Stick booten; der Leap-16- Installer (Agama) fragt:

  • Region: Sprache Deutsch, Tastatur de, Zeitzone Europe/Berlin.
  • Festplatte: ganze Platte, Vorgabe-Partitionierung btrfs (Basis für Rollback / transactional-update).
  • Software: nur das Muster „Minimal Base"/Server, keinen Desktop.
  • Benutzer: root-Passwort setzen.

Installieren, neu starten, an der Textkonsole als root anmelden.

4 · System aktualisieren und Kiosk-Benutzer anlegen (unter ihm läuft die Kiosk-Sitzung; kein Passwort nötig, die Unit meldet ihn selbst an):

zypper --non-interactive refresh
zypper --non-interactive update
useradd -m kiosk

5 · Registry einbinden, Signaturschlüssel importieren, App installieren:

# Forgejo-RPM-Registry (öffentlich). --gpg-auto-import-keys holt den
# Metadaten-Schlüssel; die .repo aktiviert gpgcheck bereits selbst.
zypper --non-interactive --gpg-auto-import-keys addrepo --refresh \
  https://git.bg-ak.de/api/packages/leonhard.suckau/rpm.repo

# Maintainer-Schlüssel für die Paket-Signatur (rpmsign) importieren.
rpm --import https://git.bg-ak.de/leonhard.suckau/ngenSermon2Usb/raw/branch/main/deploy/RPM-GPG-KEY-ngensermon2usb

# Anwendung + Wayland-Kiosk-Compositor + Firewall.
zypper --non-interactive install --no-recommends ngensermon2usb cage firewalld

6 · In den Kiosk booten lassen (die systemd-Units kommen aus dem RPM):

systemctl set-default graphical.target
systemctl enable ngensermon2usb-kiosk@tty1.service    # cage + App auf tty1
systemctl enable --now ngensermon2usb-daemon.service  # privilegierter Dienst
systemctl enable --now firewalld
reboot

Nach dem Neustart bootet das Gerät vollflächig in die App: cage zeigt genau die eine maximierte Anwendung, Restart=always fängt Abstürze ab, und die Kiosk-Unit übernimmt tty1 (Conflicts=getty@tty1) es erscheint kein Login-Prompt.

7 · Erststart. Beim ersten Aufruf der HTTPS-Einstellungsseite ein Passwort setzen und die Nextcloud-Verbindung einrichten (s. HTTPS-Server, Settings & Passwörter). Updates kommen danach automatisch als RPM aus der Registry (zypper); der App-eigene Mechanismus ist in Paket-Builds abgeschaltet (s. Automatischer Update-Mechanismus).

Unbeaufsichtigt (viele Geräte): Statt Schritt 36 die Installation mit inst.auto=<URL zu deploy/agama/ngensermon2usb-kiosk.json> starten. Das Profil setzt Lokalisierung + kiosk-Benutzer (Autologin) + Software-Auswahl und ruft beim ersten Boot deploy/scripts/kiosk-setup.sh auf (Registry, Schlüssel, Units aktivieren). Vorher die CHANGEME-Passwörter im Profil ersetzen. Die Schritte 46 oben entsprechen genau dem, was dieses Skript automatisch tut.

Hintergrunddienst (zu prüfen)

  • Vermutlich sinnvoll: ein eigener Hintergrunddienst (Daemon/Service), getrennt von der GUI. Begründung:
    • Privilegierte Operationen wie Formatieren und Mount/Unmount/Eject brauchen oft erhöhte Rechte — besser gebündelt im Dienst als in der GUI.
    • Läuft unabhängig von der GUI weiter: Nextcloud-Sync/Cache-Pflege, HTTPS-Server, Watchdog/Autostart.
    • Die (unprivilegierte) GUI delegiert privilegierte/lange Aufgaben an den Dienst über eine lokale Schnittstelle (Socket/IPC).
  • Genaue Aufteilung GUI ⇄ Dienst noch festzulegen.
  • Zugriffskontrolle auf den Socket. /run/ngensermon2usb.sock ist dateisystemseitig für alle beschreibbar, weil die unprivilegierte Kiosk-GUI ihn erreichen muss. Wer darüber wirklich Befehle geben darf, entscheidet der Dienst per SO_PEERCRED: erlaubt sind root, der Benutzer des Dienstes selbst und die in ipc_allowed_users genannten (Default: kiosk, passend zu User=kiosk in ngensermon2usb-kiosk@.service). Ohne diese Prüfung könnte jeder lokale Prozess dem root-Dienst u. a. FormatUsb, SetConfig oder SetPassword schicken — relevant, weil im GUI-Prozess der Servo-Browser entfernte Inhalte rendert. Läuft die GUI unter einem anderen Benutzer, muss dieser in ipc_allowed_users eingetragen werden; abgewiesene Zugriffe stehen mit UID im Log. Änderungen wirken nach einem Dienst-Neustart.

Automatischer Update-Mechanismus

  • Ziel: Der Kiosk läuft unbeaufsichtigt → Programm-Updates müssen automatisch, sicher und mit Rückfallebene ablaufen, ohne den laufenden Betrieb (v. a. Kopiervorgänge) zu stören.
  • Auslieferung als RPM-Paket über die eingebaute Forgejo-Paket-Registry des bestehenden Git-Hosts (git.bg-ak.de). RPMs sind GPG-signiert → Authentizität/Integrität prüft das Paketsystem; ein selbstgebauter Download-und-Austausch-Mechanismus entfällt. Kein externes openSUSE Build Service nötig. Bei privater Registry kommen die Zugangsdaten (Personal Access Token) in die .repo-Datei: https://<user>:<token>@git.bg-ak.de/api/packages/<owner>/<gruppe>.repo (passt zum Leap-16-Setup mit zypper).
  • Aus einem Paket installiert aktualisiert ausschließlich die Paketverwaltung. RPM/DEB setzen beim Bauen NGEN_PACKAGED=1; --update bricht dann mit Hinweis ab, und die Pakete liefern …-update.{service,timer} gar nicht erst mit. Grund: /usr/bin/ngensermon2usb gehört rpm/dpkg — ein Selbst-Austausch bräche deren Prüfsummen (rpm -V/dpkg -V), hinterließe die Sicherungskopie als unverwaltete Datei in /usr/bin und würde beim nächsten zypper up/apt upgrade ohnehin überschrieben. Der eingebaute Mechanismus bleibt für Installationen ohne Paketverwaltung erhalten; die Units liegen dafür weiterhin in deploy/systemd/.
  • Der Update-Ordner muss root gehören und darf für andere nicht schreibbar sein. Die sha256 aus manifest.json belegt nur Unversehrtheit, nicht Herkunft — das Manifest liegt ja im selben Ordner wie das Binary. Ein welt-schreibbarer Ordner erlaubte deshalb jedem lokalen Benutzer, dem als root laufenden Update-Dienst eigenen Code unterzuschieben (Rechteausweitung). Der Default ist daher /var/lib/ngensermon2usb/update statt eines Pfades unter /tmp. Wer local_dir selbst setzt, muss dieselbe Bedingung einhalten.
  • Build & Veröffentlichung per Forgejo Actions (CI): Bei einem Release/Tag baut die CI das RPM und lädt es in die Forgejo-Registry hoch; die Kioske ziehen es danach automatisch per zypper. Vorgeschaltet läuft eine Test-Stufe (cargo test + cargo clippy -D warnings): Sie hängt als needs: vor den Paket-Jobs, sodass aus rotem Code kein Paket entsteht. Dieselbe Stufe läuft als eigener Workflow (.forgejo/workflows/test.yml) bei jedem Push auf main und bei Pull Requests — bewusst ohne browser-servo (ein Servo-Build dauert ~15 min und trägt zu den Logik-Tests nichts bei). cargo fmt ist kein Gate, weil der Code eigener Formatierung folgt.
  • Auslösung per systemd-Timer (kein Dauer-Polling): konfigurierbares Zeitfenster (z. B. nachts), prüft gezielt auf eine neue Version dieses einen Pakets statt vollem Distributions-Upgrade.
  • Client zieht aktiv vom Repo (ausgehende Verbindung) → funktioniert hinter NAT/Firewall (gleiche Logik wie bei notify_push, s. Nextcloud / Datenquelle); keine eingehenden Verbindungen nötig.
  • Koordination mit dem Betrieb: Update nur, wenn kein Kopiervorgang läuft und der USB-Speicher nicht aktiv beschrieben wird; sonst auf das nächste Fenster verschieben. Der privilegierte Hintergrunddienst (s. Hintergrunddienst) führt das Update aus, da er Rechte und App-Zustand kennt.
  • Atomar mit Rollback: vorzugsweise transactional-update (btrfs-Snapshots) für atomare Updates mit automatischem Rollback bei fehlerhaftem Start; mindestens ein Healthcheck nach Neustart — startet die neue Version nicht sauber, automatischer Rückfall auf die vorige (Snapshot bzw. vorheriges RPM).
  • Neustart nach Update: Nach erfolgreichem Update wird der Kiosk-Service (App) im selben Wartungsfenster sauber neu gestartet (vgl. Autostart/Watchdog, Restart=always).
  • Konfig-Migration: Bei Versionssprüngen werden Einstellungen automatisch migriert (vorwärtskompatibles Lesen, Defaults für unbekannte Felder — vgl. Fehlerfall „Konfiguration beschädigt").
  • Einstellbar im passwortgeschützten Settings-Bereich (GUI und HTTPS identisch, s. Settings & Passwörter): Update-Kanal (Stabil/Test), Prüfintervall/Zeitfenster, automatisch installieren (ja/nein bzw. nur benachrichtigen).
  • Status/Logs: Die HTTPS-Statusseite zeigt aktuelle Version, letzte erfolgreiche Aktualisierung, verfügbare Version und Update-Verlauf/Fehler. Im Kiosk höchstens ein dezenter Hinweis, keine technischen Details (s. Leitlinien Fehlerfälle & Meldungen).
  • (Optional) OS-Sicherheitspatches können über denselben Timer mitlaufen (zypper patch), bleiben aber konzeptionell getrennt vom App-Update.

Build-Script & Paketierung

  • Ein einziges Build-Script (z. B. scripts/build-rpm.sh), das lokal und in der CI identisch läuft („ein Skript, zwei Aufrufer") — so weicht der CI-Build nie vom Entwickler-Build ab.
  • Schritte des Scripts:
    1. Version aus dem Git-Tag ableiten (git describe/Tag → RPM-Version/Release).
    2. Release-Binary bauen: cargo build --release (Slint-Frontend inklusive).
    3. RPM paketieren aus einer .spec-Datei (via rpmbuild): enthält das Binary plus die Kiosk-Dateien — cage@.service, Daemon-systemd-Unit, udev-Regel und polkit-Regel (s. Hintergrunddienst) — und %post-Scriptlets, die die Dienste aktivieren/neu starten (greift in den Update-Neustart, s. Automatischer Update-Mechanismus). (Alternative für einfache Fälle: cargo-generate-rpm ohne volle .spec.)
    4. RPM signieren (GPG) → wird beim Update vom Paketsystem geprüft.
    5. Upload in die Forgejo-RPM-Registry (nur in der CI; lokal optional per Flag).
  • Build in einer openSUSE-Leap-16-Umgebung (Container/Buildroot), damit das Binary zu glibc/Bibliotheksständen des Zielsystems passt. Ein Cross-Build vom Entwickler-Rechner (z. B. Fedora) direkt funktioniert nicht verlässlich: glibc ist nicht vorwärtskompatibel (ein auf neuerem System gebautes Binary läuft nicht auf älterem Leap), und systemd-RPM-Makros (%service_add_post vs. %systemd_post) sowie Paketnamen unterscheiden sich zwischen den Distributionen. Deshalb lokal im Leap-Container bauenpodman run --rm -v "$PWD:/src:Z" -w /src registry.opensuse.org/opensuse/leap:16.0 ./scripts/build-rpm.sh (Wrapper: scripts/build-in-container.sh) — derselbe Aufruf wie in Forgejo Actions, dessen Runner ebenfalls im Leap-Image läuft.
  • Abhängigkeiten (Slint-/Wayland-Laufzeit, USB-/udev-Bibliotheken) werden als RPM-Requires deklariert, damit zypper sie beim Install/Update mitzieht.
  • Aufruf aus Forgejo Actions: Der CI-Workflow ruft bei Release/Tag genau dieses Script auf (s. Build & Veröffentlichung per Forgejo Actions).

Debian Trixie (.deb)

Zusätzlich zum RPM entsteht bei jedem Tag ein Debian-Paket für Trixie — nach demselben Muster („ein Skript, zwei Aufrufer") und mit der gleichen Skript-/Verzeichnisstruktur wie bei ngenAudioRecord:

  • Build-Script: scripts/build-deb.sh (lokal im Container: scripts/build-deb-local.sh, Image debian:trixie, mit anschließendem Installations-Smoke-Test). Es leitet Version + Debian-Revision aus dem Git-Tag ab, holt per rustup eine aktuelle Toolchain (Slint 1.17 braucht Rust ≥ 1.92, neuer als Distro-cargo), baut das Release-Binary (cargo build --release --features browser-servo, per CARGO_FEATURES änderbar) und paketiert dieselben Dateien wie die .spec — Binary, systemd-Units, udev-/polkit-Regel, PAM — manuell per dpkg-deb (kein debhelper).
  • Paketdaten liegen in packaging/deb/: control.in, copyright und die Maintainer-Skripte postinst/prerm/postrm + conffiles (die PAM-Datei), die Dienst und Timer über deb-systemd-helper aktivieren/stoppen (Gegenstück zu den %service_*-Makros).
  • Abhängigkeiten: dpkg-shlibdeps ermittelt die per ELF gelinkten Bibliotheken (Wayland, fontconfig, freetype, xkbcommon …) mit den zur Build-Distro passenden Paketnamen; per dlopen geladenes GL/EGL sowie die Laufzeit-Dienste cage und polkitd werden manuell ergänzt.
  • Keine Einzelsignatur: anders als beim RPM werden .deb-Dateien nicht pro Datei signiert — die Forgejo-Debian-Registry signiert die Repo-Metadaten mit ihrem eigenen Schlüssel.
  • Upload: die CI leitet die Registry-URL aus Server + Owner ab (https://git.bg-ak.de/api/packages/<owner>/debian/pool/trixie/main/upload); nur das PACKAGES_TOKEN (Scope write:package) ist ein Secret. Auf dem Zielsystem als APT-Quelle einbinden: deb [signed-by=…] https://git.bg-ak.de/api/packages/<owner>/debian trixie main.

GUI-Skizze

Erste Skizze des Layouts (Kiosk, Touch). Dient als Diskussionsgrundlage, nicht final.

Listendarstellung (wichtig)

  • In der Liste steht eine Zeile pro Album (= ein Veranstaltungs-/Gottesdienst-Ordner) — nicht jeder Beitrag (jede MP3) einzeln.
  • Albumname = Dateiname der JSON/TXT ohne Endung (s. Zusatzdatei).
  • Bei Veranstaltungstypen außer Gottesdienst wird nur der Albumname ohne Datum angezeigt (und kein Jahr/Monat-Filter).
  • Beim Gottesdienst werden Sonntage als komplette Zeile farbig hinterlegt markiert. Feiertage werden mit einer anderen Farbe markiert. Der Wochentag wird aus dem Datum berechnet (aus dem YYYY-MM-DD-Präfix), nicht aus der Tageszeit. Die Feiertage stammen aus einer in den Settings verwalteten Liste (s. Feiertage).
  • In der Gottesdienst-Zeile wird nur der Tag (TT) angezeigt (Jahr/Monat stehen schon im Filter), dazu die Tageszeit (z. B. „Morgen"/„Abend") — denn an manchen Tagen gibt es zwei Veranstaltungen, die so unterscheidbar bleiben.
  • Nur über einen besonderen Button am Ende der Zeile ([ ☰ ]) gelangt man zu den Einzelbeiträgen des Albums. Standardweg ist das Kopieren des kompletten Albums.

Hauptbildschirm (Gottesdienst gewählt)

┌───────────────────────────────────────────────────────────────────────────────┐
│  ngenSermon2Usb                                              [ ⚙ Einstellungen ] │
├──────────────────────┬────────────────────────────────────────────────────────┤
│  USB-Speicher        │  Veranstaltungstyp                                       │
│                      │  ┌──────────┐┌──────────┐┌──────────┐┌──────────┐       │
│  ● Angesteckt        │  │Gottesdnst││ Konferenz││  Seminar ││  Sonstig │       │
│  SanDisk Ultra 32 GB │  └────▲─────┘└──────────┘└──────────┘└──────────┘       │
│                      │                                                          │
│  Belegt:             │  Jahr  [ 2026 ▾ ]      Monat  [ Juni ▾ ]                 │
│  ▓▓▓▓▓░░░░░  12/32 GB │                                                          │
│                      │  ┌──────────────────────────────────────────────────┐   │
│  Inhalt:             │  │▒▒21  Morgen  Predigtreihe Römer 8 ....... [ ☰ ]▒▒│ ← So
│   • 15.06. Predigt   │  │  18  Mittag  Bibelstunde ................ [ ☰ ]  │   │
│   • 08.06. Predigt   │  │▒▒14  Morgen  Gemeindesonntag .. ✓ kopiert [ ☰ ]▒▒│ ← So
│                      │  │▒▒14  Abend   Abendmahlsfeier ............ [ ☰ ]▒▒│ ← So (2. am Tag)
│  [ ⏏ Sicher entfernen]│  │  11  Abend   Gebetsabend ................ [ ☰ ]  │   │
│                      │  │▒▒07  Morgen  Themengottesdienst ......... [ ☰ ]▒▒│ ← So
│                      │  └──────────────────────────────────────────────────┘   │
│                      │  ☐ Für Autoradio optimieren                             │
│                      │             [      ⬇  Auf USB kopieren      ]            │
│                      │  ▓▓▓▓▓▓▓░░ Kopiere 3/8 …     [ ✖ Abbrechen ]            │
└──────────────────────┴────────────────────────────────────────────────────────┘

Legende: ▒▒…▒▒ = farbig hinterlegte Sonntagszeile · Feiertage in anderer Farbe · [ ☰ ] = Button „Einzelbeiträge öffnen" · ✓ kopiert = liegt bereits auf dem Stick.

Anderer Veranstaltungstyp (z. B. Konferenz) — nur Album, ohne Datum

│  ┌──────────────────────────────────────────────────┐
│  │  Jugendkonferenz 2026 ................... [ ☰ ]  │   (kein Datum,
│  │  Ehe-Seminar ............................ [ ☰ ]  │    keine Sonntags-
│  │  Lobpreisabend .............. ✓ kopiert   [ ☰ ]  │    Markierung,
│  └──────────────────────────────────────────────────┘    kein Jahr/Monat)

Einzelbeiträge (öffnet sich über [ ☰ ])

┌──────────────────────────────────────────────────────────┐
│   Zurück     Gemeindesonntag  14.06.2026                │
│  ┌────────────────────────────────────────────────────┐  │
│  │ ☐  01  Begrüßung & Moderation                      │  │
│  │ ☐  02  Lobpreis                                    │  │
│  │ ☑  03  Predigt  Max Müller                        │  │
│  │ ☐  04  Abschluss                                   │  │
│  └────────────────────────────────────────────────────┘  │
│         [ Alle ]      [  ⬇ Auswahl auf USB kopieren  ]    │
└──────────────────────────────────────────────────────────┘

Kein USB angesteckt

│  USB-Speicher        │             [  ⬇  Auf USB kopieren  ]   (deaktiviert)
│  ○ Kein Speicher     │
│    Bitte anstecken   │      ⚠  Bitte einen USB-Speicher anstecken.

Kopieren abbrechen (Dialog)

┌──────────────────────────────────────────────────────────┐
│  Kopieren abbrechen?                                      │
│  Gerade wird kopiert: „03  Predigt  Max Müller"        │
│                                                           │
│  ( ) Aktuellen Beitrag noch fertig kopieren, dann stoppen │
│  ( ) Alles bereits Kopierte wieder löschen (Rückgängig)  │
│                                                           │
│        [ Weiter kopieren ]        [ Abbrechen … ]         │
└──────────────────────────────────────────────────────────┘

Speicher freigeben (Dialog)

Der Unterschied zwischen den Lösch-Varianten wird direkt im Dialog erklärt:

┌──────────────────────────────────────────────────────────────┐
│  Speicher auf dem USB-Stick freigeben                         │
│  Es werden noch 250 MB benötigt.                              │
│                                                               │
│  (•) Älteste Veranstaltung zuerst                            │
│      → nach dem Veranstaltungsdatum (Ordnername, MP3-Album)   │
│                                                               │
│  ( ) Zuerst gespeicherte zuerst                              │
│      → danach, wann es auf den Stick kopiert wurde (Zeitstpl.)│
│                                                               │
│  ( ) Alles löschen (Formatieren)                             │
│      → entfernt ALLE Daten auf dem Stick                      │
│                                                               │
│  ( ) Bestimmte Ordner/Alben auswählen (Mehrfachauswahl)      │
│      → unten gezielt ankreuzen, was gelöscht werden soll      │
│                                                               │
│  Vorschau  wird gelöscht (260 MB):                          │
│  ┌──────────────────────────────────────────────────────┐   │
│  │ 📁 2026-05-03 Morgen  Themengottesdienst …    120 MB │   │
│  │ 📁 2026-05-10 Abend   Gebetsabend …            95 MB │   │
│  │ 📄 alte_ansage.mp3   (lose im Wurzelordner)    45 MB │   │
│  └──────────────────────────────────────────────────────┘   │
│                                                               │
│              [ Abbrechen ]      [ Löschen … ]                 │
└──────────────────────────────────────────────────────────────┘
  • Die Vorschau listet genau die betroffenen Ordner (Alben) auf — und einzelne Dateien, falls lose Dateien direkt im Wurzelverzeichnis des Sticks liegen (Symbol 📄). Sie aktualisiert sich je nach gewählter Variante; gelöscht wird erst nach Bestätigung.

  • Bei „Bestimmte Ordner/Alben auswählen" wird die Liste ankreuzbar (Mehrfachauswahl); gelöscht wird genau die Auswahl:

    Bestimmte Ordner/Alben löschen:
    ┌──────────────────────────────────────────────────────┐
    │ ☑ 📁 2026-06-07 Morgen  Themengottesdienst …  120 MB │
    │ ☐ 📁 2026-06-08 Abend   Gebetsabend …          95 MB │
    │ ☑ 📁 2026-05-31 Morgen  Predigtreihe Römer …  130 MB │
    │ ☐ 📄 alte_ansage.mp3   (lose im Wurzelordner)  45 MB │
    └──────────────────────────────────────────────────────┘
    Ausgewählt: 2 Einträge (250 MB)        [ Auswahl löschen … ]
    
  • Hinweis: „Veranstaltungsdatum" und „Kopierzeitpunkt" können abweichen, wenn alte Gottesdienste erst spät auf den Stick gezogen werden — deshalb beide Varianten getrennt.

Einstellungen (passwortgeschützt, mit Touch-Tastatur)

┌───────────────────────────────────────────────┐
│  Einstellungen  Anmeldung                     │
│   Passwort:  [ • • • • • •            ]        │
│   ┌─────────────────────────────────────────┐ │
│   │ q w e r t z u i o p ü    ⌫              │ │   ← eingeblendete
│   │ a s d f g h j k l ö ä    ↵              │ │     Touch-Tastatur
│   │ ⇧ y x c v b n m , . -                   │ │
│   │ [        Leertaste        ] 123         │ │
│   └─────────────────────────────────────────┘ │
└───────────────────────────────────────────────┘

Nach Login: Tabs für Veranstaltungstypen verwalten, Nextcloud (Login Flow v2), USB / Autoradio-Standard, Feiertage, Cache (Pfad & Schwelle), HTTPS-Passwort. Die HTTPS-Seiten (Status/Logs, Einstellungen) sind kein Teil dieser GUI, sondern werden im Browser auf einem anderen Gerät im LAN aufgerufen.

Fehlerfälle & Meldungen

Leitlinien:

  • Kiosk-Meldungen sind kurz, verständlich und nennen eine konkrete Handlung (für Nutzer ohne technisches Wissen).
  • Technische Details (Stacktraces, Codes, Pfade) erscheinen nicht im Kiosk, sondern auf der HTTPS-Status-/Log-Seite.
  • Wo sinnvoll wird automatisch wiederholt (v. a. Netzwerk), ohne den Nutzer zu blockieren.
  • Bei Abbruch eines Kopiervorgangs werden unvollständige Dateien auf dem Stick wieder entfernt.
  • Fehler dürfen die GUI nie einfrieren und nicht zum Absturz führen.

Nextcloud / Datenquelle

Situation Meldung (Kiosk) Behebung
Server nicht erreichbar / kein Netzwerk „Keine Verbindung zur Nextcloud es werden die zuletzt geladenen Veranstaltungen angezeigt." Aus lokalem Cache weiterarbeiten; automatischer Retry im Hintergrund; Netzwerk/Server prüfen (Details im Log).
Anmeldung fehlgeschlagen / App-Passwort ungültig oder widerrufen „Anmeldung bei Nextcloud fehlgeschlagen. Bitte in den Einstellungen neu anmelden." In den Einstellungen Login Flow v2 erneut durchlaufen → neues App-Passwort.
Login Flow nicht bestätigt / Zeitüberschreitung „Anmeldung nicht abgeschlossen bitte den Link im Browser bestätigen." Anmeldevorgang erneut starten.
Quellordner fehlt / falscher Pfad „Quellordner nicht gefunden. Bitte Einstellungen prüfen." Nextcloud-Pfad in den Einstellungen korrigieren.
Einzelne Datei nicht ladbar / Download unvollständig „Eine Datei konnte nicht geladen werden neuer Versuch …" Automatischer Retry; bleibt es fehlerhaft, Eintrag mit Hinweis überspringen.
Ordner ohne JSON/TXT (keine Fehlermeldung) Erwartetes Verhalten: Ordner erscheint einfach nicht in der Liste, bis ngenAudioRecord die Datei beim Fertigstellen anlegt.

USB-Speicher

Situation Meldung (Kiosk) Behebung
Kein Stick angesteckt „Kein USB-Speicher erkannt. Bitte Stick anstecken." Kopieren-Button bleibt deaktiviert, bis ein Stick erkannt wird.
Stick während des Kopierens abgezogen „USB-Speicher wurde entfernt Kopiervorgang abgebrochen." Unvollständige Dateien werden (falls noch erreichbar) entfernt; Stick wieder anstecken und neu starten.
Nicht genug Platz „Nicht genug Speicherplatz: benötigt X MB, frei Y MB." Speicher freigeben oder anderen Stick verwenden; Größe wird vor dem Kopieren geprüft.
Schreibgeschützt „USB-Speicher ist schreibgeschützt." Schreibschutz-Schalter am Stick lösen oder anderen Stick verwenden.
Dateisystem nicht beschreibbar/unterstützt „Auf den USB-Speicher kann nicht geschrieben werden (Dateisystem)." Stick mit FAT32/exFAT formatieren (Hinweis: Formatieren löscht alle Daten).
Zugriffs-/Mount-Fehler „Auf den USB-Speicher kann nicht zugegriffen werden. Bitte erneut anstecken." Stick neu anstecken; Details im Log (Rechte/Mount).
Mehrere Sticks erkannt „Mehrere USB-Speicher erkannt. Bitte nur einen anstecken." Wird i. d. R. physikalisch verhindert; nur einen Speicher anstecken.
Sicheres Entfernen fehlgeschlagen (noch in Benutzung) „Speicher wird noch verwendet und kann nicht sicher entfernt werden." Kurz warten, bis der Schreibvorgang abgeschlossen ist, dann erneut.

Kopiervorgang

Situation Meldung (Kiosk) Behebung
Album liegt bereits auf dem Stick „Album schon vorhanden wird überschrieben." Kein Eingriff nötig; wird überschrieben.
Kopieren vom Nutzer abgebrochen „Kopieren abgebrochen." Je nach Auswahl: aktuellen Beitrag fertigstellen oder bereits Kopiertes wieder löschen (s. Dialog „Kopieren abbrechen").
Quelldatei zwischenzeitlich entfernt/geändert „Eine Datei der Veranstaltung ist nicht mehr verfügbar Liste wird aktualisiert." Liste neu laden; Vorgang erneut starten.
Kopie unvollständig / Prüfsumme passt nicht „Kopieren unvollständig. Bitte erneut versuchen." Teil-Dateien aufräumen und erneut kopieren.
Autoradio-Optimierung fehlgeschlagen „Autoradio-Optimierung fehlgeschlagen bitte erneut versuchen oder Option deaktivieren." Optimierung deaktiviert kopieren oder erneut versuchen (Details im Log).
Ungültige/zu lange Dateinamen für FAT32 (automatisch bereinigt) Bei aktiver Autoradio-Optimierung werden Sonderzeichen/Längen bereinigt; sonst Hinweis.

Einstellungen / Anmeldung

Situation Meldung (Kiosk) Behebung
Falsches Passwort „Falsches Passwort." Erneut eingeben; nach mehreren Fehlversuchen kurze Verzögerung gegen Erraten.
Kein Passwort gesetzt (Erststart) „Bitte ein Passwort für die Einstellungen festlegen." Beim Ersteinrichten Passwort vergeben (wird als Hash gespeichert).
Erster Aufruf der HTTPS-Seite ohne Passwort „Bitte zunächst ein Passwort für die HTTPS-Seite festlegen." Beim ersten Aufruf HTTPS-Passwort anlegen; erst danach Zugriff.
Einstellungen/Konfiguration beschädigt „Einstellungen konnten nicht gelesen werden Standardwerte werden verwendet." Backup aus Nextcloud wiederherstellen oder neu konfigurieren.

HTTPS-Server (Meldungen auf Status-/Log-Seite bzw. beim Start)

Situation Meldung Behebung
Port bereits belegt „HTTPS-Server konnte nicht starten: Port belegt." Anderen Port in den Einstellungen wählen; blockierenden Dienst beenden.
Zertifikat fehlt/ungültig/abgelaufen „Zertifikat ungültig oder abgelaufen." Neues (selbstsigniertes) Zertifikat erzeugen; auf den Clients neu vertrauen.
Binden/Netzwerk fehlgeschlagen „HTTPS-Server nicht erreichbar." Netzwerk/Firewall im LAN prüfen (Details im Log). Beeinträchtigt die GUI nicht.

Updates (Meldungen auf Status-/Log-Seite; im Kiosk nur dezenter Hinweis)

Situation Meldung Behebung
Repo/Netzwerk nicht erreichbar „Update konnte nicht geprüft werden wird später erneut versucht." Automatischer Retry im nächsten Zeitfenster; Netzwerk/Repo prüfen (Details im Log).
Signatur/Prüfsumme ungültig „Update abgelehnt Signatur ungültig." Update wird nicht installiert; Paketquelle/Schlüssel prüfen, Administrator informieren.
Update während laufendem Kopiervorgang fällig (keine Meldung) Erwartetes Verhalten: Update wird verschoben, bis Kopiervorgang/USB-Zugriff beendet ist.
Zu wenig Speicher für Update „Update nicht möglich zu wenig Speicherplatz." Cache/Logs aufräumen (s. Lokaler Cache), dann erneut; Details im Log.
Update/Neustart fehlgeschlagen „Aktualisierung fehlgeschlagen vorige Version wiederhergestellt." Automatischer Rollback auf die letzte funktionierende Version; Fehler im Log, Administrator informieren.

System / lokal

Situation Meldung Behebung
Lokaler Speicher (Cache) voll „Zu wenig lokaler Speicher bitte Administrator informieren." Cache wird ab der eingestellten Schwelle automatisch reduziert (s. Lokaler Cache); zusätzlich Logs aufräumen (Logrotation), ggf. Schwelle senken oder Speicher erweitern.
Fehlende lokale Schreibrechte (Log) Rechte des Datenverzeichnisses prüfen.
Absturz der Anwendung (automatischer Neustart) Autostart/Watchdog startet die App neu; letzter Zustand steht im Log.