docs(claude): Produktion/Deployment-Sektion (TrueNAS Custom-App, wie deployen)
Some checks failed
CI / Backend Tests (.NET) (push) Successful in 1m13s
CI / Docker Build & Push (push) Has been cancelled
CI / Deploy auf TrueNAS (Custom App) (push) Has been cancelled
CI / Frontend Tests (Node/Vite) (push) Has been cancelled

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-19 14:19:25 +02:00
parent 854da1a000
commit 1484d04917

View File

@@ -32,6 +32,53 @@ dotnet run --project GerbilManager.AppHost
- Postgres läuft als Container (Name wechselt pro Run, z. B. `postgres-xxxx`); die - Postgres läuft als Container (Name wechselt pro Run, z. B. `postgres-xxxx`); die
**Daten liegen in einem persistenten Volume** → überleben Neustarts. DB-Name: `gerbilmanager`. **Daten liegen in einem persistenten Volume** → überleben Neustarts. DB-Name: `gerbilmanager`.
## Produktion / Deployment (TrueNAS)
Prod läuft als **TrueNAS SCALE Custom-App** `gerbilmanager` auf dem Host **`truenas`**
(LAN-IP `192.168.2.115`, TrueNAS 25.10 „Goldeye", Docker). Erstinstallation 2026-07-19.
- **Zugriff (Bookmark der Züchterin):** **http://truenas:8090/** · API-Doku: `/scalar`.
(Port 80 ist durch den nginx-Reverse-Proxy der NAS belegt → App auf **8090**, LAN-only, keine Auth.)
- **Sichtbar unter Apps**, weil via `midclt call app.create {custom_app:true, app_name:"gerbilmanager",
custom_compose_config_string:<yaml>}` angelegt — **nicht** via `docker compose` (das taucht in Apps
NICHT auf). Container: `ix-gerbilmanager-{db,api,frontend,backup}-1`.
- **4 Dienste:** `db` (postgres:18) · `api` (.NET, EF-Migrationen laufen beim Start) · `frontend`
(nginx: SPA + `/api`-Proxy → `api:8080`) · `backup` (täglich 03:00 pg_dump + Foto-Archiv, Shell-Scheduler).
- **App-Home auf dem Pool:** `/mnt/JailStorage/DockerVolumes/gerbilmanager/` mit
`pgdata/ photos/ keys/ backups/ scripts/ publicsite/` und `deploy/truenas/{custom-app.compose.yaml,.env,scripts}`.
Images aus der Gitea-Registry `git.rismer.de/gulum/gerbilmanager-{api,frontend}:latest`.
### Deployen
**Automatisch (Regelfall):** Push auf `main` → Gitea Actions (`.gitea/workflows/ci.yml`): Tests →
Images bauen+pushen → Job `deploy` verbindet sich per SSH auf den NAS-Host und ruft
`deploy/truenas/scripts/truenas-deploy.sh` (→ `midclt app.redeploy`, zieht `:latest` neu, `pgdata`/
`photos` bleiben erhalten). Braucht die Gitea-Repo-Secrets **`NAS_SSH_HOST`** (=192.168.2.115) und
**`NAS_SSH_KEY`** (privater Deploy-Key; Pubkey liegt in `/root/.ssh/authorized_keys`).
**Manuell (auf dem NAS-Host):**
```bash
sh /mnt/JailStorage/DockerVolumes/gerbilmanager/deploy/truenas/scripts/truenas-deploy.sh
```
Legt die App an, wenn sie fehlt (create), sonst redeploy. Rendert `custom-app.compose.yaml` mit den
Werten aus `deploy/truenas/.env` (enthält `POSTGRES_PASSWORD` — **nur auf der NAS, nie im Repo**).
**Lokale DB → Prod kopieren** (einmalig gemacht): `pg_dump` der Aspire-DB → `psql`-Restore in den
`ix-gerbilmanager-db-1`-Container (dabei `api` kurz stoppen); Fotos aus `GerbilManagerWebAPI/photo-storage/`
per `tar | ssh` nach `…/gerbilmanager/photos/`.
### Deployment-Stolperfallen
- **`/opt` ist auf Goldeye read-only** → App-Home MUSS auf den Pool (`/mnt/JailStorage/...`).
- **`postgres:18`** legt PGDATA in `/var/lib/postgresql/18/docker` ab und deklariert das Volume als
`/var/lib/postgresql`. Mount auf `/var/lib/postgresql/data` (alte v≤17-Konvention) lässt v18 **nicht
starten** ("data in unused mount/volume") → db unhealthy → App-Rollback. Immer `/var/lib/postgresql`
mounten. (Debian-Image, nicht alpine → Locale `libc/en_US.utf8` = Quell-DB.)
- Gitea-Runner ist **containerisiert** (Label `ubuntu-latest`, kein `goldeye`, kein Host-/`midclt`-Zugriff)
und läuft **`maxParallel=1`** (Jobs sequenziell) — daher der SSH-Weg im Deploy-Job.
- CI-Run-Status notfalls aus der Gitea-DB: `docker exec -e PGPASSWORD=gitea ix-gitea-postgresdb-1
psql -U gitea -d gitea -c "select name,status from action_run_job where run_id=(select max(id) from action_run);"`
(`status`: 1=success, 2=failure, 4=skipped).
## Import → DB (3-stufig, WICHTIG) ## Import → DB (3-stufig, WICHTIG)
Die DB wird **nicht** direkt von Python beschrieben. Nach jeder Import-Code-Änderung Die DB wird **nicht** direkt von Python beschrieben. Nach jeder Import-Code-Änderung