diff --git a/CLAUDE.md b/CLAUDE.md index 6142970..a3aa255 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -32,6 +32,53 @@ dotnet run --project GerbilManager.AppHost - 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`. +## 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:}` 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) Die DB wird **nicht** direkt von Python beschrieben. Nach jeder Import-Code-Änderung