- TypeScript 99.8%
- Dockerfile 0.1%
| .forgejo/workflows | ||
| app | ||
| deploy | ||
| lib | ||
| public | ||
| scripts | ||
| tests | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| docker-compose.yml | ||
| Dockerfile | ||
| eslint.config.mjs | ||
| next.config.ts | ||
| package-lock.json | ||
| package.json | ||
| proxy.ts | ||
| README.md | ||
| SECURITY.md | ||
| tsconfig.json | ||
ELAN
Erfassung von Leistung, Anwesenheit und Notizen – der digitale Unterrichtsassistent.
Eine installierbare PWA für die schnelle, stundenbezogene Erfassung von Bewertungen, Klausurnoten, Kenntnisnahmen und freien Listen. Der Pilot liest den persönlichen Unterrichtstag aus der bestehenden WebUntis-jsonrpc.do-Schnittstelle und speichert personenbezogene Inhalte ausschließlich clientseitig verschlüsselt.
Funktionsumfang
- erkennt aus dem WebUntis-Tagesplan die laufende beziehungsweise zeitlich nächste Stunde
- führt direkt angrenzende Perioden derselben
lsnumberund Schülergruppe zu einer Doppelstunde zusammen - gewichtet Doppelstunden automatisch doppelt; vorhandene Stundenbewertungen werden beim Entsperren des Lehrkraft-Tresors anhand der gespeicherten Stunden-Snapshots abgeglichen, ohne Noten, Kommentare oder Freigabestatus zu ändern
- erkennt Vertretungen über WebUntis-Markierungen und Wechsel der ursprünglichen Lehrkraft (
te.orgid); fremde Vertretungsgruppen werden nicht als Kurse angelegt - gleicht Vertretungsgruppen mit dem Grundstundenplan der betreffenden Woche und regulären Stunden des Abgleichstags ab; eine frühere Bewertung allein belegt keinen eigenen Kurs. Wenn der Grundstundenplan nicht abrufbar ist, werden nur durch reguläre Stunden des Abgleichstags belegte Vertretungsgruppen übernommen
- blendet Prüfungsstunden aus, solange für deren Kurs noch keine Stundenbewertung existiert
- filtert
periodsWithAbsencesnach Datum, Schülergruppe und den exakten Periodenzeiten - löst Schülernamen über
getStudentssowohl anhand vonidals auchkeyauf - unterscheidet anwesend, teilweise abwesend, abwesend und noch nicht geprüft
- lässt Bewertungen nur für anwesende oder teilweise anwesende Kursteilnehmer zu
- erkennt die WebUntis-Entschuldigungsbezeichnungen „(e)“ und „AT“ zusätzlich zu ausgeschriebenen deutschen und englischen Statuswerten; entschuldigte Fehlzeiten erscheinen orange, bei „AT“ ergänzt um „mit Attest“
- erlaubt ausdrücklich „Keine Bewertung“; ein erneuter Klick auf eine gewählte Note entfernt sie. Die Entscheidung bleibt bei Synchronisierungen erhalten, zählt nicht zum Durchschnitt und beendet offene Bewertungshinweise auch bei unentschuldigtem Fehlen
- legt zusätzliche Leistungen je Kurs mit frei gewählter Bezeichnung, Datum und Gewicht an und erlaubt die spätere Bearbeitung; anschließend werden die Noten für alle SuS in einer gemeinsamen Kursliste erfasst (1 zählt wie eine Einzelstunde, 2 wie eine Doppelstunde)
- erlaubt es, Kurse in der Kursübersicht dauerhaft auszublenden und im eingeklappten Bereich „Ausgeblendet“ wieder einzublenden; erneute Synchronisation behält die Auswahl bei
- ermöglicht das endgültige Löschen eigener ausgeblendeter Kurse nach Bestätigung, einschließlich Bewertungen, Listen, Stundenthemen, Schülerzuordnungen und Stunden; zugehörige Browser-Caches werden bereinigt
- bietet Schülern den Schlüssel-Reset direkt auf der Entsperrseite nach der IServ-Anmeldung an; die bisherige Passphrase ist dafür nicht nötig
- unterstützt Klausuren/Klassenarbeiten
- unterstützt freie Listen mit Status und Kommentar
- integriert das Ausblenden als dezente Icon-Aktion in die Kurstabelle; ein separater Bestätigungsbutton schützt vor versehentlichen Klicks und Doppeltipps
- gliedert die Kursseite in Tabs für Notenverteilung, tabellarische Schülerliste, Stundenübersicht und Kurseinstellungen; Schülerzeilen öffnen die Details, Stundenzeilen direkt die entsprechende Schnellerfassung mit angepasstem Datum
- zeigt in den Lehrkraft-Schülerdetails die letzten fünf SoMi-Noten, Notenentwicklung und persönliche Erfolge und bietet dort die direkte, wiederholbare Vergabe eigener oder vorhandener Abzeichen
- bietet je Kurs Live-Veröffentlichung einschließlich aller vorhandenen Noten oder manuelle Freigabe
- bietet Schülern eine eigene Ansicht für ausschließlich ihre veröffentlichten Daten
- zeigt je Schülerkurs die fünf neuesten freigegebenen SoMi-Noten mit Datum, einschließlich zusätzlicher Leistungen; die Übersicht folgt der Auswahl des Halbjahres
- bietet unter Bewertungen → Abzeichen 16 Vorlagen sowie eigene Abzeichen mit freiem Namen, Beschreibung, Icon und Farbe; alle sind direkt und mehrfach an einzelne oder mehrere SuS vergebbar, ohne Einfluss auf Noten
- erlaubt das Bearbeiten und bestätigte Löschen eigener Abzeichenvorlagen; bereits verliehene Abzeichen behalten ihre ursprünglichen Angaben. Für eigene Vorlagen stehen 30 Symbole zur Verfügung, darunter Pokal, Diplom, zzZ, Computer, Buch und Dokument
- zeigt Schülern eine persönliche Abzeichensammlung mit Anzahl, Vergabedaten und Nachrichten sowie die aktuellen Noten- und Teilnahmeserien und den Fortschritt zum nächsten Sterne-Abzeichen; die Sammlung lässt sich je Kurs ausblenden; Animationen haben keinen Abschaltschalter, reduzierte Bewegung wird berücksichtigt; bisher ungesehene Abzeichen erscheinen in der Schüleransicht nacheinander als Popup mit Konfetti, ein Klick auf ein Abzeichen spielt seine Animation erneut ab. Erst das tatsächlich sichtbare Popup merkt die jeweilige Vergabe auf diesem Browser als gesehen – auch wiederholte Vergaben desselben Abzeichens werden separat gefeiert
- zeichnet die erste sehr gute SoMi-Bewertung (1+, 1, 1−) separat aus und vergibt bei 5, 10, 15 … sehr guten Bewertungen jeweils ein „Fünf Sterne“-Abzeichen; die erste zählt mit, die Bewertungen müssen nicht in Folge sein
- erkennt getrennte sehr gute und gute Notenserien, deutliche Verbesserungen und ununterbrochene Teilnahme im entsperrten Lehrkraftkonto; automatische Noten-Erfolge verwenden ausschließlich freigegebene Bewertungen und können je Kurs deaktiviert werden
- zeigt die zeitliche Entwicklung des gewichteten SoMi-Durchschnitts in der Schüler-Einzelansicht und der persönlichen Schüleransicht; das vorherige Halbjahr kann separat eingeblendet werden
- zeigt in der Kursübersicht die Notenverteilung der aktiven SuS für das aktuelle Halbjahr, wahlweise für Gesamtnote, SoMi oder schriftliche Note (auf ganze Noten gerundet)
- erlaubt Schülern bei verlorener Passphrase einen vollständigen Schlüssel-Reset
- zeigt allen betroffenen Kurslehrkräften lokale Rekey-Aufträge mit Wiederherstellungsbutton
- öffnet aus der aktuellen Stunde direkt den WebUntis-Stundenplan in einem neuen Tab
- öffnet lokale Mailprogramme über
mailto:für zugeordnete IServ-Kursgruppen und einzelne WebUntis-Benutzernamen - ist als PWA installierbar und hält die verschlüsselte letzte Synchronisation lokal bereit
Die Schülerübersicht zeigt für die beiden Quartale des aktuellen Halbjahres jeweils SoMi und bei schriftlicher Kurswahl die schriftliche Note sowie die aktuelle Halbjahresnote und eine vorhandene vorherige Halbjahresnote. Die beiden Quartalsnoten werden getrennt berechnet und gleich gewichtet zur Halbjahresnote gemittelt; die Anzahl der Bewertungen oder Unterrichtsstunden eines Quartals erhöht dessen Anteil am Halbjahr nicht. Manuell vorgegebene Quartals- und Halbjahresnoten gehen in die jeweils nächste Berechnungsebene ein. Als vorläufige Schuljahresnote dient das Mittel der verfügbaren Halbjahresnoten, bis die Lehrkraft sie überschreibt. Ein deutlicher Hinweis erklärt, dass für die Endnote vor allem das zweite Halbjahr maßgeblich ist und das Mittel die persönliche Entwicklung sowie pädagogische Erwägungen nicht hinreichend abbildet. Fehlende Quartale oder Halbjahre zählen nicht als Nullnote. Einzelbewertungen tragen den Titel der Klausur oder zusätzlichen Leistung bzw. „SoMi vom …“. Freigegebene Stundenthemen sind direkt an der zugehörigen Stundenbewertung einklappbar; sie erscheinen nicht als eigene Bewertungen. Die Schüleransicht beschränkt Noten und Entwicklungen auf das aktuelle Schuljahr. Die vorhandene Abzeichensammlung bleibt erhalten. Freigegebene Kurse desselben Fachs werden anhand der WebUntis-Fachkennung zusammengefasst; ohne Fachkennung bleiben Kurse getrennt. Die Schülerübersicht zeigt je Fach einen eigenen Tab mit dem aktuellen Kurs. Die Tabs lassen sich auch mit den Pfeiltasten sowie Pos1 und Ende wechseln; Diagrammauswahl und geöffnete Details bleiben je Fach erhalten. Die Übersicht nennt Fach und aktuellen Kurs; die Halbjahresauswahl im Diagramm nennt die jeweiligen Kurse in Klammern. Die SoMi-Entwicklung lässt sich auf ein verfügbares Halbjahr oder alle Noten des aktuellen Schuljahres umschalten. Jedes Halbjahr beginnt mit seinem eigenen Durchschnitt; auch im Verlauf werden die verfügbaren Quartalsdurchschnitte gleich gewichtet. Diese Anzeigebeschränkung löscht keine bereits gespeicherten Daten und ändert keine Freigaben.
Schüler können über PDF exportieren eine Gesamtübersicht aller freigegebenen Fächer und je Fach die Quartals- und Halbjahresnoten, die Schuljahresnote sowie die SoMi-Entwicklung des aktuellen Schuljahres herunterladen. Alle Einzelbewertungen mit zugehörigen Stundenthemen und Kommentaren können optional eingeschlossen werden; das ist unabhängig von eingeklappten Bereichen und der aktuellen Diagrammauswahl. Die PDF entsteht ausschließlich im Browser aus den bereits entschlüsselten Daten. Schriften werden von der eigenen Instanz geladen, nicht von externen Diensten. Berechnete und manuell festgelegte Noten sowie die pädagogischen Grenzen der Berechnung werden kenntlich gemacht.
Datenschutz- und Schlüsselmodell
Beim ersten Login erzeugt der Browser ein ECDH-P-256-Schlüsselpaar. Der private Schlüssel wird mit einer vom Nutzer gewählten Passphrase über PBKDF2-SHA-256 (310.000 Iterationen) und AES-256-GCM verschlüsselt. Die Datenbank erhält nur den öffentlichen Schlüssel und das verschlüsselte Backup.
Jeder Datensatz erhält einen zufälligen AES-256-GCM-Datenschlüssel. Dieser wird separat für jeden berechtigten Empfänger über einen ephemeren ECDH-Schlüssel verpackt. Dadurch können Lehrkraft und verknüpfter Schüler denselben Datensatz unabhängig entschlüsseln; andere Schüler erhalten keinen Schlüsselumschlag. Die Datenbank speichert Kommentare, Noten und Kurslisten verschlüsselt. Namen und Kontonamen liegen als verschlüsselte Anzeigedaten sowie als Hashwerte für den Abgleich vor. IServ-Subject und E-Mail werden nicht dauerhaft in SQLite gespeichert; die Benutzer-ID ist ein SHA-256-Pseudonym.
WebUntis liefert bei getTimetableWithAbsences schulweite Zeilen. Diese Rohantwort wird ausschließlich im Arbeitsspeicher verarbeitet, sofort auf die Unterrichtsblöcke der angemeldeten Lehrkraft eingeschränkt und nicht gespeichert oder an den Browser zurückgegeben. Persistiert wird ein nochmals im Browser verschlüsselter Kurs-Snapshot.
Schüler verwenden ausschließlich den IServ-Login. Der direkte Abgleich mit einem eindeutigen Benutzernamen oder, wenn im WebUntis-Profil kein Kontoname vorliegt, einem eindeutigen vollständigen Namen erfolgt beim Login und beim Aktualisieren der Schüleransicht mit der aktuellen IServ-Kursgruppe. Eine Lehrkraft-Aktualisierung erzeugt keine neue Namenszuordnung aus alten Anmeldedaten. WebUntis-Mailadressen werden nur aus der konfigurierten IServ-Maildomain übernommen. Widersprüche und mehrdeutige Namen bleiben offen.
Bestätigte Zuordnungen werden schulweit anhand eines Hashwerts der internen WebUntis-Schüler-ID gespeichert, gelten dauerhaft und werden auch in anderen Fächern, bei anderen Lehrkräften sowie bei späteren Kursimporten übernommen. Bestehende verschlüsselte Profile erhalten diese Kennung beim nächsten Öffnen ihrer Kursliste oder bei der Synchronisierung. Bis dahin können verschlüsselte Altprofile noch nicht fachübergreifend abgeglichen werden. Es gibt keine zusätzliche Zeitbegrenzung für eine Zuordnung. IServ-Anmeldesitzungen behalten ihre eigene Gültigkeitsdauer.
Manuelle Zuordnungen und Korrekturen sind ausschließlich für Admins (Rolle admin oder Lehrkraft in admins) möglich: in den Kurseinstellungen direkt am Schüler oder unter Einstellungen → Schulweite Schülerzuordnung über interne WebUntis-ID und IServ-Benutzername. Für IServ-Konten ohne Kontonamen ist der eindeutige vollständige IServ-Anzeigename möglich. Das Schülerkonto muss sich einmal in ELAN angemeldet haben; Kursgruppe und Alter dieser Anmeldung begrenzen eine Admin-Zuordnung nicht. Eine ausdrückliche Bestätigung zeigt die schulweit betroffenen Kurszuordnungen an. Ein Konto kann nicht stillschweigend einer zweiten Person entzogen werden; dazu muss die bisherige zentrale Zuordnung zuerst ausdrücklich gelöst werden. Admin-Korrekturen sind autoritativ, werden protokolliert und durch spätere Anmeldungen oder Synchronisierung nicht überschrieben. Eine ausdrückliche Lösung sperrt auch die erneute automatische Verknüpfung. Kurs- und Notenfreigabe bleiben getrennte Entscheidungen.
Bei einer Änderung werden alte Schüler-Schlüsselumschläge sofort entfernt und die betroffenen persönlichen Dokumente und bereits veröffentlichten Stundenthemen für Schüler gesperrt. Die zuständige Lehrkraft erzeugt bei entsperrtem Tresor automatisch neue AES-Datenschlüssel, verschlüsselt den Inhalt neu und verpackt diese ausschließlich für die aktuellen Empfänger. Eine bloße Umschlüsselung des bisherigen Datenschlüssels reicht für Kontokorrekturen nicht. Fehlt der neue Schüler-Schlüssel, bleiben die Dokumente gesperrt, bis der Tresor eingerichtet ist. Admins erhalten durch die Korrekturfunktion keinen Zugriff auf die entschlüsselten Noten anderer Lehrkräfte. Bereits heruntergeladene alte Inhalte können technisch nicht nachträglich zurückgerufen werden.
Die schulweite Admin-Zuordnung prüft den exakt eingegebenen IServ-Benutzernamen gegen bereits angemeldete Schülerkonten; bei Konten ohne Benutzernamen ist ein eindeutiger vollständiger IServ-Anzeigename möglich.
„Bewertungen automatisch freigeben“ erfasst auch die gesamte bereits gespeicherte Notenhistorie des Kurses: Stunden-SoMi, zusätzliche bewertete Leistungen, Klausuren sowie manuell gesetzte Quartals-, Halbjahres- und Schuljahresnoten. Beim entsperrten Lehrkraftkonto werden ausstehende Entwürfe automatisch für das aktuell zugeordnete Schülerkonto verschlüsselt und freigegeben. Dies gilt auch für Kurse, deren Automatik schon vor dem Update aktiv war. Fehlende Kontozuordnungen und noch nicht eingerichtete Schüler-Tresore bleiben ausstehend und werden bei einer späteren Prüfung automatisch berücksichtigt. Die Inhalte bleiben unverändert verschlüsselt; nur der aktuelle Schüler-Schlüsselumschlag wird ergänzt. Kurs- und Themenfreigaben bleiben unabhängig, Listen werden dadurch nicht veröffentlicht. Beim Ausschalten der Automatik bleiben bereits veröffentlichte Noten freigegeben. Nach einer Kontokorrektur wird zuerst die vorgeschriebene vollständige Neuverschlüsselung durchgeführt.
Der bestätigte IServ-Benutzername und Anzeigename werden bereits beim erfolgreichen OIDC-Login als getrennte Hashwerte registriert, unabhängig von Kursgruppen und Tresoreinrichtung. Fehlt ein nichtleerer preferred_username, dient der lokale Teil der IServ-E-Mail als Benutzername. Bestehende Sitzungen holen die Registrierung beim Abrufen der Sitzung oder Kurse nach; fehlende Identitätsfelder löschen bereits bekannte Hashwerte nicht. Unbekannte Benutzernamen und mehrfach vorhandene Kontoidentitäten liefern unterschiedliche Fehlermeldungen. Mehrdeutige Konten werden nicht automatisch ausgewählt oder zusammengeführt.
Abzeichenvorlagen, persönliche Nachrichten, Auszeichnungen und aktuelle Serienstände werden ebenfalls im Browser verschlüsselt. Schüler erhalten nur ihre eigenen Zählerstände mit dem Datum der letzten Lehrkraftprüfung, keine Kurslisten oder Anwesenheitsdaten anderer Personen. Vergebene Abzeichen werden für verknüpfte Schüler mit eingerichtetem Tresor unmittelbar freigegeben, auch wenn Bewertungen im Kurs manuell freigegeben werden. Ohne Verknüpfung bleiben sie für die Lehrkraft gespeichert und werden nach Verknüpfung beim nächsten geöffneten Lehrkraft-Tresor für den Schüler verschlüsselt. Eine versehentliche Vergabe lässt sich zurücknehmen und wiederherstellen. Automatische Vergaben sind gegen Duplikate geschützt; zurückgenommene automatische Auszeichnungen werden nicht erneut angelegt.
„Sprung nach vorn“ vergleicht den gewichteten SoMi-Schnitt zweier aufeinanderfolgender 14-Tage-Fenster und setzt eine Verbesserung um mindestens eine Notenstufe voraus. Fünf bewertete Unterrichtsstunden insgesamt dienen als anfänglicher Schutz gegen stark schwankende Schnitte; pro Zeitfenster gibt es keine feste Stundenzahl. Beide Fenster benötigen mindestens eine gültige Bewertung. Bereits verwendete Bewertungen lösen keine zweite Verbesserungsauszeichnung aus. „Sehr guter Lauf“ zählt Stundenbewertungen mit 1+, 1 oder 1− in Folge; „Guter Lauf“ zählt gute oder bessere Stundenbewertungen (1+ bis 2−). Stunden ohne Bewertung und zusätzliche Leistungen unterbrechen diese Serien nicht. Beide Notenserien und Teilnahmeserien erhalten Auszeichnungen bei 3, 5, 10 und jeweils fünf weiteren Stunden. Entschuldigte, ungeklärte und ungeprüfte Fehlzeiten pausieren die Teilnahme-Serie, ausgefallene Stunden werden übersprungen. Unentschuldigte Fehlzeiten starten eine neue Serie; bereits erhaltene Abzeichen bleiben bestehen. Die automatische Prüfung nutzt die verschlüsselten Stunden-Snapshots der Lehrkraft, auf die Schüler keinen Zugriff erhalten.
Ein fehlender IServ-Benutzername schließt ein Konto mit bestätigtem Namen und Kursgruppe nicht vom direkten eindeutigen Namensabgleich aus. „Aktualisieren“ zeigt Schülern an, ob eine passende Kursgruppe gefunden wurde, eine Zuordnung offen ist oder bereits besteht. Die Gruppen stammen aus der Anmeldung und werden durch normales Aktualisieren nicht erneut bei IServ abgefragt. Alte Schüler-Sitzungen mit verloren gegangenen Gruppen werden deshalb verworfen und erfordern eine neue Anmeldung. Enthält auch eine aktuelle ELAN-Sitzung keine IServ-Gruppen, erscheint ein Hinweis mit „Erneut über IServ anmelden“. Bleibt user.groups unter /api/auth/session anschließend leer, müssen die Gruppenmitgliedschaft und die Freigabe des Scopes iserv:groups im IServ-SSO-Client geprüft werden.
Die WebUntis-Schülergruppe wird nach den lokalen Namensregeln in eine IServ-Gruppe übersetzt: einzelne Sek.-I-Klassen in klasse.<Klasse>.<Fach>, klassenübergreifende Sek.-I-Kurse in kurs.<Jahrgang>.<FachKurs> und Oberstufenkurse in kurs.<Stufe>.<Fach>.<Kurs>. Einstellige Sek.-I-Jahrgänge erhalten die führende Null. Da lokale Bezeichnungen abweichen können, lässt sich diese Zuordnung pro Kurs manuell überschreiben und auf die automatische Erkennung zurücksetzen.
Schüler können ihren Schlüssel zurücksetzen, auch wenn die bisherige Tresor-Passphrase verloren wurde. Dabei wird der alte Schlüssel sofort durch ein neues Schlüsselpaar ersetzt. Vorhandene Datensätze sind anschließend vorübergehend nicht sichtbar. Jede Kurslehrkraft erhält beim nächsten Öffnen der App einen lokalen Rekey-Auftrag. Ihr Browser entschlüsselt mit dem Lehrerschlüssel ausschließlich den AES-Datenschlüssel und verpackt ihn neu für den aktuellen Schüler-Key; Chiffrat, Bewertung und Kommentar werden nicht erneut entschlüsselt oder an den Server übertragen. Neue Datensätze verwenden sofort den neuen Schlüssel.
Lehrerschlüssel sind weiterhin nicht über diesen Mechanismus zurücksetzbar. Für sie ist vor einem realen Rollout ein organisatorisches Recovery-Konzept erforderlich, etwa ein separat verwahrter Recovery-Empfänger.
Installation
ELAN läuft vollständig lokal. Zur Laufzeit werden ausschließlich die konfigurierte IServ- und WebUntis-Instanz angesprochen. Benötigt werden Docker Engine mit Docker Compose, eine erreichbare IServ-OIDC-Konfiguration und für andere Geräte als localhost ein lokaler HTTPS-Reverse-Proxy.
1. Repository und Konfiguration
git clone ssh://git@dev.spittank.org/daniel/Elan.git
cd Elan
cp .env.example .env
In .env müssen mindestens diese Werte angepasst werden:
APP_ORIGIN=https://elan.example.schule
SESSION_SECRET=HIER_EIN_ZUFAELLIGES_GEHEIMNIS_MIT_MINDESTENS_32_ZEICHEN
ISERV_ISSUER=https://iserv.example.schule
ISERV_CLIENT_ID=elan
ISERV_CLIENT_SECRET=HIER_DAS_CLIENT_GEHEIMNIS
ISERV_MAIL_DOMAIN=example.schule
Ein geeignetes Sitzungsschlüssel-Geheimnis lässt sich beispielsweise mit openssl rand -base64 48 erzeugen. .env ist von Git ausgeschlossen und darf nicht eingecheckt werden.
Im IServ-OIDC-Client muss als Callback exakt folgende Adresse eingetragen sein:
https://elan.example.schule/api/auth/callback
Die Basis muss mit APP_ORIGIN übereinstimmen. Freizugeben sind die Scopes openid, profile, email, iserv:groups und iserv:roles. In der mitgelieferten .env.example sind alle weiteren Einstellungen dokumentiert.
2. Installation aus dem Release-Container
Forgejo veröffentlicht bei Release-Tags automatisch ein Multi-Arch-Image für AMD64 und ARM64 unter dev.spittank.org/daniel/elan. Die Standard-Compose-Datei verwendet dieses Image ohne lokalen Build. Die aktuelle Version wird so gestartet:
docker compose pull
docker compose up -d
docker compose logs -f elan
Eine bestimmte Version kann ohne Änderung der Compose-Datei gewählt werden:
ELAN_IMAGE=dev.spittank.org/daniel/elan:1.0.0 docker compose up -d
Bei einer privaten Registry ist vorher docker login dev.spittank.org erforderlich.
ELAN ist danach am in APP_ORIGIN angegebenen Host beziehungsweise lokal unter http://localhost:3000 erreichbar. Der Health-Check steht unter /api/health bereit.
Falls Port 3000 bereits belegt ist, müssen APP_PORT und der Port in APP_ORIGIN gemeinsam geändert werden, beispielsweise auf 3001. Die geänderte Callback-URL muss dann auch im IServ-OIDC-Client hinterlegt sein.
3. Persistenz, Backup und Aktualisierung
Die SQLite-Datenbank liegt im Docker-Volume elan-data unter /app/data/elan.sqlite. Normales docker compose down erhält das Volume; docker compose down -v löscht es und darf bei bestehenden Daten nicht verwendet werden.
Konsistentes Backup der Docker-Installation:
docker compose stop elan
docker compose cp elan:/app/data/elan.sqlite ./elan-backup.sqlite
docker compose start elan
Aktualisierung einer Release-Installation:
git pull --ff-only
docker compose pull
docker compose up -d
Vor jeder Aktualisierung sollte ein Datenbank-Backup erstellt werden. Schemaerweiterungen werden beim Start idempotent ausgeführt.
Migration einer bisherigen School-Assistant-Installation
Beim ersten ELAN-Start werden eine vorhandene school-assistant.sqlite, Browser-Caches, gespeicherte Browser-Schlüssel und noch gültige Sitzungen automatisch übernommen. Bei einer Docker-Installation muss der Inhalt des bisherigen Volumes einmalig in elan-data kopiert werden; die lokale Entwicklungsinstallation in diesem Repository führt diesen Schritt beim Upgrade automatisch aus. Das alte Volume sollte erst nach erfolgreicher Kontrolle und einem Backup entfernt werden.
SQLite-Fehler: „unable to open database file“
Das Release-Image läuft als Benutzer elan. Dieser benötigt Schreibrechte auf /app/data und die darin gespeicherten SQLite-Dateien, einschließlich der WAL- und SHM-Dateien. Bei einem bestehenden oder migrierten Volume können die Dateien noch einem anderen Benutzer gehören. Pfad und Rechte lassen sich so prüfen:
docker compose exec elan sh -c 'id; printf "DATABASE_PATH=%s\n" "$DATABASE_PATH"; ls -ldn /app/data; ls -ln /app/data; test -w /app/data && echo data-writable'
Wenn das Volume wegen falscher Eigentümer oder Dateirechte nicht beschreibbar ist, können diese mit dem bereits vorhandenen Image korrigiert werden:
docker compose stop elan
docker compose run --rm --no-deps --user 0 --entrypoint sh elan -c 'chown -R elan:elan /app/data && chmod -R u+rwX /app/data'
docker compose up -d
docker compose exec elan node -e 'fetch("http://127.0.0.1:3000/api/health").then(async r => { console.log(r.status, await r.text()); if (!r.ok) process.exitCode = 1; }).catch(e => { console.error(e); process.exitCode = 1; })'
Der einmalige Wartungscontainer passt nur Eigentümer und Zugriffsrechte im Daten-Volume an. Die Anwendung startet anschließend wieder als elan; ein Image-Build ist nicht erforderlich. Bei Podman ist derselbe Ablauf mit dem verwendeten Compose-Aufruf auszuführen. Bleibt der Fehler bestehen, müssen Volume-Mount, freier Speicherplatz und gegebenenfalls SELinux-Freigaben geprüft werden.
Entwicklung ohne Docker
Voraussetzung ist Node.js 22.13 oder neuer.
npm install
npm run dev
Wenn Next.js wegen eines belegten Ports beispielsweise auf http://localhost:3001 ausweicht, muss APP_ORIGIN in .env ebenfalls http://localhost:3001 sein. Im IServ-OIDC-Client ist dann http://localhost:3001/api/auth/callback als Callback einzutragen. Anschließend den Entwicklungsserver neu starten und ELAN über die Adresse aus APP_ORIGIN öffnen. Andernfalls leitet der Login auf den alten Port weiter und erreicht möglicherweise einen anderen Dienst.
Ohne konfigurierte IServ-Werte zeigt die lokale Entwicklungsumgebung Demo-Anmeldungen. Diese sind in Produktions-Builds deaktiviert, solange ALLOW_DEV_LOGIN=true nicht ausdrücklich gesetzt wird. ALLOW_DEV_LOGIN=false deaktiviert die Testanmeldung auch in der Entwicklungsumgebung; beide Testanmelde-Buttons und Hinweise zur Testanmeldung werden dann nicht angezeigt. DATABASE_PATH ist optional und verwendet standardmäßig ./data/elan.sqlite.
Prüfkommandos:
npm run typecheck
npm run lint
npm test
npm run build
Automatische Builds und Releases
Forgejo Actions prüft bei jedem Push auf main sowie bei Pull Requests Typen, Lint, Tests, Produktions-Build und Docker-Build. Ein Git-Tag nach dem Muster v1.0.0 baut und veröffentlicht analog zu DomainOrganizer das Image für linux/amd64 und linux/arm64 als dev.spittank.org/daniel/elan:1.0.0 und :latest.
Im Forgejo-Repository müssen dafür dieselben Actions-Secrets wie bei DomainOrganizer hinterlegt sein:
REGISTRY_USERREGISTRY_TOKEN
Ein Release wird anschließend beispielsweise so ausgelöst:
git tag -a v1.0.0 -m "ELAN 1.0.0"
git push origin v1.0.0
IServ-Anbindung
Die Anmeldung verwendet den standardisierten Authorization-Code-Flow mit PKCE und validiert das ID-Token gegen die vom Issuer veröffentlichten JWKS. In IServ muss dafür ein OIDC/OAuth-Client mit folgender Callback-URL eingerichtet werden:
https://DEINE-APP/api/auth/callback
ISERV_ISSUER enthält nur die Basisadresse des IServ, beispielsweise https://iserv.example.schule. Die App probiert automatisch die von verschiedenen IServ-Versionen verwendeten Discovery-Pfade. Als Scopes werden standardmäßig openid profile email iserv:groups iserv:roles angefordert. Diese entsprechen den aktuellen IServ-SSO-Einstellungen für Gruppen und Rollen und lassen sich über ISERV_SCOPES anpassen. Gruppen und Rollen werden sowohl aus Arrays als auch aus den bei dieser IServ-Instanz bestätigten Objekten mit UUID-Schlüsseln gelesen. Liefert IServ keinen der Gruppen-Claims, bricht die Anmeldung mit einem Konfigurationshinweis ab, statt das Konto stillschweigend als Schüler einzuordnen.
APP_ORIGIN ist zugleich die kanonische Browseradresse und muss exakt mit der in IServ hinterlegten Callback-Basis übereinstimmen. Lokale Aliase wie localhost, 127.0.0.1 und 0.0.0.0 dürfen nicht gemischt werden, da OIDC-Transaktionscookies hostgebunden sind. HOSTNAME=0.0.0.0 in Docker ist ausschließlich die interne Bind-Adresse; im Browser wird die Adresse aus APP_ORIGIN verwendet.
ISERV_MAIL_DOMAIN legt die Domain für die mailto:-Links und den Kontenabgleich mit WebUntis-Mailadressen fest und ist für den Pilot standardmäßig gymsedan.de. Der Kontenabgleich verwendet die Laufzeit-Einstellung. Kursmails verwenden die zugeordnete IServ-Gruppe als lokalen Teil, Einzelmails den von WebUntis gelieferten Benutzernamen. Da die Mail-Link-Domain in die Browseroberfläche eingebaut wird, verwenden die Forgejo-Release-Images gymsedan.de. Für eine andere Domain muss ein Image mit dem Build-Argument NEXT_PUBLIC_ISERV_MAIL_DOMAIN veröffentlicht und über ELAN_IMAGE ausgewählt werden; eine Änderung der Laufzeit-.env allein reicht für die Mail-Links nicht aus.
Rollen werden aus dem konfigurierten Gruppen-Claim abgeleitet. Nicht zugeordnete Konten erhalten immer die Schülerrolle; eine Lehrkraftrolle wird nicht allein aufgrund einer E-Mail-Adresse vergeben.
Klassenlehrer werden nicht über eine eigene Rolle oder Gruppe erkannt. Ein Konto gilt als Klassenlehrer, wenn es zugleich Mitglied der konfigurierten Lehrergruppe und mindestens einer reinen Klassengruppe nach dem Muster klasse.X ist. Beispiele: klasse.9a und klasse.10b. Gruppen mit einem weiteren Punkt beziehungsweise Fachbezeichner werden nicht akzeptiert. Die hochgeschriebenen Altgruppen klasse.11, klasse.15, klasse.16, klasse.17, klasse.18 und klasse.19 werden ausdrücklich ignoriert.
WebUntis-Anbindung
Der Pilot benötigt keine Freischaltung durch Untis und führt ausschließlich Leseaufrufe gegen die vorhandene Legacy-JSON-RPC-Schnittstelle aus:
authenticategetTimetablegetTimetableWithAbsencesgetStudentsgetSubjectsgetRoomslogout
Zugangsdaten werden im Backend im Klartext nur für die Dauer des Abrufs gehalten. Bei „Zugang verschlüsselt auf dem Server merken“ werden URL, Schule, Benutzername und Passwort im Browser für den eigenen Lehrkraft-Tresor verschlüsselt und als Chiffrat in SQLite gespeichert. Nach dem Entsperren stehen sie auch auf anderen Geräten bereit. Bisherige verschlüsselte Browser-Einträge werden beim Laden der Verbindung übernommen und erst nach erfolgreichem Speichern entfernt. Die gespeicherte Verbindung lässt sich im WebUntis-Dialog sofort löschen. Konten, die ausschließlich über IServ-SSO bei WebUntis funktionieren und kein direktes WebUntis-Passwort besitzen, können diesen Abruf nicht verwenden.
WEBUNTIS_URL und WEBUNTIS_SCHOOL können die Verbindung schulweit in der Laufzeit-Konfiguration vorgeben. Gesetzte Felder sind im Dialog schreibgeschützt und werden auch serverseitig beim Abruf verbindlich verwendet; leere Werte erlauben eigene Eingaben.
Schulweite Schuljahres-, Halbjahres- und Quartalsgrenzen sind nur für Administratoren sichtbar und über die API änderbar. Die IServ-Gruppe admins wird immer erkannt; ISERV_ADMIN_GROUP kann eine weitere Administratorgruppe festlegen. Alle angemeldeten Nutzer lesen weiterhin dieselben Berechnungszeiträume.
Eigene Abzeichenvorlagen lassen sich im Vergabebereich bearbeiten und nach Bestätigung löschen. Bereits vergebene Abzeichen enthalten eine eigene Kopie ihrer Vorlage und bleiben unverändert. Für eigene Vorlagen stehen unter anderem Pokal, Diplom, zzZ, Computer, Buch, Dokument und weitere Symbole bereit.
Stunden-SoMi wird nach der Anzahl zusammengeführter Unterrichtsstunden gewichtet: Einzelstunde ×1, Doppelstunde ×2. Bereits gespeicherte Stundenbewertungen werden beim entsperrten Lehrkraftkonto anhand der synchronisierten Stundenstände angepasst und erneut verschlüsselt; Entwürfe bleiben dabei Entwürfe. Zusätzliche Leistungen behalten ihre frei gewählte Gewichtung. Die Gewichtung wird als „Gewicht ×…“ angezeigt.
Unter Bewertungen → Zusätzliche Leistung → Leistung bearbeiten lassen sich Bezeichnung, Datum und Gewichtung bestehender Leistungen ändern. Die Leistung und alle zugehörigen Bewertungen werden gemeinsam gespeichert, auch für inzwischen inaktive Schüler und ältere Leistungen ohne eigene Definition. Noten, individuelle Kommentare, Gruppen, Erfassungsdaten und Freigabestatus bleiben erhalten. Datum und Gewichtung gehen unmittelbar mit der passenden Schuljahres-, Halbjahres- und Quartalszuordnung in die Berechnung ein. Die Änderungen werden im Browser verschlüsselt und auf dem Server atomar übernommen; parallel gespeicherte Schülerangaben werden vor einem erneuten Versuch frisch geladen und erhalten.
Die Kursübersicht zeigt eine Tabelle mit Lerngruppe, Fach, aktiver Schülerzahl, synchronisierten Stunden und Freigabestatus. Pro Kurs gibt es Tabs für Notenverteilung, die tabellarische Schüler- und Notenliste, die Stundenübersicht mit Themen und Anwesenheitszahlen sowie Kurseinstellungen. Ein Klick auf eine Schülerzeile öffnet die Details, ein Klick auf eine Unterrichtszeile die zugehörige Schnellerfassung. Kurswahl und Maillinks in einer Schülerzeile lassen sich unabhängig bedienen. Teilweise Anwesende zählen als anwesend; unbekannte Anwesenheit wird separat ausgewiesen. Ausgefallene Stunden werden ausgelassen. Themen werden anhand der konkreten Unterrichtsblockkennung zugeordnet, auch bei mehreren Stunden desselben Kurses am selben Tag.
Standardmäßig sind nur öffentliche Hosts unter *.webuntis.com zulässig. Einen selbst betriebenen Host trägt man explizit in WEBUNTIS_ALLOWED_HOSTS ein. Es gibt bewusst keine Schreibzugriffe auf Stundeninhalte oder Fehlzeiten. Der Umschaltknopf öffnet stattdessen die passende WebUntis-Stundenplanansicht, in der die normale WebUntis-Anmeldung weitergilt.
Speicherung und Betrieb
Das Projekt verwendet ausschließlich eine lokale SQLite-Datei mit WAL-Modus und aktivierten Fremdschlüsseln. Tabellen und Indizes werden beim Start idempotent angelegt. Alle API-Routen prüfen Sitzung und Rolle serverseitig, auch wenn die Oberfläche eine Aktion bereits ausblendet. Docker bindet nur Port 3000 und ein lokales persistentes Daten-Volume ein.
Für Zugriffe anderer Geräte und die IServ-Callback-URL sollte vor dem Container ein lokal betriebener HTTPS-Reverse-Proxy stehen. Service Worker und sichere Cookies funktionieren außerhalb von localhost nur über HTTPS. APP_ORIGIN muss exakt auf diese lokale HTTPS-Adresse zeigen.
Vor einem schulweiten Betrieb sind zusätzlich erforderlich:
- Auftragsverarbeitung, Datenschutz-Folgenabschätzung und Lösch-/Aufbewahrungskonzept
- ein belastbares Schlüssel-Recovery- und Gerätewechselverfahren
- Abgleich der erkannten IServ-Klassengruppen mit den WebUntis-Klassen für die optionale klassenweite Einsicht
- Rate Limits, Monitoring, Backups und ein externer Penetrationstest
- Prüfung, ob die nicht garantierte Legacy-WebUntis-Schnittstelle nach Updates weiterhin funktioniert
Schlüsselversionen verhindern, dass alte Schüler-Schlüssel nach einem Reset erneut verwendet werden. Rekey-Aufträge werden je betroffenem Kurs geführt und können bei großen Datenmengen in begrenzten Batches verarbeitet werden. Die Rolle class_teacher und Mehrfach-Empfänger sind im Daten- und Schlüsselmodell bereits vorgesehen. Eine automatische klassenweite Freigabe bleibt absichtlich deaktiviert, bis eine verlässliche IServ/WebUntis-Klassenzuordnung definiert ist.