160 lines
7.8 KiB
Markdown
160 lines
7.8 KiB
Markdown
# LehrerApp.Api – Deployment per Docker
|
||
|
||
## Starten
|
||
|
||
```bash
|
||
cd docker
|
||
JWT_SECRET=<zufälliger-langer-string> docker compose up -d
|
||
```
|
||
|
||
Alle Server-Daten (Ereignis-Logs, Snapshots, Anhänge, Nutzer, Schulstandorte und Wetter-Cache)
|
||
liegen im Named Volume `api-data` –
|
||
bei Neustarts/Updates bleibt es erhalten. Bewusst **kein** Bind-Mount auf einen Host-Pfad: bei
|
||
Git-basierten Deployments (z. B. Dokploy) wird das Checkout-Verzeichnis bei jedem Redeploy neu
|
||
geklont, ein dort liegendes `./data` wäre dabei jedes Mal leer (siehe TODO.md 10.2.4 für den
|
||
Vorfall, der das aufgedeckt hat).
|
||
|
||
## Nutzer anlegen
|
||
|
||
Es gibt keine offene Registrierung (`/api/auth/register`). Neue Nutzer werden per CLI im
|
||
laufenden Container angelegt:
|
||
|
||
```bash
|
||
docker compose exec api dotnet LehrerApp.Api.dll create-user <benutzername>
|
||
```
|
||
|
||
Fragt interaktiv nach einem Passwort (mind. 12 Zeichen). Alternativ nicht-interaktiv, z. B. aus
|
||
einem Skript:
|
||
|
||
```bash
|
||
docker compose exec api dotnet LehrerApp.Api.dll create-user <benutzername> --password "<passwort>"
|
||
```
|
||
|
||
Der Befehl beendet sich danach sofort wieder, ohne den API-Dienst zu starten – für den
|
||
eigentlichen Serverbetrieb läuft `docker compose up` unverändert weiter.
|
||
|
||
## Passwort zurücksetzen
|
||
|
||
Analog zu `create-user`, aber für einen bereits existierenden Nutzer (schlägt fehl, wenn der
|
||
Nutzer nicht existiert – zum Anlegen `create-user` verwenden):
|
||
|
||
```bash
|
||
docker compose exec api dotnet LehrerApp.Api.dll set-password <benutzername>
|
||
docker compose exec api dotnet LehrerApp.Api.dll set-password <benutzername> --password "<passwort>"
|
||
```
|
||
|
||
## Backup
|
||
|
||
`backup.sh` sichert das Named Volume `api-data` (Ereignis-Logs, Snapshots, Anhänge, Nutzer) über
|
||
einen kurzlebigen Alpine-Container als komprimiertes Tar-Archiv unter `docker/backups/` und räumt
|
||
Archive älter als 30 Tage automatisch auf:
|
||
|
||
```bash
|
||
./docker/backup.sh
|
||
```
|
||
|
||
Der volle Docker-Volume-Name ist `<compose-projektname>_api-data`. Lokal (`cd docker && docker
|
||
compose up`) ist das `docker_api-data` (Default im Skript), bei Dokploy der App-Slug, z. B.
|
||
`lehrerapp-syncserver-xyz_api-data` – im Zweifel prüfen mit `docker volume ls | grep api-data`
|
||
und per Umgebungsvariable übergeben:
|
||
|
||
```bash
|
||
LEHRERAPP_DATA_VOLUME=lehrerapp-syncserver-xyz_api-data ./docker/backup.sh
|
||
```
|
||
|
||
Für regelmäßige Sicherung z. B. per Cron, einmal täglich nachts:
|
||
|
||
```bash
|
||
0 3 * * * LEHRERAPP_DATA_VOLUME=... /pfad/zu/docker/backup.sh >> /pfad/zu/docker/backup.log 2>&1
|
||
```
|
||
|
||
## Rate Limiting
|
||
|
||
`/api/auth/login` ist auf 5 Versuche pro Minute begrenzt (Brute-Force-Schutz). Alle übrigen
|
||
Endpunkte sind zusätzlich global auf 120 Anfragen pro Minute je IP begrenzt. Anhänge sind auf
|
||
15 MB Anfragegröße gedeckelt (Kestrel `MaxRequestBodySize`), einzelne Dateien clientseitig
|
||
zusätzlich auf 10 MB (`IAttachmentStorage.MaxSizeBytes`).
|
||
|
||
## Schulstandort und Wetter
|
||
|
||
Die authentifizierten Endpunkte unter `/api/school` geocodieren eine Schuladresse einmalig über
|
||
Nominatim/OpenStreetMap und laden danach ausschließlich mit den ermittelten Koordinaten
|
||
MOSMIX-Vorhersagen und CAP-Warnungen des DWD. Ergebnisse werden gecacht; bei einem vorübergehenden
|
||
DWD-Ausfall liefert der Server den letzten erfolgreichen Stand. Für eine eigene Installation sollte
|
||
`GEOCODING_USER_AGENT` auf eine Kennung mit eigener Kontakt-Webseite gesetzt werden, beispielsweise
|
||
`LehrerApp-Server/1.0 (+https://schule.example)`. Der Container benötigt ausgehenden HTTPS-Zugriff
|
||
auf `nominatim.openstreetmap.org`, `www.dwd.de` und `opendata.dwd.de`.
|
||
|
||
## WebUntis
|
||
|
||
Die WebUntis-Zugangsdaten werden ausschließlich im API-Container konfiguriert und nie an den
|
||
Desktop-Client ausgegeben:
|
||
|
||
```dotenv
|
||
WEBUNTIS_SCHOOL=meine-schule
|
||
WEBUNTIS_HOST=meine-schule.webuntis.com
|
||
WEBUNTIS_USER=technischer-benutzer
|
||
WEBUNTIS_PASSWORD=geheimes-passwort
|
||
WEBUNTIS_CLIENT=LehrerApp
|
||
WEBUNTIS_SESSION_IDLE_MINUTES=10
|
||
```
|
||
|
||
`WEBUNTIS_HOST` kann entfallen, wenn der Host `<WEBUNTIS_SCHOOL>.webuntis.com` entspricht. Der
|
||
technische Benutzer benötigt die jeweiligen WebUntis-Leserechte und darf für die alte JSON-RPC-
|
||
Schnittstelle keine aktivierte Zwei-Faktor-Authentifizierung haben.
|
||
|
||
Der API-Server meldet sich beim ersten Abruf an und verwendet diese Sitzung für weitere Abrufe.
|
||
Erst wenn zehn Minuten lang kein WebUntis-Abruf mehr aktiv war, meldet er sich automatisch ab.
|
||
`WEBUNTIS_SESSION_IDLE_MINUTES` kann bei Bedarf auf einen Wert zwischen 1 und 30 Minuten geändert
|
||
werden.
|
||
|
||
Alle Endpunkte benötigen dasselbe Bearer-Token wie die Sync-API und führen genau einen Abruf aus;
|
||
sie speichern das Ergebnis nicht serverseitig:
|
||
|
||
| Endpunkt | Zweck |
|
||
| --- | --- |
|
||
| `GET /api/webuntis/student-report?className=7a` | Schülerreport, optional nach Klasse gefiltert |
|
||
| `GET /api/webuntis/schoolyears` | Schuljahre |
|
||
| `GET /api/webuntis/classes?schoolyearId=123` | Klassen eines Schuljahres |
|
||
| `GET /api/webuntis/teachers` | Lehrkräfte und WebUntis-IDs |
|
||
| `GET /api/webuntis/holidays` | Ferienzeiträume |
|
||
| `GET /api/webuntis/timegrid` | Stunden-/Zeitraster |
|
||
| `GET /api/webuntis/substitutions?startDate=20260824&endDate=20260828` | Vertretungen im Zeitraum |
|
||
| `GET /api/webuntis/timetable?elementType=teacher&elementId=42&startDate=20260824&endDate=20260828` | Stundenplan für Klasse, Lehrkraft, Fach, Raum oder Schüler |
|
||
| `GET /api/webuntis/students/9001/absences?startDate=20260801&endDate=20270731` | Fehlzeiten eines Schülers; `9001` ist der `externKey` aus dem Schülerreport |
|
||
| `GET /api/webuntis/students/17/class-register-entries?startDate=20260801&endDate=20270731` | Klassenbucheinträge eines Schülers; `17` ist dessen `untisId` |
|
||
| `GET /api/webuntis/class-register/categories` | Kategorien der Klassenbucheinträge |
|
||
| `GET /api/webuntis/class-register/category-groups` | Kategoriegruppen der Klassenbucheinträge |
|
||
|
||
Datumsparameter verwenden das WebUntis-Format `yyyyMMdd`. Vertretungen sind auf 31 Tage und
|
||
Stundenpläne auf 62 Tage pro Anfrage begrenzt, damit ein einzelner Client keine unkontrolliert
|
||
großen WebUntis-Abfragen auslösen kann. Fehlzeiten und Klassenbucheinträge dürfen für einen
|
||
kompletten Schuljahreszeitraum von bis zu 400 Tagen geladen werden. Diese Klassenbuchfunktionen
|
||
sind nur verfügbar, wenn das Modul an der Schule aktiv ist und der technische Benutzer die
|
||
benötigten Leserechte besitzt.
|
||
|
||
## Deployment über Dokploy
|
||
|
||
Kein manuelles Bauen/Hochladen nötig – Dokploy zieht das Repo direkt per Git und baut das Image
|
||
selbst aus `docker/Dockerfile.api`.
|
||
|
||
1. Neue **Compose-Application** anlegen, Quelle „Git" (Custom Git Provider), Repo-URL
|
||
`https://git.science-teaching.de/admin/LehrerApp.git`, Branch `main`. Bei privatem Repo einen
|
||
SSH-Deploy-Key in Dokploy hinterlegen und im Git-Server eintragen.
|
||
2. **Compose Path**: `docker/docker-compose.yml`. Der Build-Context (`context: ..`) und das
|
||
Dockerfile sind darin bereits korrekt referenziert – Dokploy checkt das ganze Repo aus, das
|
||
passt ohne Anpassung.
|
||
3. **Umgebungsvariable** `JWT_SECRET` im Dokploy-UI unter „Environment" setzen (nicht als `.env`
|
||
einchecken) – wird als `${JWT_SECRET}` in die Compose-Datei injiziert, wie beim lokalen Start.
|
||
4. **Domain/Port**: Domain auf Container-Port `5000` mappen, HTTPS über Dokploys eingebauten
|
||
Reverse-Proxy aktivieren.
|
||
5. **Auto-Deploy**: Webhook-URL aus Dokploy als Push-Hook im Git-Server eintragen (falls
|
||
unterstützt, z. B. Gitea) – dann löst jeder `git push` automatisch ein Rebuild aus.
|
||
6. **Nutzer anlegen** nach dem ersten Deploy: über Dokploys Terminal/Exec-Tab am laufenden
|
||
Container `dotnet LehrerApp.Api.dll create-user <name>` ausführen (siehe oben).
|
||
|
||
**Wichtig:** die Daten liegen im Named Volume `api-data`, nicht in einem Bind-Mount – Dokploy
|
||
klont das Repo bei jedem Redeploy neu, ein Bind-Mount relativ zum Checkout wäre dabei jedes Mal
|
||
leer gewesen (das ist real passiert, siehe TODO.md 10.2.4). Named Volumes übersteht Dokploys
|
||
`docker compose up --build --remove-orphans` dagegen unverändert.
|