deploy/truenas/compose.yaml:
- 4 services: db (postgres:17), api (.NET), frontend (nginx), backup sidecar
- Named volumes as NAS bind-mounts (paths configured in .env)
- Healthchecks: db pg_isready, api /health, frontend waits on api
- ANTHROPIC_API_KEY placeholder for FEAT-12a AI stub
deploy/truenas/.env.example: POSTGRES_PASSWORD, PORT, REGISTRY, dataset paths
deploy/truenas/scripts/:
- entrypoint.sh: installs daily 03:00 cron, starts crond in foreground
- backup.sh: pg_dump -> .sql + tar czf photos -> .tar.gz + rotation
- restore.sh: psql < dump + tar xzf photos; confirmation prompt (-f to skip)
Predecessor logic (commit 6a37f9e) translated from PowerShell to POSIX sh
for Alpine containers. Same pg_dump/restore semantics, same KEEP_DAYS rotation.
.gitea/workflows/ci.yml (DRAFT -- inactive until Julian confirms Actions):
dotnet test + npm test/build on push/PR to main;
docker build+push to Gitea registry on main push only
docs/ops.md:
German ops guide -- architecture diagram, first-install checklist,
start/stop/update commands, backup runbook (manual + auto), ZFS-snapshot
layering strategy, CI activation steps, open questions, troubleshooting table
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
382 lines
11 KiB
Markdown
382 lines
11 KiB
Markdown
# GerbilManager — Betriebsanleitung (TrueNAS)
|
||
|
||
> 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](#3-erstinstallation-auf-truenas)
|
||
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. [Offene Fragen (bitte beantworten)](#8-offene-fragen)
|
||
9. [Fehlerbehebung](#9-fehlerbehebung)
|
||
|
||
---
|
||
|
||
## 1. Übersicht & Architektur
|
||
|
||
```
|
||
Browser / Handy
|
||
| HTTP :80
|
||
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 (NAS-Dataset)
|
||
photos-Volume (NAS-Dataset)
|
||
backups-Volume (NAS-Dataset)
|
||
```
|
||
|
||
**Einziger veröffentlichter Port:** `80` (konfigurierbar via `PORT` in `.env`).
|
||
Alles andere läuft intern im Docker-Netz.
|
||
|
||
---
|
||
|
||
## 2. Voraussetzungen
|
||
|
||
| Was | Details |
|
||
|-----|---------|
|
||
| TrueNAS SCALE | Electric Eel 24.10+ (native Docker Custom Apps) |
|
||
| Gitea | http://truenas:13000 — Repository `Gulum/GerbilManager` |
|
||
| Docker | bereits auf TrueNAS vorhanden (Custom Apps nutzen es) |
|
||
| Datasets | Drei ZFS-Datasets anlegen (siehe Schritt 3) |
|
||
|
||
---
|
||
|
||
## 3. Erstinstallation auf TrueNAS
|
||
|
||
### 3.1 ZFS-Datasets anlegen
|
||
|
||
In TrueNAS → **Datasets** → **Dataset hinzufügen** (je einmal wiederholen):
|
||
|
||
| Dataset-Name | Empfohlener Pfad | Verwendung |
|
||
|---|---|---|
|
||
| `gerbil/pgdata` | `/mnt/SSD/gerbil/pgdata` | Postgres-Datenbankdateien |
|
||
| `gerbil/photos` | `/mnt/SSD/gerbil/photos` | Hochgeladene Tierfotos |
|
||
| `gerbil/backups` | `/mnt/SSD/gerbil/backups` | Tägliche Backups |
|
||
|
||
> **Tipp:** Passe die Pool-Bezeichnung (`SSD`) an deinen tatsächlichen Pool an.
|
||
|
||
### 3.2 Repository klonen
|
||
|
||
```bash
|
||
# SSH in TrueNAS oder lokale Shell
|
||
git clone http://truenas:13000/Gulum/GerbilManager.git /opt/gerbilmanager
|
||
cd /opt/gerbilmanager
|
||
```
|
||
|
||
### 3.3 Konfiguration anlegen
|
||
|
||
```bash
|
||
cp deploy/truenas/.env.example deploy/truenas/.env
|
||
# Jetzt .env bearbeiten:
|
||
nano deploy/truenas/.env
|
||
```
|
||
|
||
Mindestens setzen:
|
||
- `POSTGRES_PASSWORD` — sicheres Passwort (mind. 20 Zeichen)
|
||
- `PGDATA_PATH`, `PHOTOS_PATH`, `BACKUPS_PATH` — tatsächliche Dataset-Pfade
|
||
|
||
### 3.4 Images bauen und App starten
|
||
|
||
```bash
|
||
cd /opt/gerbilmanager
|
||
docker compose -f deploy/truenas/compose.yaml build
|
||
docker compose -f deploy/truenas/compose.yaml up -d
|
||
```
|
||
|
||
Erster Start dauert ca. 2–3 Minuten (Postgres-Init + EF-Migrationen).
|
||
|
||
### 3.5 Prüfen
|
||
|
||
```bash
|
||
# Alle Container laufen?
|
||
docker compose -f deploy/truenas/compose.yaml ps
|
||
|
||
# API-Healthcheck
|
||
curl http://localhost/api/health
|
||
|
||
# Webapp im Browser
|
||
http://<NAS-IP>/
|
||
```
|
||
|
||
---
|
||
|
||
## 4. App starten / stoppen / aktualisieren
|
||
|
||
### Starten
|
||
|
||
```bash
|
||
docker compose -f deploy/truenas/compose.yaml up -d
|
||
```
|
||
|
||
### Stoppen
|
||
|
||
```bash
|
||
docker compose -f deploy/truenas/compose.yaml down
|
||
```
|
||
|
||
### Aktualisieren (nach `git push` auf main)
|
||
|
||
```bash
|
||
cd /opt/gerbilmanager
|
||
git pull
|
||
docker compose -f deploy/truenas/compose.yaml build
|
||
docker compose -f deploy/truenas/compose.yaml up -d
|
||
```
|
||
|
||
> EF-Migrationen laufen automatisch beim API-Start — kein manueller Schritt nötig.
|
||
|
||
### Mit Gitea CI (wenn Actions aktiviert)
|
||
|
||
Push auf `main` triggert automatisch Build → Test → Image-Push.
|
||
Danach auf der NAS:
|
||
```bash
|
||
docker compose -f deploy/truenas/compose.yaml pull
|
||
docker compose -f deploy/truenas/compose.yaml up -d
|
||
```
|
||
|
||
---
|
||
|
||
## 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: `${BACKUPS_PATH}/YYYY-MM-DD_HH-MM/`
|
||
|
||
```
|
||
/mnt/SSD/gerbil/backups/
|
||
2026-06-06_03-00/
|
||
gerbilmanager_2026-06-06_03-00.sql (Datenbank)
|
||
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/SSD/gerbil/backups/backup.log
|
||
```
|
||
|
||
### Wiederherstellung — Runbook
|
||
|
||
> **WARNUNG:** Alle aktuellen Daten werden überschrieben!
|
||
|
||
**Schritt 1:** App stoppen (optional, aber empfohlen)
|
||
```bash
|
||
docker compose -f deploy/truenas/compose.yaml stop api frontend
|
||
```
|
||
|
||
**Schritt 2:** Restore ausführen
|
||
```bash
|
||
# Neuestes Backup wiederherstellen:
|
||
docker compose -f deploy/truenas/compose.yaml exec backup \
|
||
/bin/sh /scripts/restore.sh
|
||
|
||
# Bestimmtes Backup wiederherstellen:
|
||
docker compose -f deploy/truenas/compose.yaml exec backup \
|
||
/bin/sh /scripts/restore.sh 2026-06-05_03-00
|
||
```
|
||
|
||
**Schritt 3:** API neu starten
|
||
```bash
|
||
docker compose -f deploy/truenas/compose.yaml start api frontend
|
||
```
|
||
|
||
**Schritt 4:** Prüfen
|
||
```bash
|
||
curl http://localhost/api/color-varieties | grep -c '"id"'
|
||
# Erwarteter Wert: 73
|
||
```
|
||
|
||
### Restore-Nachweis (Round-Trip-Test)
|
||
|
||
Protokoll vom Test auf lokalem Aspire-Postgres (Vorgänger-Instanz, 2026-06-06 07:09):
|
||
```
|
||
73 ColorVarieties vorhanden
|
||
→ DELETE 12 Zeilen → 61 verbleibend
|
||
→ pg_restore eingespielt
|
||
→ 73 ColorVarieties bestätigt
|
||
Exit-Code: 0
|
||
```
|
||
Die Container-Restore-Skripte nutzen dieselbe `psql < dump.sql` Logik.
|
||
**Erster echter Test auf TrueNAS:** nach Erstinstallation bitte ausführen und das Ergebnis notieren.
|
||
|
||
---
|
||
|
||
## 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** → Dataset auswählen → **Snapshots** → **Regelmäßige Snapshots**:
|
||
|
||
| Dataset | Häufigkeit | Aufbewahrung |
|
||
|---------|-----------|--------------|
|
||
| `gerbil/photos` | Stündlich | 24 Stunden |
|
||
| `gerbil/photos` | Täglich | 30 Tage |
|
||
| `gerbil/pgdata` | Stündlich | 24 Stunden |
|
||
| `gerbil/pgdata` | Täglich | 30 Tage |
|
||
| `gerbil/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
|
||
# TrueNAS CLI
|
||
zfs snapshot SSD/gerbil/photos@vor-update-$(date +%Y%m%d)
|
||
zfs snapshot SSD/gerbil/backups@vor-update-$(date +%Y%m%d)
|
||
```
|
||
|
||
### Aus ZFS-Snapshot wiederherstellen (Fotos)
|
||
|
||
```bash
|
||
# Snapshot auflisten
|
||
zfs list -t snapshot SSD/gerbil/photos
|
||
|
||
# Datei aus Snapshot kopieren
|
||
cp /mnt/SSD/gerbil/photos/.zfs/snapshot/<NAME>/datei.jpg /mnt/SSD/gerbil/photos/
|
||
```
|
||
|
||
---
|
||
|
||
## 7. CI/CD via Gitea Actions
|
||
|
||
Der Workflow `.gitea/workflows/ci.yml` ist als **Entwurf vorhanden, aber inaktiv**.
|
||
|
||
### Aktivierung
|
||
|
||
1. **Gitea Actions aktivieren:**
|
||
Gitea → Repository `GerbilManager` → Einstellungen → Actions → "Actions aktivieren"
|
||
|
||
2. **Gitea Actions Runner installieren** (auf TrueNAS oder einem separaten Gerät):
|
||
```bash
|
||
# Gitea Runner Container (einfachste Variante für TrueNAS)
|
||
docker run -d --name gitea-runner \
|
||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||
-v /opt/gitea-runner:/data \
|
||
-e GITEA_INSTANCE_URL=http://truenas:13000 \
|
||
-e GITEA_RUNNER_REGISTRATION_TOKEN=<TOKEN> \
|
||
gitea/act_runner:latest
|
||
```
|
||
Token: Gitea → Admin → Actions → Runner → "Runner hinzufügen"
|
||
|
||
3. **Registry-Secrets konfigurieren:**
|
||
Gitea → Repository → Einstellungen → Secrets:
|
||
- `REGISTRY_USER` — dein Gitea-Benutzername
|
||
- `REGISTRY_TOKEN` — Gitea Access Token mit `package:write`-Berechtigung
|
||
|
||
### Workflow nach Aktivierung
|
||
|
||
```
|
||
git push origin main
|
||
→ Gitea Actions: dotnet test + npm test + npm run build
|
||
→ Bei Erfolg: docker build + push zu truenas:13000/gulum/
|
||
→ Auf NAS: docker compose pull + up -d
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Offene Fragen
|
||
|
||
Bitte beantworte diese Fragen, damit das Setup fertiggestellt werden kann:
|
||
|
||
| # | Frage | Auswirkung |
|
||
|---|-------|-----------|
|
||
| 1 | **TrueNAS SCALE Version?** Electric Eel 24.10 hat native Docker Custom Apps. Ältere Versionen nutzen Kubernetes. | Bestimmt ob `docker compose` direkt läuft |
|
||
| 2 | **Gitea Actions verfügbar/aktivierbar?** | CI/CD-Workflow aktiv oder nur manuell deployen |
|
||
| 3 | **Eigener Postgres-Container (empfohlen) oder vorhandene NAS-Postgres-App?** | Isolation vs. geteilte Instanz |
|
||
| 4 | **Genaue Dataset-Pfade?** Poolname und Pfad-Präfix | `.env`-Konfiguration |
|
||
| 5 | **Port-Wahl?** Standard 80 — frei auf der NAS? | `PORT`-Wert in `.env` |
|
||
|
||
---
|
||
|
||
## 9. Fehlerbehebung
|
||
|
||
### App startet nicht
|
||
|
||
```bash
|
||
# Logs aller Container
|
||
docker compose -f deploy/truenas/compose.yaml logs
|
||
|
||
# Logs eines bestimmten Containers
|
||
docker compose -f deploy/truenas/compose.yaml logs api
|
||
docker compose -f deploy/truenas/compose.yaml logs db
|
||
```
|
||
|
||
### Datenbank nicht erreichbar
|
||
|
||
```bash
|
||
# DB-Container läuft?
|
||
docker compose -f deploy/truenas/compose.yaml ps db
|
||
|
||
# Verbindung testen
|
||
docker compose -f deploy/truenas/compose.yaml exec db \
|
||
psql -U postgres -d gerbilmanager -c "\dt"
|
||
```
|
||
|
||
### Backup-Fehler
|
||
|
||
```bash
|
||
# Backup-Log prüfen
|
||
cat /mnt/SSD/gerbil/backups/backup.log | tail -30
|
||
|
||
# Backup manuell starten (mit Fehlerausgabe)
|
||
docker compose -f deploy/truenas/compose.yaml exec backup \
|
||
/bin/sh /scripts/backup.sh
|
||
```
|
||
|
||
### Fotos werden nicht angezeigt
|
||
|
||
Prüfe ob das `photos`-Volume korrekt gemounted ist:
|
||
```bash
|
||
docker compose -f deploy/truenas/compose.yaml exec api ls /data/photos
|
||
```
|
||
|
||
### Container-Status zurücksetzen (Neustart)
|
||
|
||
```bash
|
||
docker compose -f deploy/truenas/compose.yaml restart api
|
||
```
|
||
|
||
### Kompletter Neustart (Daten bleiben erhalten)
|
||
|
||
```bash
|
||
docker compose -f deploy/truenas/compose.yaml down
|
||
docker compose -f deploy/truenas/compose.yaml up -d
|
||
```
|