8cbfb8b69d
- Changelog unter Unveroeffentlicht -> Geaendert: Bilder liegen im Dateibereich, vorhandene ziehen beim ersten Start automatisch um - Betriebsanleitung Kap. 6: user-files nennt die Bilderrahmen-Bilder und haelt fest, dass pg_dump allein sie nicht mehr enthaelt - Todo fuer Stufe 2 (DROP data, storagePath NOT NULL) mit Vorbedingung, Migrationsname und den Nacharbeiten an Spec und Klassifikation Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
718 lines
41 KiB
Markdown
718 lines
41 KiB
Markdown
<!-- generated-by: gsd-doc-writer -->
|
||
# Tessera – Betriebshandbuch
|
||
|
||
Anleitung für den laufenden Betrieb von Tessera: Erstinstallation, neue Fassungen
|
||
einspielen, Datenbank-Migrationen, Sicherung/Wiederherstellung und Fehlersuche.
|
||
Richtet sich an Kolleg:innen, die die Docker-Container auf dem Server betreiben.
|
||
|
||
Für den Aufbau der CI/CD-Pipeline (Gitea Actions, act_runner) siehe
|
||
[`docs/ci-cd-setup.md`](./ci-cd-setup.md) – dieses Dokument behandelt nur den
|
||
Betrieb der bereits laufenden Installation, nicht deren automatisierten Build.
|
||
|
||
## Inhaltsverzeichnis
|
||
|
||
1. [Überblick der Dienste](#1-überblick-der-dienste)
|
||
2. [Erstinstallation](#2-erstinstallation)
|
||
3. [Konfiguration](#3-konfiguration)
|
||
4. [Neue Fassung einspielen](#4-neue-fassung-einspielen)
|
||
5. [Datenbank-Migrationen](#5-datenbank-migrationen)
|
||
6. [Sicherung und Wiederherstellung](#6-sicherung-und-wiederherstellung)
|
||
7. [Protokolle und Fehlersuche](#7-protokolle-und-fehlersuche)
|
||
8. [Abgrenzung zur CI/CD-Pipeline](#8-abgrenzung-zur-cicd-pipeline)
|
||
9. [Zwei Kanäle: Live und Beta](#9-zwei-kanäle-live-und-beta)
|
||
10. [Desktop-App: Pakete und Release-Dateien](#10-desktop-app-pakete-und-release-dateien)
|
||
|
||
---
|
||
|
||
## 1. Überblick der Dienste
|
||
|
||
Tessera besteht aus drei Containern, definiert in `docker-compose.yml` (Basis) und
|
||
`docker-compose.prod.yml` (Produktionsvariante mit fertigen Images statt lokalem Build):
|
||
|
||
| Dienst | Image / Build | Host-Port | Zweck |
|
||
|--------|---------------|-----------|-------|
|
||
| `web` | `apps/web/Dockerfile` (Basis) bzw. `git.vicolab.de/schalli/tessera-ctl/web:${IMAGE_TAG}` (Prod; `beta` oder `live`, siehe Kapitel 9) | 3000 | Next.js-Frontend (Portal, Dashboard) |
|
||
| `api` | `apps/api/Dockerfile` (Basis) bzw. `git.vicolab.de/schalli/tessera-ctl/api:${IMAGE_TAG}` (Prod; `beta` oder `live`, siehe Kapitel 9) | 3001 | NestJS-Backend (REST-API, Prisma/PostgreSQL-Zugriff) |
|
||
| `db` | `postgres:16-alpine` | kein Host-Port | PostgreSQL-Datenbank |
|
||
|
||
Zusätzlich existiert `docker-compose.dev.yml` (Bind-Mounts für Live-Reload,
|
||
Mailhog, OpenLDAP, phpLDAPadmin) für die lokale Entwicklung sowie
|
||
`docker-compose.ci.yml` für den Gitea `act_runner` – beide sind für den
|
||
Produktivbetrieb ohne Belang und werden hier nicht weiter behandelt.
|
||
|
||
**Netzwerke** (siehe `docker-compose.yml`):
|
||
|
||
- `frontend-net` – nur `web`, darüber ist der Browser-Zugriff erreichbar (Port 3000).
|
||
- `backend-net` – `web` und `api`, Kommunikation zwischen Frontend-Server und Backend.
|
||
- `data-net` – `api` und `db`, als `internal: true` markiert. Die Datenbank ist damit
|
||
aus dem Host-Netzwerk grundsätzlich nicht erreichbar; Zugriff nur über
|
||
`docker compose exec db ...` von innen.
|
||
|
||
**Startreihenfolge:** `db` → `api` → `web`, erzwungen über `depends_on` mit
|
||
`condition: service_healthy`:
|
||
|
||
- `db`: Healthcheck `pg_isready -U tessera` (Intervall 5 s, 5 Versuche).
|
||
- `api`: Healthcheck `wget --spider http://localhost:3001/health` (Intervall 10 s,
|
||
3 Versuche, `start_period` 10 s in der Basis- / 20 s in der Prod-Variante). Der
|
||
`/health`-Endpunkt (`apps/api/src/health/health.controller.ts`) prüft nur, dass
|
||
der NestJS-Prozess antwortet – **keine** Datenbankverbindung. Ein "healthy"
|
||
API-Container sagt also nichts darüber aus, ob Prisma tatsächlich verbunden ist.
|
||
- `web`: hat selbst **keinen** Healthcheck. `docker compose ps` zeigt `web` daher nie
|
||
als "healthy" an, nur als laufend – das ist normal.
|
||
|
||
Der Browser spricht ausschließlich mit `web` (Port 3000). Aufrufe unter
|
||
`/api-proxy/*` werden von Next.js serverseitig auf `API_INTERNAL_URL`
|
||
(`http://api:3001` im Docker-Netz) umgeschrieben (siehe `apps/web/next.config.ts`).
|
||
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).
|
||
|
||
## 2. Erstinstallation
|
||
|
||
Voraussetzung: Docker und Docker Compose (Plugin, `docker compose ...`) sind auf dem
|
||
Zielsystem installiert.
|
||
|
||
1. Repository auf den Server holen:
|
||
|
||
```bash
|
||
git clone <gitea-url>/schalli/tessera-ctl.git
|
||
cd tessera-ctl
|
||
```
|
||
|
||
2. `.env` aus der Vorlage anlegen:
|
||
|
||
```bash
|
||
cp .env.prod.example .env
|
||
```
|
||
|
||
Die Vorlage `.env.prod.example` enthält die für den Produktivbetrieb relevanten
|
||
Variablennamen; `.env.example` ist die schlankere Entwicklungsvariante. Welche
|
||
Variablen zu setzen sind, steht in Kapitel [3. Konfiguration](#3-konfiguration).
|
||
Mindestens erforderlich, bevor irgendetwas startet:
|
||
|
||
- `DB_PASSWORD` und dazu passend `DATABASE_URL`
|
||
- `JWT_SECRET`
|
||
- `TESSERA_ADMIN_EMAIL`, `TESSERA_ADMIN_PASSWORD`
|
||
- `TESSERA_ENCRYPTION_KEY` (siehe Warnhinweis unten – ohne diesen Wert startet
|
||
nichts)
|
||
|
||
3. Encryption-Key erzeugen und eintragen:
|
||
|
||
```bash
|
||
openssl rand -hex 32
|
||
```
|
||
|
||
Das Ergebnis (64 Hex-Zeichen) als `TESSERA_ENCRYPTION_KEY=` in die `.env`
|
||
eintragen. Dieser Schlüssel verschlüsselt alle bei Tessera hinterlegten
|
||
Zugangsdaten (LDAP-Bind-Passwort, SMTP, Kalender- sowie DKV-/Ausschreibungs-Postfächer).
|
||
**Ohne diesen Wert startet die API nicht** – `docker-compose.prod.yml` verwendet
|
||
die Compose-Syntax `${TESSERA_ENCRYPTION_KEY:?...}`, wodurch bereits `docker
|
||
compose` selbst mit einer Fehlermeldung abbricht, bevor ein Container startet,
|
||
falls die Variable fehlt. Zusätzlich prüft `CryptoService`
|
||
(`apps/api/src/crypto/crypto.service.ts`) beim Hochfahren der API die Länge
|
||
(muss genau 64 Hex-Zeichen sein) und wirft andernfalls einen Fehler.
|
||
|
||
Diesen Schlüssel getrennt von jedem Datenbank-Dump aufbewahren (siehe Kapitel 6)
|
||
– geht er verloren, sind alle gespeicherten Zugangsdaten unwiderruflich
|
||
unbrauchbar und müssen von Hand neu eingegeben werden.
|
||
|
||
4. Container starten:
|
||
|
||
```bash
|
||
docker compose -f docker-compose.prod.yml pull
|
||
docker compose -f docker-compose.prod.yml up -d
|
||
```
|
||
|
||
Beim ersten Start seedet die API (`AdminSeedService`,
|
||
`apps/api/src/user/admin-seed.service.ts`) automatisch den initialen
|
||
Super-Admin-Account aus `TESSERA_ADMIN_USER` / `_EMAIL` / `_PASSWORD` sowie
|
||
den Mandanten `default`. Das passiert nur, wenn noch **kein** Benutzer mit
|
||
diesem Nutzernamen existiert – bei einer bereits befüllten Datenbank wird der
|
||
Seed-Schritt stillschweigend übersprungen, auch wenn sich die Passwort-Variable
|
||
in der `.env` danach ändert.
|
||
|
||
5. Prüfen, ob alles läuft:
|
||
|
||
```bash
|
||
docker compose -f docker-compose.prod.yml ps
|
||
curl -s http://localhost:3001/health
|
||
```
|
||
|
||
`api` sollte als `healthy` markiert sein, die `/health`-Abfrage liefert
|
||
`{"status":"ok",...}`. Danach ist die Oberfläche unter Port 3000 erreichbar.
|
||
|
||
## 3. Konfiguration
|
||
|
||
Alle Variablen werden über `.env` im Projektverzeichnis eingelesen (Docker Compose
|
||
liest diese Datei automatisch). Werte unten sind **Platzhalter**, keine echten
|
||
Zugangsdaten.
|
||
|
||
| Variable | Pflicht in Prod? | Default (Prod-Compose) | Zweck |
|
||
|----------|:---:|---|---|
|
||
| `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_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_BUGREPORT_TO` | nein | leer | Rückfall-Postfach für den Knopf „Fehler melden“ in der Kopfleiste, falls unter Administrator → 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. |
|
||
| `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
|
||
auf `/api-proxy` gesetzt (`ENV NEXT_PUBLIC_API_URL=/api-proxy` in
|
||
`apps/web/Dockerfile`, vor `pnpm build`). Next.js baut `NEXT_PUBLIC_*`-Variablen zur
|
||
Build-Zeit in das an den Browser ausgelieferte JavaScript ein; der zur Laufzeit über
|
||
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. -->
|
||
|
||
**Wichtig – Konfigurationsdrift auf dem Server:** Auf dem Produktivserver wurde die
|
||
laufende Compose-Datei in der Vergangenheit direkt von Hand angepasst und ist damit
|
||
nicht mehr zwingend identisch mit `docker-compose.prod.yml` in diesem Repository.
|
||
Änderungen an `docker-compose*.yml` im Git-Repo wirken sich **nicht** automatisch
|
||
auf die laufende Installation aus – der Deploy-Schritt zieht ausschließlich neue
|
||
Images, die Compose-Datei selbst muss bei Bedarf separat auf dem Server aktualisiert
|
||
werden. Vor grösseren Änderungen an der Compose-Struktur die auf dem Server
|
||
tatsächlich liegende Datei prüfen, nicht blind von der Repo-Version ausgehen.
|
||
|
||
## 4. Neue Fassung einspielen
|
||
|
||
Das ist der wichtigste Ablauf im Tagesgeschäft. Zwei Befehle:
|
||
|
||
```bash
|
||
docker compose -f docker-compose.prod.yml pull
|
||
docker compose -f docker-compose.prod.yml up -d --force-recreate api web
|
||
```
|
||
|
||
**Verifizierte Falle:** `docker compose up -d` **ohne** `--force-recreate` ersetzt
|
||
einen bereits laufenden Container **nicht**, wenn Compose der Meinung ist, an der
|
||
Service-Definition habe sich nichts geändert – auch wenn `pull` gerade ein neues
|
||
Image unter demselben Etikett (`beta` bzw. `live`) heruntergeladen hat. Der alte Container läuft
|
||
dann unverändert mit dem alten Code weiter, ohne Fehlermeldung. Das Deployment
|
||
wirkt erfolgreich, ist es aber nicht. Dieser Fehler ist dem Team schon mehrfach
|
||
passiert. Deshalb: nach jedem `pull` **immer** `--force-recreate` verwenden (nur
|
||
`api` und `web` betrifft das – `db` soll normalerweise nicht neu erstellt werden,
|
||
sonst würde sie kurz neu starten).
|
||
|
||
**Kontrolle, ob es wirklich funktioniert hat:** Startzeitpunkt des Containers mit dem
|
||
Erstellungszeitpunkt des zugehörigen Images vergleichen. Der Container muss
|
||
**nach** dem Image erzeugt worden sein:
|
||
|
||
```bash
|
||
docker inspect -f '{{.State.StartedAt}}' tessera-api-1
|
||
docker image inspect -f '{{.Created}}' git.vicolab.de/schalli/tessera-ctl/api:beta
|
||
|
||
docker inspect -f '{{.State.StartedAt}}' tessera-web-1
|
||
docker image inspect -f '{{.Created}}' git.vicolab.de/schalli/tessera-ctl/web:beta
|
||
```
|
||
|
||
(Auf dem Live-Server statt `:beta` jeweils `:live` einsetzen – das Etikett, das in
|
||
der `.env` als `IMAGE_TAG` steht, siehe Kapitel 9.)
|
||
|
||
(Container-Namen mit `docker compose -f docker-compose.prod.yml ps` prüfen, falls
|
||
sie auf dem Server abweichen.) Liegt `StartedAt` **vor** `Created` des Images, läuft
|
||
noch die alte Version – dann `--force-recreate` nachholen.
|
||
|
||
## 5. Datenbank-Migrationen
|
||
|
||
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`):
|
||
|
||
```
|
||
prisma migrate deploy --schema apps/api/prisma/schema.prisma && node apps/api/dist/main.js
|
||
```
|
||
|
||
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
|
||
bleibt dann im Neustart-Loop bzw. terminiert, sichtbar in `docker compose logs api`.
|
||
|
||
Kontrolle, ob eine Migration tatsächlich angekommen ist:
|
||
|
||
```bash
|
||
docker compose -f docker-compose.prod.yml exec db \
|
||
psql -U tessera -d tessera -c \
|
||
"SELECT migration_name, finished_at FROM _prisma_migrations ORDER BY finished_at DESC LIMIT 5;"
|
||
```
|
||
|
||
Der Name der zuletzt erwarteten Migration lässt sich mit dem Ordnernamen in
|
||
`apps/api/prisma/migrations/` abgleichen (Namensschema
|
||
`YYYYMMDDHHMMSS_beschreibung`).
|
||
|
||
## 6. Sicherung und Wiederherstellung
|
||
|
||
Es gibt aktuell **kein** eingebautes Backup-Skript und kein Makefile-Target dafür im
|
||
Repository. Sicherung erfolgt manuell über die Postgres-Bordmittel.
|
||
|
||
### Datenbank sichern
|
||
|
||
```bash
|
||
docker compose -f docker-compose.prod.yml exec db \
|
||
pg_dump -U tessera -d tessera -F c -f /tmp/tessera-$(date +%Y%m%d).dump
|
||
docker compose -f docker-compose.prod.yml cp db:/tmp/tessera-$(date +%Y%m%d).dump ./
|
||
```
|
||
|
||
### Datenbank wiederherstellen
|
||
|
||
```bash
|
||
docker compose -f docker-compose.prod.yml cp ./tessera-YYYYMMDD.dump db:/tmp/restore.dump
|
||
docker compose -f docker-compose.prod.yml exec db \
|
||
pg_restore -U tessera -d tessera --clean --if-exists /tmp/restore.dump
|
||
```
|
||
|
||
Vor einer Wiederherstellung `api` stoppen, damit keine Schreibzugriffe während des
|
||
Restores stattfinden.
|
||
|
||
### Was sonst noch an Zustand existiert
|
||
|
||
- **PostgreSQL-Daten**: liegen im benannten Docker-Volume `pgdata`
|
||
(`docker-compose.prod.yml`, Mount `pgdata:/var/lib/postgresql/data` im
|
||
`db`-Container). `pgdata` ist eines von zwei dauerhaften Docker-Volumes (siehe
|
||
nächster Punkt) und kann zusätzlich über ein `docker volume`-Backup gesichert
|
||
werden.
|
||
- **Verschlüsselungsschlüssel** `TESSERA_ENCRYPTION_KEY`: liegt nur in `.env` auf
|
||
dem Host, **nicht** im Datenbank-Dump. Getrennt sichern (siehe Kapitel 2/3) – ohne
|
||
ihn sind alle per `pg_dump` gesicherten verschlüsselten Zugangsdaten wertlos.
|
||
- **Hochgeladene Dateien** (Avatare unter `user-files/avatars/`, Bilder des
|
||
Bilderrahmen-Widgets unter `user-files/dashboard-images/<Benutzerkennung>/`,
|
||
generierte DKV-Exporte unter `user-files/`, siehe
|
||
`apps/api/src/user/user.controller.ts`,
|
||
`apps/api/src/dashboard/dashboard-images.service.ts` und
|
||
`apps/api/src/dkv/dkv-export.service.ts`): Diese Dateien liegen im benannten
|
||
Docker-Volume `user-files`, gemountet auf `/app/user-files` im Dienst `api`. Der
|
||
Mount ist in `docker-compose.yml` und `docker-compose.prod.yml` eingetragen:
|
||
|
||
```yaml
|
||
# beim Dienst api:
|
||
- user-files:/app/user-files
|
||
# im Top-Level-Block volumes:
|
||
user-files:
|
||
```
|
||
|
||
Damit überstehen Avatare, Bilderrahmen-Bilder und DKV-Exporte ein
|
||
`--force-recreate` von `api`. Seit den Bilderrahmen-Bildern (Version nach 1.3.0)
|
||
gehört dieses Volume zwingend zur Sicherung: `pg_dump` allein enthält diese
|
||
Bilder nicht mehr — genau das ist der Zweck der Umstellung, der
|
||
Datenbank-Abzug bleibt dadurch klein. Wie bei
|
||
`pgdata` zeigt `docker volume ls` das Volume mit vorangestelltem Projektnamen an
|
||
(`<projekt>_user-files`). Gesichert werden die Dateien weiterhin mit
|
||
`docker compose cp api:/app/user-files ./user-files-backup`, alternativ über eine
|
||
Sicherung des Docker-Volumes selbst (wie bei `pgdata`).
|
||
|
||
**Wichtig für den Betrieb auf einem bestehenden Server:** `/opt/tessera/docker-compose.yml`
|
||
ist keine Arbeitskopie dieses Repositorys – die Datei wurde dort von Hand
|
||
bearbeitet und weicht ab. Ein Deploy holt ausschließlich Images und fasst diese
|
||
Datei nicht an. Diese Compose-Änderung erreicht eine bereits laufende Installation
|
||
deshalb **nicht von selbst**. Wer die Reparatur dort haben will, trägt dieselben
|
||
zwei Zeilen selbst in `/opt/tessera/docker-compose.yml` ein (vorher sichern) und
|
||
erstellt die Container einmal neu. Bis dahin gilt für die laufende Installation
|
||
weiterhin der alte, verlustbehaftete Zustand – prüfbar mit `docker inspect
|
||
tessera-api-1` und einem Blick auf `Mounts`.
|
||
|
||
## 7. Protokolle und Fehlersuche
|
||
|
||
Logs ansehen:
|
||
|
||
```bash
|
||
docker compose -f docker-compose.prod.yml logs -f api
|
||
docker compose -f docker-compose.prod.yml logs -f web
|
||
docker compose -f docker-compose.prod.yml logs -f db
|
||
```
|
||
|
||
**Gesunder Start sieht so aus:** `db` wird `healthy`, danach startet `api` und
|
||
protokolliert die durchgelaufenen Prisma-Migrationen sowie zuletzt
|
||
`Tessera API running on port 3001` und direkt darunter eine Zeile wie
|
||
`Tessera API v1.0.0 (live) abc1234` (beides aus `apps/api/src/main.ts`) – Version,
|
||
Kanal und Kurzkennung des Standes, der gerade läuft (siehe Kapitel 9). Erst danach
|
||
startet `web`, weil `depends_on: api: condition: service_healthy` das erzwingt.
|
||
|
||
| Symptom | Wahrscheinliche Ursache | Prüfen / Beheben |
|
||
|---|---|---|
|
||
| `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` 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. |
|
||
| Initialer Admin-Login funktioniert nicht nach Änderung von `TESSERA_ADMIN_PASSWORD` | Seed läuft nur, wenn der Benutzername noch **nicht** existiert; bestehende Accounts werden nicht überschrieben | Passwort über die Anwendung selbst (bzw. direkt in der Datenbank) ändern, nicht über die `.env`-Variable. |
|
||
| Mails werden nicht versendet | `TESSERA_SMTP_HOST` leer (Prod-Default) | SMTP-Variablen vollständig setzen und Container neu erstellen. |
|
||
| Fehlermeldungen der Anwender kommen nicht an | Kein Postfach gesetzt (weder „Fehlermeldungen an“ unter Administrator → SMTP noch `TESSERA_BUGREPORT_TO`), oder der SMTP-Versand des Mandanten scheitert | Feld „Fehlermeldungen an“ (Administrator → SMTP) oder `TESSERA_BUGREPORT_TO` prüfen; API-Log nach `Bug report` durchsuchen (eine Zeile je gesendeter Meldung mit dem Herkunfts-Kürzel `[Browser]`, `[Desktop/Windows]` oder `[Desktop/Linux]`, `Bug report mail failed` bei Versandfehler). |
|
||
| Avatare/DKV-Exporte nach einem Deploy verschwunden | Die verwendete Compose-Datei mountet `user-files/` nicht als Volume – im Repository-Stand seit dieser Version behoben, betrifft nur eine Installation mit abweichender Compose-Datei | Die zwei Zeilen aus Kapitel 6 in die verwendete Compose-Datei eintragen (auf dem Server: `/opt/tessera/docker-compose.yml`, vorher sichern) und `api` neu erstellen. |
|
||
|
||
## 8. Abgrenzung zur CI/CD-Pipeline
|
||
|
||
Dieses Dokument beschreibt den manuellen Betrieb: Erstinstallation, Redeploy,
|
||
Migrations-Kontrolle, Backup und Fehlersuche auf einer bereits eingerichteten
|
||
Installation.
|
||
|
||
Wie die Gitea-Actions-Pipeline (Runner-Registrierung, `.gitea/workflows/ci.yml`,
|
||
Berechtigungen des `act_runner`-Docker-Socket-Mounts) aufgesetzt wird, ist bewusst
|
||
nicht Teil dieses Dokuments – das steht vollständig in
|
||
[`docs/ci-cd-setup.md`](./ci-cd-setup.md). Die Grenze zwischen beiden Dokumenten:
|
||
Sobald ein neues Image lokal vorliegt oder in einer Registry verfügbar ist, beginnt
|
||
dieses Betriebshandbuch (Kapitel 4 und 9); alles davor – wie das Image entsteht –
|
||
gehört in das CI/CD-Runbook.
|
||
|
||
## 9. Zwei Kanäle: Live und Beta
|
||
|
||
Seit September 2026 gibt es Tessera in zwei Ausgaben, die getrennt voneinander
|
||
laufen. Dieses Kapitel erklärt, was das bedeutet, welche Zeile auf welchem Server
|
||
stehen muss, wie eine Version freigegeben wird, wie ein dringender Fehler auf Live
|
||
behoben wird, und wie Sie jederzeit sehen, welche Fassung gerade läuft.
|
||
|
||
### Was ein Kanal ist
|
||
|
||
Ein Kanal ist eine Ausgabe von Tessera, die auf einem bestimmten Server läuft und
|
||
nach eigenen Regeln neue Stände bekommt. Es gibt zwei:
|
||
|
||
- **Beta** – alles Neue, sofort nach jeder Änderung. Läuft unter
|
||
`alpha.tessera.ctl.de`. Das Etikett (die Kennzeichnung des Docker-Images in der
|
||
Registry) heißt `beta`. Das ältere Etikett `latest` ist nur ein zweiter Name für
|
||
genau dasselbe Beta-Image; es bleibt vorerst bestehen, damit nichts kaputtgeht,
|
||
und kann später wegfallen.
|
||
- **Live** – nur freigegebene Versionen mit einer Nummer. Läuft unter
|
||
`tessera.ctl.de` auf dem neuen Server. Das Etikett heißt `live`; zusätzlich trägt
|
||
jede freigegebene Version ihre Nummer als eigenes Etikett (`v1.0.0`, `v1.0.1`, …),
|
||
damit man jederzeit auch einen älteren Stand gezielt holen kann.
|
||
|
||
Die Versionsnummer kommt aus der Freigabe (in Git heißt das „Tag“ – eine Markierung
|
||
an einem bestimmten Stand), nicht aus einer Datei im Code. Zwischen zwei Freigaben
|
||
zeigt die Beta eine Kennung wie `v1.0.0-12-gabc1234`: das bedeutet „12 Änderungen
|
||
nach Version 1.0.0, Stand abc1234“. Vor der allerersten Freigabe steht dort nur die
|
||
Kurzkennung des Standes (sieben Zeichen, z. B. `abc1234`).
|
||
|
||
### Die eine Zeile je Server
|
||
|
||
Welchen Kanal ein Server bekommt, entscheidet **eine einzige Zeile** in der Datei
|
||
`/opt/tessera/.env`:
|
||
|
||
- auf **alpha** (Beta): `IMAGE_TAG=beta`
|
||
- auf dem **neuen Live-Server**: `IMAGE_TAG=live`
|
||
|
||
Fehlt die Zeile ganz, nimmt die Compose-Datei von selbst `beta`. Für den Live-Server
|
||
ist die Zeile also Pflicht, sonst zieht er die Beta.
|
||
|
||
Weil `/opt/tessera` keine Arbeitskopie des Repositorys ist (siehe Kapitel 3,
|
||
„Konfigurationsdrift“), muss die Compose-Datei auf dem Server einmal von Hand
|
||
angepasst werden. Auf alpha ist das die Datei `/opt/tessera/docker-compose.prod.yml`
|
||
(die `.env` dort verweist mit `COMPOSE_FILE` auf sie). Zuerst eine Sicherung:
|
||
|
||
```bash
|
||
cd /opt/tessera
|
||
cp docker-compose.prod.yml docker-compose.prod.yml.bak.$(date +%Y%m%d)
|
||
```
|
||
|
||
Dann die zwei `image:`-Zeilen (eine beim Dienst `web`, eine beim Dienst `api`) auf
|
||
diese Form bringen – der einzige Unterschied zu heute ist das Ende der Zeile:
|
||
|
||
```yaml
|
||
image: git.vicolab.de/schalli/tessera-ctl/web:${IMAGE_TAG:-beta}
|
||
```
|
||
|
||
```yaml
|
||
image: git.vicolab.de/schalli/tessera-ctl/api:${IMAGE_TAG:-beta}
|
||
```
|
||
|
||
Danach wie in Kapitel 4:
|
||
|
||
```bash
|
||
docker compose -f docker-compose.prod.yml pull
|
||
docker compose -f docker-compose.prod.yml up -d --force-recreate api web
|
||
```
|
||
|
||
Hinweis: Die Vorlage `.env.prod.example` im Repository enthält die Zeilen
|
||
`IMAGE_TAG=live` und `COMPOSE_FILE=docker-compose.prod.yml` bereits. Wer eine neue
|
||
`.env` aus der Vorlage anlegt, setzt `IMAGE_TAG` nur noch auf den gewünschten
|
||
Kanal; auf einer älteren, von Hand gepflegten `.env` (wie auf alpha) werden die
|
||
Zeilen einmal ergänzt.
|
||
|
||
### Eine Version freigeben
|
||
|
||
Das Freigeben erledigt Claude; Sie sagen nur „Version X freigeben“. Zur Einordnung,
|
||
was dabei passiert:
|
||
|
||
1. **Änderungsliste abschließen:** In `CHANGELOG.md` wird der Abschnitt
|
||
„Unveröffentlicht“ in „X.Y.Z – JJJJ-MM-TT“ umbenannt, darüber ein neues, leeres
|
||
„Unveröffentlicht“ angelegt, und das Ganze auf `main` committet und gepusht.
|
||
Erst dann wird zusammengeführt und getaggt:
|
||
|
||
```bash
|
||
git checkout live
|
||
git merge --ff-only main
|
||
git tag -a vX.Y.Z -m "Tessera X.Y.Z"
|
||
git push origin live vX.Y.Z
|
||
```
|
||
|
||
Der zweite Befehl übernimmt den Stand der Beta in den Live-Zweig. Wenn er sich
|
||
weigert, ist eine frühere Korrektur (siehe Hotfix, Schritt 5) noch nicht zurück in
|
||
`main` – dann wird erst das nachgeholt. Der Push löst die Pipeline zweimal aus: der
|
||
Zweig `live` wird nur geprüft, der Tag `vX.Y.Z` wird gebaut und als `live` und
|
||
`vX.Y.Z` abgelegt. Das dauert etwa vier bis sechs Minuten.
|
||
|
||
Derselbe Tag baut zusätzlich die Desktop-Pakete (Windows-Installer und
|
||
Linux-AppImage) und hängt beide als Dateien an denselben Release an – Details
|
||
dazu in [Kapitel 10](#10-desktop-app-pakete-und-release-dateien).
|
||
|
||
Beim Tag legt die Pipeline zusätzlich einen **Release in Gitea** an: Name
|
||
„Tessera X.Y.Z”, Text ist der Abschnitt dieser Version aus `CHANGELOG.md`. Sie
|
||
finden ihn im Repository unter „Releases”. Fehlt der Abschnitt in der
|
||
Änderungsliste, schlägt genau dieser letzte Schritt fehl – die Abbilder sind dann
|
||
trotzdem gebaut und abgelegt. Der Release wird nachgeholt, sobald der Abschnitt
|
||
nachgetragen ist: entweder durch erneutes Auslösen des Tag-Laufs oder lokal per
|
||
Skript (`.gitea/scripts/publish-release.sh --tag vX.Y.Z`).
|
||
|
||
Danach spielen Sie die Version auf dem Live-Server ein – Kapitel 4 gilt unverändert:
|
||
|
||
```bash
|
||
docker compose -f docker-compose.prod.yml pull
|
||
docker compose -f docker-compose.prod.yml up -d --force-recreate api web
|
||
```
|
||
|
||
**Erstfreigabe v1.0.0:** Die erste Freigabe ist erfolgt. Der Zweig `live`
|
||
entstand am 2026-09-14 aus `main` (`git checkout -b live main`), bekam den Tag
|
||
`v1.0.0` und wurde zusammen mit dem Tag gepusht; seit 2026-09-15 läuft diese
|
||
Version auf dem Live-Server. Der Release „Tessera 1.0.0“ in Gitea wurde
|
||
nachträglich mit dem Skript angelegt, weil die Änderungsliste erst danach
|
||
eingeführt wurde.
|
||
|
||
### Einen Fehler auf Live beheben (Hotfix)
|
||
|
||
Ein Hotfix ist eine kleine Korrektur, die auf Live landen muss, **ohne** die
|
||
Neuerungen der Beta mitzunehmen. Ablauf (Claude führt die Git-Schritte aus, Sie
|
||
spielen ein):
|
||
|
||
1. Den Live-Stand holen: `git checkout live && git pull`.
|
||
2. Einen Korrekturzweig `hotfix/<kurzer-name>` von `live` anlegen.
|
||
3. Die Korrektur machen und die Tests laufen lassen.
|
||
4. Nach `live` mergen, die nächste Nummer vergeben (`vX.Y.(Z+1)`, also z. B.
|
||
`v1.0.1` nach `v1.0.0`) und beides pushen: `git push origin live vX.Y.(Z+1)`.
|
||
Danach spielen Sie die Version auf dem Live-Server ein (Befehle wie oben).
|
||
5. Die Korrektur in die Beta übernehmen: `git checkout main && git merge live`.
|
||
Vorher wird geprüft, ob die Korrektur dort noch zusammenpasst (Konflikte, Tests),
|
||
dann `git push` – die Beta bekommt sie mit dem nächsten Pipeline-Lauf.
|
||
|
||
**Keine Datenbankänderung als Hotfix.** Der Grund in Alltagssprache:
|
||
Datenbankänderungen (Migrationen) tragen einen Zeitstempel im Namen und werden in
|
||
dieser Reihenfolge ausgeführt. Die Beta hat womöglich schon neuere Änderungen
|
||
eingespielt. Eine Hotfix-Änderung mit noch späterem Zeitstempel landet beim
|
||
Übernehmen in die Beta hinter Änderungen, die sie eigentlich nicht kennt – das ist
|
||
der eine Fall, der beim Zusammenführen still kaputtgehen kann. Braucht eine Korrektur
|
||
eine Datenbankänderung, wird sie als reguläre Version über `main` freigegeben.
|
||
|
||
### Woran Sie erkennen, welche Version läuft
|
||
|
||
Vier Wege, vom einfachsten zum genauesten:
|
||
|
||
1. **In der Oberfläche:** Unten in der Seitenleiste steht `v1.0.0 · Live` bzw.
|
||
`v1.0.0-12-gabc1234 · Beta`. Wenn Sie die Maus darüber halten, erscheinen die
|
||
Kurzkennung des Standes und die Version, die der Server meldet. Weichen
|
||
Oberfläche und Server voneinander ab, wurde nur einer der beiden Container neu
|
||
erstellt – dann Kapitel 4 anwenden (`--force-recreate api web`).
|
||
2. **Auf dem Server per Abfrage:**
|
||
|
||
```bash
|
||
curl -s http://localhost:3001/health/version
|
||
```
|
||
|
||
Die Antwort enthält die Felder `version`, `channel` (`beta` oder `live`),
|
||
`commit` (Kurzkennung) und `buildTime` (wann das Image gebaut wurde).
|
||
3. **Im Protokoll:**
|
||
|
||
```bash
|
||
docker compose -f docker-compose.prod.yml logs api | grep "Tessera API"
|
||
```
|
||
|
||
Zeigt die Startzeile `Tessera API v1.0.0 (live) abc1234` (siehe Kapitel 7).
|
||
4. **Was sich geändert hat:** Ein Klick auf die Versionszeile unten in der
|
||
Seitenleiste öffnet die Seite „Was ist neu“ mit der Änderungsliste. Auf Live
|
||
sehen Sie nur freigegebene Versionen; auf der Beta steht zusätzlich der
|
||
Abschnitt „Noch nicht freigegeben (Beta)“ mit dem, was seit der letzten
|
||
Freigabe dazugekommen ist.
|
||
|
||
### Den neuen Live-Server einrichten
|
||
|
||
Kapitel 2 gilt vollständig. Die Abweichungen gegenüber alpha:
|
||
|
||
- In der `.env` steht `IMAGE_TAG=live`.
|
||
- Eigene, neu erzeugte Geheimnisse: `JWT_SECRET`, `TESSERA_ENCRYPTION_KEY`,
|
||
`DB_PASSWORD` und das Admin-Passwort. Nichts davon von alpha übernehmen.
|
||
- Eine eigene, leere Datenbank. Die API legt beim ersten Start den ersten Admin an
|
||
(Kapitel 2, Schritt 4). Die alpha-Datenbank wird **nicht** kopiert – es sei denn,
|
||
das wird ausdrücklich gewünscht. In diesem Fall gilt Kapitel 6 (Wiederherstellung)
|
||
**und** der Live-Server muss denselben `TESSERA_ENCRYPTION_KEY` wie alpha
|
||
bekommen, sonst sind alle gespeicherten Zugangsdaten unbrauchbar.
|
||
- `APP_URL=https://tessera.ctl.de`.
|
||
- Der erste `pull` holt das Etikett `live`. Vor der Erstfreigabe v1.0.0 gibt es
|
||
dieses Etikett noch nicht – deshalb erst freigeben, dann installieren. Für einen
|
||
Probelauf davor kann vorübergehend `IMAGE_TAG=beta` stehen; danach auf `live`
|
||
umstellen und `pull` + `up -d --force-recreate api web` wiederholen.
|
||
|
||
## 10. Desktop-App: Pakete und Release-Dateien
|
||
|
||
Seit September 2026 gibt es Tessera zusätzlich als Desktop-App für Windows und
|
||
Linux. Dieses Kapitel beschreibt, woher die Pakete kommen, wo sie liegen und
|
||
wie Sie Fehlerbilder rund um den Download einordnen. Die Anwendersicht (Download,
|
||
Installation, SmartScreen-Hinweis, Bedienung) steht in
|
||
`docs/anleitung-anwender.md`, Kapitel „Desktop-App".
|
||
|
||
### Woher die Pakete kommen
|
||
|
||
Der CI-Job `desktop` läuft nach `test` und vor `publish` – auf Push nach `main`
|
||
und bei jedem Freigabe-Tag `v*`. In diesem einen Job entstehen auf dem
|
||
Linux-Runner sowohl das Linux-AppImage als auch der Windows-Installer per
|
||
Cross-Bau (`cargo-xwin` + NSIS aus dem Ubuntu-Paket, kein Windows-Rechner in
|
||
der Pipeline). Einzelheiten zur Werkzeugkette stehen in
|
||
[`docs/ci-cd-setup.md`](./ci-cd-setup.md), Abschnitt 4.
|
||
|
||
Der Rust-Bau ist auf vier parallele Prozesse begrenzt (`CARGO_BUILD_JOBS`),
|
||
weil sich der Runner den Rechner mit Gitea und dem Entwicklungs-Stack teilt;
|
||
mit acht Prozessen geriet ein Rechner mit 15 GB Arbeitsspeicher an die Grenze.
|
||
Der Job dauert damit etwa fünf bis sieben Minuten (mit warmem Zwischenspeicher),
|
||
der erste Lauf nach einer Änderung der Abhängigkeiten deutlich länger – sofern überhaupt gebaut wird, siehe nächster Abschnitt.
|
||
|
||
### Wann gebaut wird und wann Pakete übernommen werden
|
||
|
||
Seit September 2026 baut die Pipeline die Desktop-Pakete auf dem Beta-Kanal
|
||
nur noch, wenn sich an der Desktop-App etwas geändert hat. Maßgeblich ist ein
|
||
Stempel aus der Versionsnummer des letzten Freigabe-Tags und dem letzten
|
||
Commit an den Desktop-Pfaden (`apps/desktop/`, die Skripte
|
||
`desktop-version.sh`, `desktop-collect.sh`, `desktop-stamp.sh`, die
|
||
Workflow-Datei `ci.yml`). Liegen zu diesem Stempel fertige Pakete im
|
||
Zwischenspeicher des Runners, übernimmt der Job sie unverändert; die
|
||
Bau-Schritte entfallen, und der Job braucht dann unter einer Minute. Im
|
||
Protokoll steht dann eine Zeile wie „Desktop unveraendert seit
|
||
<Commit>: Pakete … aus dem Zwischenspeicher".
|
||
|
||
Drei Regeln dazu:
|
||
|
||
- Freigabe-Tags bauen immer – die Release-Dateien entstehen frisch mit reiner
|
||
Versionsnummer.
|
||
- Nach einer Freigabe wird einmal neu gebaut, auch ohne Änderung an der
|
||
Desktop-App, weil die Versionsnummer zum Stempel gehört; die Beta-Pakete
|
||
tragen danach die neue Basisversion.
|
||
- Übernommene Pakete tragen den Stand ihres Baus – Dateiname (`-beta.<Commit>`)
|
||
und Manifest nennen den Commit des Baus, nicht den des aktuellen Abbilds.
|
||
Das ist gewollt: ein Client dieses Standes bekommt keinen unnötigen Hinweis
|
||
auf einen neuen Beta-Stand, ein älterer Client weiterhin.
|
||
|
||
Fehlt der Eintrag im Zwischenspeicher (der Runner räumt ungenutzte Einträge
|
||
nach einigen Tagen, alte nach etwa einem Monat weg) oder ist er unvollständig,
|
||
wird ganz normal gebaut – die Pipeline prüft vor der Übernahme Manifest,
|
||
Kanal, Version, Dateinamen, Größen und Prüfsummen.
|
||
|
||
### Wo die Pakete im Abbild liegen
|
||
|
||
`publish` kopiert die fertigen Pakete in das API-Abbild nach
|
||
`/app/desktop-dist/`, zusammen mit einer `manifest.json` (Version, Kanal,
|
||
Dateinamen, Größen, Prüfsummen; seit der Update-Funktion zusätzlich
|
||
`updateVersion` – die Form, die der Client vergleicht, `X.Y.Z` auf Live und
|
||
`X.Y.Z-beta.g{commit}` auf Beta – sowie je Plattform die `signature` des
|
||
Pakets). Auf dem Beta-Kanal tragen die Dateinamen
|
||
zusätzlich den Suffix `-beta.{commit}`, zum Beispiel
|
||
`Tessera-Setup-1.1.0-beta.742fb5c.exe` und
|
||
`Tessera-1.1.0-beta.742fb5c.AppImage`; auf Live steht dort die reine Form
|
||
`Tessera-Setup-X.Y.Z.exe` / `Tessera-X.Y.Z.AppImage`.
|
||
|
||
Kontrolle auf dem Server:
|
||
|
||
```bash
|
||
docker compose exec api ls -l /app/desktop-dist
|
||
curl -s https://{ihre-adresse}/api-proxy/desktop/latest
|
||
curl -si "https://{ihre-adresse}/api-proxy/desktop/update?target=windows&arch=x86_64¤t=0.0.0&base=https://{ihre-adresse}"
|
||
```
|
||
|
||
`ls -l` zeigt die abgelegten Dateien samt `manifest.json`; die erste
|
||
`curl`-Abfrage liefert dieselben Angaben als JSON (Version, Kanal, je
|
||
Plattform Dateiname, Größe, Prüfsumme und relative Download-Adresse) – das
|
||
ist genau die Antwort, die auch die Anmeldeseite und die Einstellungsseite
|
||
auswerten. Antwortet die Abfrage mit `404`, fehlt entweder das Verzeichnis
|
||
oder das Manifest; die Web-Oberfläche blendet den Download-Link dann
|
||
automatisch aus.
|
||
|
||
Die zweite Kontrollzeile stellt die Frage, die der Desktop-Client beim Start
|
||
stellt: `200` mit `version`, `url` und `signature` bedeutet, dass sich
|
||
installierte Clients von diesem Server aktualisieren können; `204` bedeutet,
|
||
dass für diese Plattform kein signiertes Paket vorliegt (zum Beispiel ein Stand
|
||
vor September 2026 oder ein Bau ohne Schlüssel). Für Linux `target=linux`
|
||
einsetzen.
|
||
|
||
### Release-Dateien in Gitea
|
||
|
||
Bei einem Freigabe-Tag hängt die Pipeline zusätzlich beide Dateien aus dem
|
||
Manifest als Anhänge an den Gitea-Release desselben Tags (Kapitel 9, „Eine
|
||
Version freigeben") – idempotent: ein erneuter Lauf ersetzt eine bereits
|
||
vorhandene Datei gleichen Namens, statt einen zweiten Anhang anzulegen. Die am
|
||
Release hinterlegte Datei ist byteidentisch mit der im Abbild ausgelieferten;
|
||
die Prüfsumme (`sha256`) aus `manifest.json` gilt für beide gleichermaßen.
|
||
|
||
### Updates in der App und der Signierschlüssel
|
||
|
||
Seit September 2026 aktualisiert sich die Desktop-App per Klick im Menü des
|
||
Infobereich-Symbols. Beim Start fragt sie `GET /api-proxy/desktop/update`
|
||
und installiert ausschließlich Pakete, deren Signatur zu dem im Client
|
||
hinterlegten öffentlichen Schlüssel passt – ein manipuliertes oder fremdes
|
||
Paket wird abgelehnt, bevor irgendetwas installiert wird. Das ist die
|
||
Vertrauensbasis der Update-Funktion, nicht die Prüfsumme im Manifest.
|
||
|
||
Der **private Schlüssel** liegt nicht im Repository. Er existiert an zwei
|
||
Stellen:
|
||
|
||
- als Gitea-Secrets `TAURI_SIGNING_PRIVATE_KEY` und
|
||
`TAURI_SIGNING_PRIVATE_KEY_PASSWORD` (Repository → Einstellungen → Actions →
|
||
Secrets); nur die beiden `tauri build`-Schritte des Jobs `desktop` sehen
|
||
sie, die Skripte kennen den Schlüssel nicht;
|
||
- als Sicherung auf dem Entwicklungsrechner unter
|
||
`~/.tessera/desktop-updater/` (`tessera-updater.key`, `password.txt` und
|
||
der öffentliche Teil `tessera-updater.key.pub`).
|
||
|
||
**Sicherung:** Legen Sie die beiden Dateien zusätzlich an einem zweiten
|
||
sicheren Ort ab. Geht der private Schlüssel verloren, können bereits
|
||
installierte Clients kein Update mehr annehmen: Es muss ein neues
|
||
Schlüsselpaar erzeugt (`pnpm --filter @tessera/desktop exec tauri signer
|
||
generate -w <pfad>`), der öffentliche Teil in
|
||
`apps/desktop/src-tauri/tauri.conf.json` unter `plugins.updater.pubkey`
|
||
eingetragen und jeder Client einmal von Hand neu installiert werden.
|
||
|
||
Ohne die Secrets bricht der CI-Bau ab („A public key has been found, but no
|
||
private key"). `tauri build --no-sign` ist ausschließlich für lokale Proben
|
||
gedacht und im CI nicht vorgesehen – ein so gebautes Paket trägt keine
|
||
Signatur, und der Update-Endpunkt antwortet dafür mit `204`.
|
||
|
||
Windows legt bei jedem Update einen Ordner `%TEMP%\Tessera-{Version}-updater-…`
|
||
(rund 100 MB) an und räumt ihn nicht auf. Das ist kein Fehler; die Ordner
|
||
können jederzeit gelöscht werden.
|
||
|
||
Der Client erlaubt Updates in der App nur über `https`. Anwender mit einer
|
||
`http`-Adresse sehen im Menü den Hinweis „Update nur über https möglich" und
|
||
nutzen weiterhin den Weg über den Browser.
|
||
|
||
### Umgebungsvariablen
|
||
|
||
Für die Desktop-Auslieferung ist keine neue Pflichtvariable nötig.
|
||
|
||
| Variable | Pflicht? | Default | Zweck |
|
||
|----------|:---:|---|---|
|
||
| `DESKTOP_DIST_DIR` | nein | `/app/desktop-dist` (im Abbild) | Ablageort der Desktop-Pakete und der `manifest.json`, aus dem `GET /desktop/latest` und `GET /desktop/download/:platform` lesen. In der Regel nicht ändern. |
|
||
|
||
### Fehlerbilder
|
||
|
||
| Symptom | Wahrscheinliche Ursache | Prüfen / Beheben |
|
||
|---|---|---|
|
||
| Download-Link fehlt auf der Anmeldeseite bzw. `/api-proxy/desktop/latest` liefert `404` | Das laufende Abbild trägt keine Desktop-Pakete – der Job `publish` hätte ohne Manifest eigentlich abbrechen müssen | Den zugehörigen Pipeline-Lauf prüfen (Job `desktop`/`publish` grün?), danach `docker compose pull` + `up -d --force-recreate api` erneut ausführen. |
|
||
| Download bricht bei großen Dateien ab | Größengrenze oder Zeitlimit des vorgeschalteten Proxys (Nginx Proxy Manager) – `client_max_body_size` bzw. Timeout-Einstellungen | Proxy-Konfiguration für die betroffene Adresse prüfen und die Grenze anheben. |
|
||
| Client meldet „Unter dieser Adresse antwortet kein Tessera-Server" | Anwender hat die interne API-Adresse statt der Web-Adresse eingetragen, oder `/api-proxy` ist vom Client-Rechner aus nicht erreichbar | Die im Anwenderhandbuch beschriebene Adresse verwenden (dieselbe wie im Browser); Netzwerk-/Firewall-Erreichbarkeit der Web-Adresse prüfen. |
|
||
| Windows zeigt die SmartScreen-Warnung | Erwartet – die App ist für den internen Gebrauch nicht signiert (D-09) | Kein Fehler; Anwenderhandbuch, Abschnitt „Installation unter Windows", beschreibt den Ablauf. |
|
||
| Beta-Paket nennt einen älteren Commit als das laufende Abbild (Dateiname `-beta.<Commit>`, Einstellungen → Desktop-App) | Erwartet: Desktop-App seit diesem Commit unverändert, Pakete aus dem Zwischenspeicher übernommen (Abschnitt „Wann gebaut wird …") | Kein Fehler. Soll dennoch neu gebaut werden, genügt eine Änderung unter `apps/desktop/` im nächsten Push. |
|
||
| Client meldet „Update fehlgeschlagen" | Download über den Proxy abgebrochen (Größengrenze/Zeitlimit, siehe zweite Zeile dieser Tabelle), oder die Signatur passt nicht – die Pakete stammen nicht aus dem CI-Bau mit dem aktuellen Schlüssel | Kontrollzeile `/api-proxy/desktop/update` (Abschnitt „Wo die Pakete im Abbild liegen"), den Pipeline-Lauf und die Proxy-Einstellungen prüfen. Der Anwender kommt über den Browser-Weg weiter. |
|
||
| `/api-proxy/desktop/update` antwortet dauerhaft `204`, obwohl Pakete da sind | Manifest ohne `signature`/`updateVersion`: Pakete aus einem Bau vor der Update-Funktion oder mit `--no-sign` | Eine Änderung unter `apps/desktop/` pushen bzw. den Tag neu bauen lassen; im CI prüfen, dass die Secrets `TAURI_SIGNING_PRIVATE_KEY`/`_PASSWORD` gesetzt sind (Abschnitt „Updates in der App und der Signierschlüssel"). |
|
||
| Eine Fehlermeldung aus der Desktop-App nennt als Herkunft „Desktop-App (unbekannt)“ ohne Version, Betreff-Kürzel `[Desktop]` | Der Client ist älter als diese Fassung: er meldet dem Server beim Start nur `desktop=1`, nicht Version, Stand und Betriebssystem (Parameter `dv`, `dc`, `dos`, aus denen `web` das Cookie `tessera_desktop_client` bildet) | Kein Fehler, die Meldung ist trotzdem als Desktop-App erkennbar. Client über „Auf Version … aktualisieren“ im Infobereich oder den Browser-Installer aktualisieren; danach stehen Betriebssystem, Version und Stand in der Meldung. |
|