# 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
```
