Registry truenas:13000 -> git.rismer.de/gulum (alle 6 Stellen). Dataset-Pfade auf /mnt/JailStorage/DockerVolumes/gerbilmanager/ gesetzt. docs/ops.md: vollstaendiges Goldeye-Runbook (Ordner + UID-999-Perms, Registry-Login, Port-80-Fallback, Restore-Drill-Pflichtschritt, ZFS-Pfade). docker compose config: OK.
417 lines
13 KiB
Markdown
417 lines
13 KiB
Markdown
# GerbilManager — Betriebsanleitung (TrueNAS SCALE Goldeye)
|
||
|
||
> Zielgruppe: Julian (Systemadministration) und Ehefrau (tägliche Nutzung).
|
||
> Bookmark für die Ehefrau: **http://\<NAS-IP\>/** (z. B. http://truenas/)
|
||
|
||
---
|
||
|
||
## Inhaltsverzeichnis
|
||
|
||
1. [Übersicht & Architektur](#1-übersicht--architektur)
|
||
2. [Voraussetzungen](#2-voraussetzungen)
|
||
3. [Erstinstallation auf TrueNAS Goldeye](#3-erstinstallation-auf-truenas-goldeye)
|
||
4. [App starten / stoppen / aktualisieren](#4-app-starten--stoppen--aktualisieren)
|
||
5. [Backup & Wiederherstellung](#5-backup--wiederherstellung)
|
||
6. [ZFS-Snapshot-Schichtung](#6-zfs-snapshot-schichtung)
|
||
7. [CI/CD via Gitea Actions](#7-cicd-via-gitea-actions)
|
||
8. [Fehlerbehebung](#8-fehlerbehebung)
|
||
|
||
---
|
||
|
||
## 1. Übersicht & Architektur
|
||
|
||
```
|
||
Browser / Handy
|
||
| HTTP :80 (oder PORT aus .env, z.B. 8080)
|
||
v
|
||
┌──────────────────┐
|
||
│ frontend (nginx) │ statisches React-SPA + Reverse-Proxy
|
||
└──────┬───────────┘
|
||
│ /api/* → http://api:8080/*
|
||
│ /scalar → http://api:8080/scalar
|
||
v
|
||
┌──────────────────┐
|
||
│ api (.NET 10) │ GerbilManagerWebAPI, Minimal API
|
||
└──────┬───────────┘
|
||
│ ConnectionStrings__gerbilmanager
|
||
v
|
||
┌──────────────────┐ ┌──────────────────────┐
|
||
│ db (Postgres 17)│ │ backup (Sidecar) │
|
||
└──────────────────┘ │ pg_dump + tar + cron │
|
||
│ └──────────────────────┘
|
||
└─ pgdata-Volume → /mnt/JailStorage/DockerVolumes/gerbilmanager/pgdata
|
||
photos-Volume → /mnt/JailStorage/DockerVolumes/gerbilmanager/photos
|
||
backups-Volume → /mnt/JailStorage/DockerVolumes/gerbilmanager/backups
|
||
keys-Volume → /mnt/JailStorage/DockerVolumes/gerbilmanager/keys
|
||
```
|
||
|
||
**Einziger veröffentlichter Port:** `80` (konfigurierbar via `PORT` in `.env`).
|
||
Alles andere läuft intern im Docker-Netz.
|
||
|
||
---
|
||
|
||
## 2. Voraussetzungen
|
||
|
||
| Was | Details |
|
||
|-----|---------|
|
||
| TrueNAS SCALE | **25.10.2.1 „Goldeye"** (native Docker Custom Apps) |
|
||
| Container Registry | `git.rismer.de/gulum` (externes HTTPS) |
|
||
| Docker | bereits auf TrueNAS Goldeye vorhanden |
|
||
| Verzeichnisse | 4 Ordner unter `/mnt/JailStorage/DockerVolumes/gerbilmanager/` anlegen (Schritt 3.1) |
|
||
|
||
---
|
||
|
||
## 3. Erstinstallation auf TrueNAS Goldeye
|
||
|
||
### 3.1 Verzeichnisse anlegen und Berechtigungen setzen
|
||
|
||
Öffne eine Shell auf der NAS (TrueNAS → System → Shell oder SSH):
|
||
|
||
```bash
|
||
# Vier Ordner anlegen
|
||
mkdir -p /mnt/JailStorage/DockerVolumes/gerbilmanager/pgdata
|
||
mkdir -p /mnt/JailStorage/DockerVolumes/gerbilmanager/photos
|
||
mkdir -p /mnt/JailStorage/DockerVolumes/gerbilmanager/backups
|
||
mkdir -p /mnt/JailStorage/DockerVolumes/gerbilmanager/keys
|
||
|
||
# Postgres-Container läuft als UID 999 (postgres) / GID 999 intern.
|
||
# pgdata muss von UID 999 beschreibbar sein; postgres erzwingt chmod 0700.
|
||
chown -R 999:999 /mnt/JailStorage/DockerVolumes/gerbilmanager/pgdata
|
||
chmod 700 /mnt/JailStorage/DockerVolumes/gerbilmanager/pgdata
|
||
|
||
# photos, backups und keys werden von der API bzw. dem Sidecar beschrieben
|
||
# (laufen als root im Container) — keine weiteren ACL-Anpassungen nötig.
|
||
```
|
||
|
||
> **TrueNAS Dataset-ACL-Hinweis:** Falls `JailStorage` ein ZFS-Dataset mit NFSv4-ACLs ist,
|
||
> und `chown` meldet „Operation not permitted": setze in TrueNAS → Datasets →
|
||
> `JailStorage` → Berechtigungen → **ACL-Typ: POSIX** (oder nutze das UI-Formular
|
||
> „Eigentümer: 999, Gruppe: 999" für das `pgdata`-Unterverzeichnis).
|
||
|
||
### 3.2 Registry-Login auf der NAS
|
||
|
||
```bash
|
||
docker login git.rismer.de
|
||
# Benutzername und Token/Passwort eingeben (Gitea-Account oder Access Token mit read:packages)
|
||
```
|
||
|
||
Der Login wird unter `/root/.docker/config.json` gespeichert und bleibt nach Reboots erhalten.
|
||
|
||
### 3.3 Repository klonen
|
||
|
||
```bash
|
||
git clone https://git.rismer.de/gulum/GerbilManager.git /opt/gerbilmanager
|
||
cd /opt/gerbilmanager
|
||
```
|
||
|
||
### 3.4 Konfiguration anlegen
|
||
|
||
```bash
|
||
cp deploy/truenas/.env.example deploy/truenas/.env
|
||
nano deploy/truenas/.env
|
||
```
|
||
|
||
Mindestens setzen:
|
||
|
||
| Variable | Wert |
|
||
|----------|------|
|
||
| `POSTGRES_PASSWORD` | Sicheres Passwort (mind. 20 Zeichen, keine `"`) |
|
||
| `AI__BaseUrl` | Gemini: `https://generativelanguage.googleapis.com/v1beta/openai` |
|
||
| `AI__ApiKey` | Dein Gemini API-Key |
|
||
| `AI__Model` | `gemini-2.0-flash` (oder `gemini-flash-latest`) |
|
||
| `PORT` | `80` — falls Port 80 auf der NAS bereits belegt ist: **auf `8080` ändern** |
|
||
|
||
Die Pfad-Variablen (`PGDATA_PATH`, `PHOTOS_PATH`, etc.) sind bereits auf die Goldeye-Standardpfade
|
||
vorbelegt und müssen nur geändert werden, wenn du einen anderen Pool nutzt.
|
||
|
||
### 3.5 Images ziehen und App starten
|
||
|
||
```bash
|
||
cd /opt/gerbilmanager
|
||
docker compose -f deploy/truenas/compose.yaml pull
|
||
docker compose -f deploy/truenas/compose.yaml up -d
|
||
```
|
||
|
||
Erster Start dauert ca. 2–3 Minuten (Postgres-Init + EF-Migrationen).
|
||
|
||
> **TrueNAS Goldeye Custom App (Alternative):**
|
||
> Statt der Shell kann die App auch über TrueNAS → Apps → „Custom App installieren" →
|
||
> „Install via YAML" deployt werden: compose-Inhalt einfügen, Volumes als Host-Pfade
|
||
> konfigurieren. Die Shell-Methode ist einfacher und gibt mehr Kontrolle.
|
||
|
||
### 3.6 Verifikation
|
||
|
||
```bash
|
||
# Alle 4 Container laufen?
|
||
docker compose -f deploy/truenas/compose.yaml ps
|
||
|
||
# API-Healthcheck (erwartet: {"status":"Healthy"})
|
||
curl -s http://localhost/api/health
|
||
|
||
# Tier-Gesamtanzahl prüfen (erwartet > 0 nach Import)
|
||
curl -s "http://localhost/api/gerbils?pageSize=1" | grep -o '"totalCount":[0-9]*'
|
||
|
||
# API-Doku (Scalar) im Browser
|
||
http://<NAS-IP>/scalar
|
||
|
||
# Foto-Upload: in der Webapp ein Tier öffnen → Foto hochladen → Foto erscheint
|
||
```
|
||
|
||
---
|
||
|
||
## 4. App starten / stoppen / aktualisieren
|
||
|
||
### Starten
|
||
|
||
```bash
|
||
cd /opt/gerbilmanager
|
||
docker compose -f deploy/truenas/compose.yaml up -d
|
||
```
|
||
|
||
### Stoppen
|
||
|
||
```bash
|
||
docker compose -f deploy/truenas/compose.yaml down
|
||
```
|
||
|
||
### Aktualisieren (nach CI-Push auf main)
|
||
|
||
```bash
|
||
cd /opt/gerbilmanager
|
||
git pull
|
||
docker compose -f deploy/truenas/compose.yaml pull
|
||
docker compose -f deploy/truenas/compose.yaml up -d
|
||
```
|
||
|
||
> EF-Migrationen laufen automatisch beim API-Start — kein manueller Schritt nötig.
|
||
|
||
---
|
||
|
||
## 5. Backup & Wiederherstellung
|
||
|
||
### Automatisches Backup
|
||
|
||
Der `backup`-Sidecar-Container läuft dauerhaft und sichert täglich um **03:00 Uhr**:
|
||
- Kompletter `pg_dump` der Datenbank als `.sql`
|
||
- Komprimiertes Foto-Archiv als `.tar.gz`
|
||
- Rotation: Backups älter als `BACKUP_KEEP_DAYS` (Standard: 7) werden gelöscht
|
||
|
||
Backups liegen unter: `/mnt/JailStorage/DockerVolumes/gerbilmanager/backups/YYYY-MM-DD_HH-MM/`
|
||
|
||
```
|
||
/mnt/JailStorage/DockerVolumes/gerbilmanager/backups/
|
||
2026-06-06_03-00/
|
||
gerbilmanager_2026-06-06_03-00.sql (Datenbank-Dump, Klartext SQL)
|
||
photos_2026-06-06_03-00.tar.gz (Fotos)
|
||
backup.log (Protokoll)
|
||
```
|
||
|
||
### Manuelles Backup auslösen
|
||
|
||
```bash
|
||
docker compose -f deploy/truenas/compose.yaml exec backup /bin/sh /scripts/backup.sh
|
||
```
|
||
|
||
### Backup-Log prüfen
|
||
|
||
```bash
|
||
tail -50 /mnt/JailStorage/DockerVolumes/gerbilmanager/backups/backup.log
|
||
```
|
||
|
||
Backup-Validierung: Das Skript prüft ob der Dump `CREATE TABLE` enthält — fehlt dieser
|
||
Marker, erscheint eine WARNUNG im Log. Größe 0 KB bedeutet Fehlschlag.
|
||
|
||
### Wiederherstellung — Runbook
|
||
|
||
> **WARNUNG:** Alle aktuellen Datenbankdaten und Fotos werden überschrieben!
|
||
|
||
**Schritt 1:** API und Frontend stoppen (DB und backup-Sidecar laufen weiter)
|
||
```bash
|
||
docker compose -f deploy/truenas/compose.yaml stop api frontend
|
||
```
|
||
|
||
**Schritt 2:** Restore ausführen
|
||
|
||
```bash
|
||
# Neuestes Backup automatisch wählen und bestätigen:
|
||
docker compose -f deploy/truenas/compose.yaml exec -T backup \
|
||
/bin/sh /scripts/restore.sh latest -f
|
||
|
||
# Bestimmtes Backup (Datum aus Verzeichnisname):
|
||
docker compose -f deploy/truenas/compose.yaml exec -T backup \
|
||
/bin/sh /scripts/restore.sh 2026-06-06_03-00 -f
|
||
```
|
||
|
||
Das Skript:
|
||
1. Trennt alle offenen DB-Verbindungen
|
||
2. Spielt den SQL-Dump mit `psql -h db -U postgres -d gerbilmanager < dump.sql` ein
|
||
3. Entpackt das Foto-Archiv nach `/data/photos`
|
||
|
||
**Schritt 3:** API neu starten
|
||
```bash
|
||
docker compose -f deploy/truenas/compose.yaml start api frontend
|
||
```
|
||
|
||
**Schritt 4 — Verifikation (Pflicht nach erstem Restore-Drill):**
|
||
```bash
|
||
# Tier-Anzahl prüfen
|
||
curl -s "http://localhost/api/gerbils?pageSize=1" | grep -o '"totalCount":[0-9]*'
|
||
|
||
# ColorVariety-Anzahl (Stammdaten, erwartet: >= 60)
|
||
curl -s http://localhost/api/color-varieties | python3 -c "import sys,json; print(len(json.load(sys.stdin)))"
|
||
|
||
# Foto stichprobenartig prüfen
|
||
ls /mnt/JailStorage/DockerVolumes/gerbilmanager/photos/ | head -5
|
||
```
|
||
|
||
### Restore-Nachweis (Round-Trip-Test, lokal 2026-06-06)
|
||
|
||
Protokoll vom getesteten Restore auf lokalem Aspire-Postgres:
|
||
```
|
||
73 ColorVarieties vorhanden
|
||
→ DELETE 12 Zeilen → 61 verbleibend
|
||
→ psql < dump.sql eingespielt
|
||
→ 73 ColorVarieties bestätigt
|
||
Exit-Code: 0
|
||
```
|
||
|
||
**Erster TrueNAS-Restore-Drill:** nach Erstinstallation bitte ausführen und Tier-Anzahl
|
||
notieren — beweist dass Backup + Restore auf dem NAS korrekt funktionieren.
|
||
|
||
---
|
||
|
||
## 6. ZFS-Snapshot-Schichtung
|
||
|
||
ZFS-Snapshots ergänzen die pg_dump-Backups als zweite Sicherungsebene.
|
||
Sie schützen vor versehentlichem Datenverlust auf Dataset-Ebene.
|
||
|
||
### Empfohlene Snapshot-Konfiguration
|
||
|
||
In TrueNAS → **Datasets** → `JailStorage/DockerVolumes/gerbilmanager` → **Snapshots** → **Regelmäßige Snapshots**:
|
||
|
||
| Unterordner | Häufigkeit | Aufbewahrung |
|
||
|-------------|-----------|--------------|
|
||
| `.../photos` | Stündlich | 24 Stunden |
|
||
| `.../photos` | Täglich | 30 Tage |
|
||
| `.../pgdata` | Stündlich | 24 Stunden |
|
||
| `.../pgdata` | Täglich | 30 Tage |
|
||
| `.../backups` | Täglich | 90 Tage |
|
||
|
||
> **Hinweis:** `pgdata` enthält Live-Postgres-Dateien. ZFS-Snapshots davon sind crash-konsistent,
|
||
> aber **nicht** application-konsistent — für einen sauberen DB-Restore immer den `pg_dump` verwenden,
|
||
> nicht den ZFS-Snapshot von `pgdata`.
|
||
|
||
### Snapshot manuell erstellen (z. B. vor Update)
|
||
|
||
```bash
|
||
# Pool-/Dataset-Name anpassen falls nötig
|
||
zfs snapshot JailStorage/DockerVolumes/gerbilmanager/photos@vor-update-$(date +%Y%m%d)
|
||
zfs snapshot JailStorage/DockerVolumes/gerbilmanager/backups@vor-update-$(date +%Y%m%d)
|
||
```
|
||
|
||
### Aus ZFS-Snapshot wiederherstellen (Fotos)
|
||
|
||
```bash
|
||
# Snapshots auflisten
|
||
zfs list -t snapshot JailStorage/DockerVolumes/gerbilmanager/photos
|
||
|
||
# Einzelne Datei aus Snapshot kopieren
|
||
cp /mnt/JailStorage/DockerVolumes/gerbilmanager/photos/.zfs/snapshot/<NAME>/datei.jpg \
|
||
/mnt/JailStorage/DockerVolumes/gerbilmanager/photos/
|
||
```
|
||
|
||
---
|
||
|
||
## 7. CI/CD via Gitea Actions
|
||
|
||
CI pusht Images nach Erfolg zu `git.rismer.de/gulum/gerbilmanager-api` und
|
||
`git.rismer.de/gulum/gerbilmanager-frontend`.
|
||
|
||
### Registry-Secrets in Gitea
|
||
|
||
Gitea → Repository → Einstellungen → Secrets:
|
||
|
||
| Secret | Wert |
|
||
|--------|------|
|
||
| `REGISTRY_USER` | Gitea-Benutzername |
|
||
| `REGISTRY_TOKEN` | Gitea Access Token mit `package:write` |
|
||
|
||
### Update nach CI-Push
|
||
|
||
```bash
|
||
# Auf der NAS nach erfolgreichem CI-Lauf:
|
||
cd /opt/gerbilmanager
|
||
git pull
|
||
docker compose -f deploy/truenas/compose.yaml pull
|
||
docker compose -f deploy/truenas/compose.yaml up -d
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Fehlerbehebung
|
||
|
||
### App startet nicht
|
||
|
||
```bash
|
||
docker compose -f deploy/truenas/compose.yaml logs
|
||
docker compose -f deploy/truenas/compose.yaml logs api
|
||
docker compose -f deploy/truenas/compose.yaml logs db
|
||
```
|
||
|
||
### Port 80 belegt
|
||
|
||
Falls Port 80 vom TrueNAS-System selbst genutzt wird:
|
||
|
||
```bash
|
||
# In deploy/truenas/.env:
|
||
PORT=8080
|
||
# Dann neu starten:
|
||
docker compose -f deploy/truenas/compose.yaml up -d
|
||
```
|
||
|
||
### Postgres startet nicht (Permission denied auf pgdata)
|
||
|
||
```bash
|
||
# UID 999 muss Eigentümer des pgdata-Verzeichnisses sein:
|
||
chown -R 999:999 /mnt/JailStorage/DockerVolumes/gerbilmanager/pgdata
|
||
chmod 700 /mnt/JailStorage/DockerVolumes/gerbilmanager/pgdata
|
||
docker compose -f deploy/truenas/compose.yaml restart db
|
||
```
|
||
|
||
### Registry-Pull schlägt fehl
|
||
|
||
```bash
|
||
# Neu einloggen:
|
||
docker login git.rismer.de
|
||
# Dann pull wiederholen:
|
||
docker compose -f deploy/truenas/compose.yaml pull
|
||
```
|
||
|
||
### Datenbank nicht erreichbar
|
||
|
||
```bash
|
||
docker compose -f deploy/truenas/compose.yaml ps db
|
||
docker compose -f deploy/truenas/compose.yaml exec db \
|
||
psql -U postgres -d gerbilmanager -c "\dt"
|
||
```
|
||
|
||
### Backup-Fehler
|
||
|
||
```bash
|
||
tail -50 /mnt/JailStorage/DockerVolumes/gerbilmanager/backups/backup.log
|
||
docker compose -f deploy/truenas/compose.yaml exec backup /bin/sh /scripts/backup.sh
|
||
```
|
||
|
||
### Fotos werden nicht angezeigt
|
||
|
||
```bash
|
||
docker compose -f deploy/truenas/compose.yaml exec api ls /data/photos
|
||
```
|
||
|
||
### Kompletter Neustart (Daten bleiben erhalten)
|
||
|
||
```bash
|
||
docker compose -f deploy/truenas/compose.yaml down
|
||
docker compose -f deploy/truenas/compose.yaml up -d
|
||
```
|