docs: alle Anleitungen gegen den Code geprueft und nachgearbeitet
Anwender-, Administrations-, Betriebs- und Entwicklungsanleitung gegen Code und Oberflaechentexte abgeglichen; falsche und veraltete Stellen korrigiert, fehlende Funktionen ergaenzt. Willkommensmail: Hinweis nennt jetzt sechs statt vier Felder. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+35
-10
@@ -66,7 +66,14 @@ Der Browser spricht ausschließlich mit `web` (Port 3000). Aufrufe unter
|
||||
Der Browser erreicht die API also nie direkt und muss auch keine eigene CORS-Freigabe
|
||||
für die API-Origin haben – Port 3001 muss von außen in der Regel nicht erreichbar
|
||||
sein, ist es in den mitgelieferten Compose-Dateien aber (Host-Port 3001 ist
|
||||
gemappt).
|
||||
gemappt, sowohl in `docker-compose.yml` als auch in `docker-compose.prod.yml`).
|
||||
|
||||
**Empfehlung zur Firewall:** Sperren Sie Port 3001 in der Firewall des Servers (bzw.
|
||||
lassen Sie ihn nur vom Server selbst zu) und geben Sie nach außen ausschließlich
|
||||
Port 3000 frei, im Normalfall über den Nginx Proxy Manager. Der Port 3001 wird für
|
||||
den normalen Betrieb nicht gebraucht, weil der Browser die API immer über `web`
|
||||
und `/api-proxy` erreicht. Die Abfragen mit `curl` auf `http://localhost:3001/...`
|
||||
in dieser Anleitung laufen auf dem Server selbst und bleiben davon unberührt.
|
||||
|
||||
## 2. Erstinstallation
|
||||
|
||||
@@ -153,15 +160,21 @@ Zugangsdaten.
|
||||
| `DB_PASSWORD` | ja | – | Passwort des PostgreSQL-Benutzers `tessera`, an den `db`-Container durchgereicht. |
|
||||
| `DATABASE_URL` | ja | – | Vollständiger Prisma-Connection-String, z. B. `postgresql://tessera:<passwort>@db:5432/tessera`. Muss zum `DB_PASSWORD` passen. |
|
||||
| `JWT_SECRET` | ja | – | Signiert/prüft die JWT-Auth-Cookies. Wird **sowohl** an `api` **als auch** an `web` durchgereicht – beide müssen denselben Wert erhalten, sonst schlägt die Anmeldung fehl. In der Basis-Compose (Dev) gibt es einen unsicheren Default (`tessera-dev-jwt-secret-change-in-production`), in Prod nicht. |
|
||||
| `TESSERA_ENCRYPTION_KEY` | ja | – | AES-256-GCM-Schlüssel (64 Hex-Zeichen) für gespeicherte Drittanbieter-Zugangsdaten. Siehe Kapitel 2. `CALENDAR_ENCRYPTION_KEY` ist der alte Variablenname und wird weiterhin akzeptiert (mit Warnung im Log). |
|
||||
| `TESSERA_ENCRYPTION_KEY` | ja | – | AES-256-GCM-Schlüssel (64 Hex-Zeichen) für gespeicherte Drittanbieter-Zugangsdaten. Siehe Kapitel 2. |
|
||||
| `CALENDAR_ENCRYPTION_KEY` | nein | leer | Alter Name von `TESSERA_ENCRYPTION_KEY` (aus der Zeit, als der Schlüssel nur den Kalender schützte). Wird nur noch aus Kompatibilität gelesen: Ist `TESSERA_ENCRYPTION_KEY` nicht gesetzt, übernimmt die API diesen Wert und schreibt beim Start eine Warnung ins Protokoll. Ist `TESSERA_ENCRYPTION_KEY` gesetzt, hat er Vorrang. Eine bestehende `.env` mit dem alten Namen funktioniert also weiter; für neue Installationen immer `TESSERA_ENCRYPTION_KEY` verwenden. |
|
||||
| `TESSERA_MIGRATE_DATABASE_URL` | nein | leer | Eigene Datenbankverbindung, die ausschließlich der Migrationsschritt beim Start des `api`-Containers benutzt (Kapitel 5). Sie ist dafür gedacht, dass die Migration mit den Rechten des Tabelleneigentümers läuft, die laufende Anwendung aber über `DATABASE_URL` mit einer rechteärmeren Rolle arbeitet. Leer (der Normalfall) heißt: Migration und Anwendung nutzen beide `DATABASE_URL`. Erst belegen, wenn die Schritte in `docs/mandantentrennung-datenbankrolle.md` vollständig abgearbeitet sind – eine voreilige Umstellung sperrt die Anwendung von ihren eigenen Daten aus. |
|
||||
| `TESSERA_ADMIN_USER` | nein | `admin` | Benutzername des initialen Super-Admin, nur beim allerersten Start relevant. |
|
||||
| `TESSERA_ADMIN_EMAIL` | ja (für den Seed) | – | E-Mail des initialen Super-Admin. |
|
||||
| `TESSERA_ADMIN_PASSWORD` | ja (für den Seed) | – | Initiales Passwort des Super-Admin. |
|
||||
| `TESSERA_FORCE_CHANGE` | nein | `true` | Erzwingt Passwortwechsel beim ersten Login des geseedeten Admin-Accounts. |
|
||||
| `TESSERA_SMTP_HOST` / `_PORT` / `_SECURE` / `_USER` / `_PASSWORD` / `_FROM` | nein (aber ohne Host kein Mailversand) | Host leer, Port `587`, `_SECURE=false` | SMTP-Relay für ausgehende Mails (Passwort-Reset, Benachrichtigungen). |
|
||||
| `TESSERA_SMTP_HOST` / `_PORT` / `_SECURE` / `_USER` / `_PASSWORD` / `_FROM` | nein (aber ohne Host kein Mailversand) | Host leer, Port `587`, `_SECURE=false`, `_FROM` = `Tessera <noreply@tessera.local>` | SMTP-Relay für ausgehende Mails (Passwort-Reset, Benachrichtigungen). Gilt nur als Rückfall: Sobald unter Administration → E-Mail-Versand (SMTP) ein Versandweg eingerichtet ist, verwendet Tessera diesen und ignoriert die Variablen. Der Standard-Absender sollte durch eine echte Adresse der Firma ersetzt werden. |
|
||||
| `MAIL_HOST` / `MAIL_PORT` / `MAIL_USER` / `MAIL_PASS` | nein | nicht gesetzt | Alte Rückfallnamen für den SMTP-Versand, die die API im Code weiterhin liest. Sie haben dort Vorrang vor den gleichnamigen `TESSERA_SMTP_*`-Werten (`MAIL_HOST` vor `TESSERA_SMTP_HOST` usw.; ohne beides greift `localhost:1025`, der Mailhog der Entwicklung). Weder die Compose-Dateien noch `.env.prod.example` reichen diese Namen an den `api`-Container durch; sie wirken also nur, wenn jemand sie von Hand in die Compose-Datei einträgt. Nutzen Sie sie nicht für neue Installationen, sondern `TESSERA_SMTP_*` oder besser die Einstellung in der Oberfläche. |
|
||||
| `TESSERA_BUGREPORT_TO` | nein | leer | Rückfall-Postfach für den Knopf „Fehler melden“ in der Kopfleiste, falls unter Administration → E-Mail-Versand (SMTP) kein Feld „Fehlermeldungen an“ gesetzt ist. Leer = nur die Einstellung in der Oberfläche gilt. Wie `IMAGE_TAG` (Kapitel 9): die Serverdatei `/opt/tessera/docker-compose.prod.yml` bekommt die Zeile `TESSERA_BUGREPORT_TO: ${TESSERA_BUGREPORT_TO:-}` nur von Hand. |
|
||||
| `APP_URL` | empfohlen | `http://localhost:3001` (für `NEXT_PUBLIC_API_URL`) / `http://localhost:3000` (für `TESSERA_APP_URL`) | Öffentliche Basis-URL der Web-Oberfläche. Wird serverseitig u. a. für in E-Mails generierte Links verwendet (`TESSERA_APP_URL`). |
|
||||
| `API_INTERNAL_URL` | fest verdrahtet | `http://api:3001` | Adresse, unter der `web` die API **innerhalb** des Docker-Netzes erreicht; dorthin schreibt Next.js die `/api-proxy/*`-Rewrites um. In der Regel nicht ändern. |
|
||||
| `CORS_ORIGIN` | nein | `http://localhost:3000` (im Code der API, in keiner Compose-Datei gesetzt) | Erlaubte Herkunftsadresse für Browser-Anfragen direkt an die API (CORS). Im Normalbetrieb ohne Wirkung, weil der Browser die API nie direkt, sondern nur über `web` und `/api-proxy` erreicht (Kapitel 1). Nur nötig, wenn jemand die API bewusst unter einer eigenen Adresse im Browser aufruft; dann muss die Variable zusätzlich in die Compose-Datei beim Dienst `api` eingetragen werden. |
|
||||
| `APP_VERSION` / `APP_CHANNEL` / `APP_COMMIT` / `APP_BUILD_TIME` | nein – nicht per `.env` setzen | im Abbild fest eingebaut | Versionsstempel von API (und Web). Sie werden beim Bau des Abbilds durch die Pipeline (`.gitea/scripts/publish-images.sh`) als Build-Argumente übergeben und im Abbild als Umgebungsvariablen abgelegt. Aus ihnen speist sich `/health/version` (Kapitel 9) und die Startzeile im Protokoll. Ein selbst gebautes Abbild ohne diese Angaben meldet als Version und Kanal `dev` und lässt Kennung und Bauzeit leer. |
|
||||
| `DASHBOARD_IMAGES_DIR` / `FAVORITE_ICONS_DIR` | nein – im Betrieb nie setzen | nicht gesetzt (Ablage unter `/app/user-files/dashboard-images` bzw. `/app/user-files/favorite-icons`) | Schalter, die die Ablage der Bilderrahmen-Bilder bzw. der Favoriten-Symbole auf ein anderes Verzeichnis umlenken. Sie existieren für Tests. Wer sie dennoch setzt, muss dafür sorgen, dass das neue Verzeichnis ebenfalls in einem Volume liegt und in die Sicherung aufgenommen wird (Kapitel 6), sonst gehen die Dateien beim Neuerstellen des Containers verloren. |
|
||||
| `IMAGE_TAG` | empfohlen | `beta` | Welcher Kanal auf diesem Server läuft: `beta` (alle Neuerungen, alpha) oder `live` (nur freigegebene Versionen, tessera.ctl.de). Siehe Kapitel 9. |
|
||||
|
||||
Hinweis zu `NEXT_PUBLIC_API_URL`: Diese Variable wird beim Image-Build bereits fest
|
||||
@@ -172,7 +185,7 @@ Compose gesetzte Wert (`${APP_URL:-http://localhost:3001}`) hat auf das bereits
|
||||
gebaute Frontend-Bundle daher keine sichtbare Wirkung mehr. Praktisch reicht es, dass
|
||||
`APP_URL` für `TESSERA_APP_URL` (serverseitig, z. B. Mail-Links) korrekt gesetzt ist.
|
||||
|
||||
<!-- VERIFY: Ob und wie CORS_ORIGIN, NODE_ENV oder weitere in main.ts gelesene Variablen auf dem tatsächlichen Server zusätzlich gesetzt sind, lässt sich aus dem Repository allein nicht feststellen. -->
|
||||
<!-- VERIFY: Ob auf dem tatsächlichen Server zusätzliche Variablen (z. B. `CORS_ORIGIN`, `NODE_ENV`) in der Compose-Datei gesetzt sind, lässt sich aus dem Repository allein nicht feststellen. -->
|
||||
|
||||
**Wichtig – Konfigurationsdrift auf dem Server:** Auf dem Produktivserver wurde die
|
||||
laufende Compose-Datei in der Vergangenheit direkt von Hand angepasst und ist damit
|
||||
@@ -202,7 +215,7 @@ Das Modul „Zertifikatsmanager“ braucht keine Einstellungen und keine eigene
|
||||
|
||||
- **Größe der Anfragen:** Die Dateien gehen über `/api-proxy` an die API. Eine einzelne Analyse schickt höchstens 10 MB (höchstens 30 Dateien, je Datei bis 5 MB). Die Voraussetzung am Proxy ist dieselbe wie im Abschnitt „Dateien (Nextcloud)“: `client_max_body_size` von mindestens `10m`. Beim Herunterladen eines Ergebnisses schickt der Browser nur Zertifikate und höchstens einen Schlüssel als JSON, höchstens 512 KiB je Anfrage (Tessera legt für genau diese Anfrage eine eigene Grenze fest, alle anderen JSON-Anfragen bleiben bei 100 kB).
|
||||
- **Ausgehender Zugriff:** Der Zertifikatsmanager ruft von sich aus nichts im Internet ab. Eine Ausnahme gibt es: Klickt ein Benutzer auf „Fehlendes Zertifikat holen“, schickt der `api`-Container eine einzelne Anfrage an die Adresse, die im Zertifikat als Aussteller-Adresse steht (meist `http://`, selten `https://`). Dafür muss der `api`-Container ausgehend per HTTP und HTTPS (Ports 80 und 443) ins Internet kommen; andere Ports ruft Tessera nie ab. Ist das ausgehend gesperrt (Firewall, Proxy-Pflicht), meldet der Knopf „nicht erreichbar“, alles andere im Modul funktioniert weiter, und die Benutzer laden das Zertifikat selbst herunter. Tessera ruft dabei nur öffentliche Adressen ab (keine internen Rechner, keine Adressen des eigenen Netzes), prüft die Adresse im Moment des Verbindens noch einmal, folgt höchstens drei Weiterleitungen, begrenzt Wartezeit (8 Sekunden) und Antwortgröße (256 KiB) und übernimmt nur ein Zertifikat, das das betroffene Zertifikat wirklich ausgestellt hat. Im Protokoll steht bei einem Fehler genau eine Zeile mit dem Servernamen und einem Fehlercode, nie ein Zertifikat.
|
||||
- **Rechenaufwand:** Passwortgeschützte PFX-Dateien und verschlüsselte Schlüssel öffnet Tessera mit den eingegebenen Passwörtern (je Datei höchstens zehn verschiedene Versuche); das kostet kurz Rechenzeit, belastet den Server aber nicht dauerhaft. Damit eine präparierte Datei die API nicht ausbremsen kann, gelten Grenzen je Anfrage: eine Passwortableitung höchstens eine Million Runden, alle zusammen höchstens sechs Millionen (mehr überspringt Tessera mit Hinweis), höchstens 200 Zertifikate, 50 Schlüssel und 50 Zertifikatsanfragen sowie 20 MiB für alle Dateien zusammen (Tessera bricht schon beim Empfang ab, mit der Meldung zu große Dateien, HTTP 413). ZIP-Dateien werden mit hartem Deckel entpackt (1 MiB je Eintrag, 20 MiB zusammen).
|
||||
- **Rechenaufwand:** Passwortgeschützte PFX-Dateien und verschlüsselte Schlüssel öffnet Tessera mit den eingegebenen Passwörtern (je Datei höchstens zehn verschiedene Versuche); das kostet kurz Rechenzeit, belastet den Server aber nicht dauerhaft. Damit eine präparierte Datei die API nicht ausbremsen kann, gelten Grenzen je Anfrage: eine Passwortableitung höchstens eine Million Runden, alle zusammen höchstens sechs Millionen (mehr überspringt Tessera mit Hinweis), höchstens 200 Zertifikate, 50 Schlüssel und 50 Zertifikatsanfragen sowie 20 MiB für alle Dateien zusammen (Tessera bricht schon beim Empfang ab, mit der Meldung zu große Dateien, HTTP 413). ZIP-Dateien werden mit hartem Deckel entpackt (1 MiB je Eintrag, 20 MiB zusammen, höchstens 100 Einträge je ZIP und ein Kompressionsverhältnis von höchstens 100 : 1 je Eintrag); ein ZIP mit mehr Einträgen wird ganz abgelehnt, ein Eintrag mit auffälligem Verhältnis wird als verdächtig übersprungen und im Ergebnis gemeldet. Ordner, `__MACOSX`, versteckte Dateien (Name mit Punkt am Anfang), `Thumbs.db` und `desktop.ini` zählen nicht mit.
|
||||
|
||||
## 4. Neue Fassung einspielen
|
||||
|
||||
@@ -275,16 +288,22 @@ durchgespielt.)
|
||||
|
||||
Ein separater Migrationsschritt ist **nicht** nötig. Der `api`-Container führt
|
||||
Prisma-Migrationen bei jedem Start automatisch aus, bevor der eigentliche Prozess
|
||||
hochfährt (`apps/api/Dockerfile`, `CMD`):
|
||||
hochfährt. Der Startbefehl des Images (`apps/api/Dockerfile`, `CMD`) ruft dafür das
|
||||
Skript `apps/api/scripts/migrate-and-start.sh` auf. Es arbeitet in drei Schritten:
|
||||
|
||||
```
|
||||
prisma migrate deploy --schema apps/api/prisma/schema.prisma && node apps/api/dist/main.js
|
||||
```
|
||||
1. Es prüft, dass `DATABASE_URL` gesetzt ist. Fehlt sie, bricht der Container sofort
|
||||
mit der Meldung „FEHLER: DATABASE_URL ist nicht gesetzt.“ ab.
|
||||
2. Es führt `prisma migrate deploy --schema apps/api/prisma/schema.prisma` aus. Als
|
||||
Verbindung dient `TESSERA_MIGRATE_DATABASE_URL`, falls diese Variable gesetzt ist
|
||||
(Kapitel 3); ist sie leer oder fehlt sie, nimmt das Skript `DATABASE_URL`.
|
||||
3. Erst wenn die Migration erfolgreich war, startet es die API mit
|
||||
`exec node apps/api/dist/main.js`. Die API selbst arbeitet immer mit
|
||||
`DATABASE_URL`.
|
||||
|
||||
Das heisst: Sobald ein neues Image (mit neuen Migrationen im Ordner
|
||||
`apps/api/prisma/migrations/`) per `pull` + `--force-recreate` eingespielt wird,
|
||||
laufen ausstehende Migrationen beim nächsten Start automatisch. Schlägt eine
|
||||
Migration fehl, startet `node apps/api/dist/main.js` erst gar nicht – der Container
|
||||
Migration fehl, startet die API (Schritt 3) erst gar nicht – der Container
|
||||
bleibt dann im Neustart-Loop bzw. terminiert, sichtbar in `docker compose logs api`.
|
||||
|
||||
Kontrolle, ob eine Migration tatsächlich angekommen ist:
|
||||
@@ -393,6 +412,7 @@ startet `web`, weil `depends_on: api: condition: service_healthy` das erzwingt.
|
||||
|---|---|---|
|
||||
| `docker compose up` bricht sofort ab, ohne dass ein Container startet, mit einer Meldung zu `TESSERA_ENCRYPTION_KEY` | Variable fehlt in `.env` – Compose selbst verweigert die Variablen-Interpolation (`${TESSERA_ENCRYPTION_KEY:?...}`) | `TESSERA_ENCRYPTION_KEY` setzen (siehe Kapitel 2), danach erneut starten. |
|
||||
| `api`-Container startet und beendet sich sofort wieder, Log zeigt `TESSERA_ENCRYPTION_KEY is not set` oder `must be a 64-character hex string` | Key ist zwar irgendwo gesetzt, aber leer oder falsch lang | Mit `openssl rand -hex 32` neu erzeugen, Länge prüfen (64 Zeichen). |
|
||||
| `api`-Container beendet sich sofort, Log zeigt `FEHLER: DATABASE_URL ist nicht gesetzt.` | `DATABASE_URL` fehlt oder ist leer; das Startskript `apps/api/scripts/migrate-and-start.sh` bricht ab, bevor Prisma sich verbindet (Kapitel 5) | `DATABASE_URL` in der `.env` setzen (Form siehe Kapitel 3) und den `api`-Container neu erstellen (`--force-recreate api`). |
|
||||
| `api` wird nie `healthy`, Log zeigt Verbindungsfehler von Prisma/PostgreSQL | `db` ist noch nicht bereit, `DATABASE_URL` falsch, oder `DB_PASSWORD`/`DATABASE_URL` passen nicht zusammen | `docker compose ps` – ist `db` `healthy`? `DATABASE_URL` gegen `DB_PASSWORD` abgleichen. |
|
||||
| Login funktioniert nicht, obwohl `api` und `web` laufen | `JWT_SECRET` bei `web` und `api` unterschiedlich, z. B. nach halbherzigem Neustart nur eines Containers | Beide Container mit demselben `JWT_SECRET` neu erstellen (`--force-recreate api web`). |
|
||||
| Neue Version scheint nicht anzukommen, obwohl `pull` gelaufen ist | Klassische `up -d`-Falle ohne `--force-recreate` (siehe Kapitel 4) | `StartedAt` des Containers gegen `Created` des Images vergleichen, ggf. `--force-recreate` nachholen. |
|
||||
@@ -582,6 +602,11 @@ Vier Wege, vom einfachsten zum genauesten:
|
||||
|
||||
Die Antwort enthält die Felder `version`, `channel` (`beta` oder `live`),
|
||||
`commit` (Kurzkennung) und `buildTime` (wann das Image gebaut wurde).
|
||||
Diese Angaben stammen aus den Umgebungsvariablen `APP_VERSION`, `APP_CHANNEL`,
|
||||
`APP_COMMIT` und `APP_BUILD_TIME`, die die Pipeline beim Bau des Images fest
|
||||
einträgt (`.gitea/scripts/publish-images.sh`); sie werden nicht über die `.env`
|
||||
gesetzt. Ein Image, das jemand von Hand ohne diese Angaben gebaut hat, meldet
|
||||
deshalb `dev` als Version und Kanal und leere Felder für Kennung und Bauzeit.
|
||||
3. **Im Protokoll:**
|
||||
|
||||
```bash
|
||||
|
||||
Reference in New Issue
Block a user