Files
kochwas/docs/OPERATIONS.md

149 lines
6.1 KiB
Markdown
Raw Normal View History

# Kochwas — Deployment & Operations
## Deployment-Topologie
```
Browser
↓ HTTPS (kochwas.siegeln.net)
Cloudflare DNS (A-Record auf Pi-IP oder Tunnel)
Raspberry Pi 5 (arm64, Debian/Ubuntu)
Traefik v3 (Docker, Container "traefik" im Netz traefik_proxy)
↓ reverse proxy
Kochwas-Container (Node 22 Alpine, Port 3000, internal bridge)
↔ SearXNG-Container (Sidecar im gleichen Stack, Port 8080 intern)
```
- **Traefik** terminiert TLS mit Wildcard-Cert `*.siegeln.net` von Let's Encrypt (DNS-01 Challenge über Cloudflare-API).
- **SearXNG** läuft als Sidecar im kochwas-Compose. Kochwas spricht ihn über `http://searxng:8080` intern an.
- **Gitea Registry** `gitea.siegeln.net/claude/kochwas` hostet das arm64-Image.
- **Daten** liegen im Volume `/opt/docker/kochwas/data/` (SQLite + images/).
## Build & Publish (Gitea Actions)
Workflow in `.gitea/workflows/docker.yml`:
1. Trigger: push auf `main`
2. Checkout, Setup QEMU + Buildx
3. Login an `gitea.siegeln.net` mit Secret `REGISTRY_TOKEN` (PAT mit `write:package` + `read:package` Scope)
4. `docker/build-push-action` baut **nativ arm64** (nicht via emuliertem amd64!), mit `cache-from/to: type=registry,ref=...:buildcache`
5. Push als `:latest` und `:${commit}`
Wenn die Pipeline rot ist, häufig:
- `unauthorized`: Token fehlt oder ohne Package-Scope. PAT unter Gitea → Settings → Applications → Generate Token.
- Build-Cache i/o-Timeout: Registry-Cache benutzen, nicht GHA-Artifact-Cache.
## Deploy auf den Pi
```bash
ssh admin@pi5
cd /opt/docker/kochwas
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d
docker compose logs -f kochwas
```
Was der Pi braucht (einmalig):
- `/opt/docker/kochwas/docker-compose.prod.yml` — gespiegelt aus dem Repo
- `/opt/docker/kochwas/.env` mit `KOCHWAS_TAG=latest` (optional) und `SEARXNG_SECRET=...`
- `/opt/docker/kochwas/searxng/settings.yml` — aus dem Repo, mit `limiter: false` und `public_instance: false`
- `/opt/docker/kochwas/data/` existiert (für SQLite + images)
- Netzwerk `traefik_proxy` existiert, damit Traefik den Container sieht
## Traefik-Integration
Labels am kochwas-Container (siehe `docker-compose.prod.yml`):
```yaml
- traefik.enable=true
- traefik.docker.network=traefik_proxy
- traefik.http.routers.kochwas.rule=Host(`kochwas.siegeln.net`)
- traefik.http.routers.kochwas.entrypoints=websecure
- traefik.http.routers.kochwas.tls.certresolver=cloudflareResolver
- traefik.http.routers.kochwas.tls.domains[0].main=siegeln.net
- traefik.http.routers.kochwas.tls.domains[0].sans=*.siegeln.net
- traefik.http.services.kochwas.loadbalancer.server.port=3000
```
Die `tls.domains`-Zeilen sorgen dafür, dass der Router das Wildcard-Cert nutzt statt einen neuen per-Host-Cert zu holen. **Nie per-Host für neue Subdomains** — Let's Encrypt Rate-Limit (5 failed Authorizations pro Identifier pro Stunde, 50 Certs pro Registered Domain pro Woche).
### Wenn Cert fehlt / TLS-Fehler
1. `echo | openssl s_client -servername kochwas.siegeln.net -connect kochwas.siegeln.net:443 2>/dev/null | openssl x509 -noout -issuer -subject` — ist der Issuer „TRAEFIK DEFAULT CERT"? Dann hat Traefik kein Cert.
2. `sudo jq '.cloudflareResolver.Certificates | map(.domain.main)' /opt/docker/traefik/letsencrypt/acme.json` — ist `siegeln.net` (mit SAN `*.siegeln.net`) dabei?
3. `docker logs traefik 2>&1 | grep -iE 'lego|acme|cloudflare|kochwas' | tail -60` — Fehler?
- `Invalid access token` → Cloudflare-API-Token abgelaufen, neu erstellen mit `Zone → DNS → Edit` Scope, `CF_DNS_API_TOKEN` im Traefik-Compose setzen, `docker compose up -d traefik`
- `429 rateLimited` → Warten (zeitangabe im Error) oder auf Wildcard umstellen
## Troubleshooting
### Container läuft, Traefik filtert ihn raus
Symptom: Traefik-Logs sagen `Filtering unhealthy or starting container`.
Ursache: Healthcheck schlägt fehl. Der Check ruft `wget 127.0.0.1:3000/api/health` (muss IPv4 sein!).
```bash
docker inspect kochwas-kochwas-1 --format '{{json .State.Health}}' | jq
docker exec kochwas-kochwas-1 wget -qO- 127.0.0.1:3000/api/health
```
### SearXNG gibt 403 zurück
Log: `Internet-Suche zurzeit nicht möglich: HTTP 403`
Ursache: Bot-Detection. Fix war schon einmal nötig — `src/lib/server/http.ts` setzt via `extraHeaders` `X-Forwarded-For: 127.0.0.1` und `X-Real-IP: 127.0.0.1`. Wenn trotzdem 403: `searxng/settings.yml` prüfen:
```yaml
use_default_settings: true
server:
limiter: false
public_instance: false
secret_key: ${SEARXNG_SECRET:-dev-secret-change-in-prod}
search:
formats: [html, json]
```
Der Server-Container muss diese Datei per Volume Mount sehen. Nach Änderung: `docker compose restart searxng`.
### Thumbnail-Cache leeren
```bash
docker exec kochwas-kochwas-1 sqlite3 /data/kochwas.db 'DELETE FROM thumbnail_cache;'
```
Oder gezielt eine URL:
```bash
docker exec kochwas-kochwas-1 sqlite3 /data/kochwas.db \
"DELETE FROM thumbnail_cache WHERE url = 'https://www.chefkoch.de/rezepte/...';"
```
### Datenbank-Backup manuell
```bash
ssh admin@pi5 'docker exec kochwas-kochwas-1 sqlite3 /data/kochwas.db ".backup /data/backup.db"'
scp admin@pi5:/opt/docker/kochwas/data/backup.db ./kochwas-$(date +%F).db
```
Die App hat ein eingebautes Backup unter `/admin` (ZIP-Export mit DB + Bildern). Restore via `/admin` ebenfalls.
## Umgebungsvariablen
| Name | Default | Bedeutung |
|---|---|---|
| `SEARXNG_URL` | `http://localhost:8888` | SearXNG-Endpoint, im Compose auf `http://searxng:8080` |
| `KOCHWAS_THUMB_TTL_DAYS` | `30` | TTL für Thumbnail-Cache in der SQLite |
| `DATABASE_PATH` | `data/kochwas.db` | Pfad zur SQLite, relativ oder absolut |
| `IMAGES_PATH` | `data/images` | Pfad für lokale Bild-Dateien |
| `PORT` | `3000` | Node-HTTP-Port (adapter-node) |
Siehe `.env.example` im Repo.
## Häufige Commits als Referenz
- **Healthcheck-Fix** → `Dockerfile` (localhost → 127.0.0.1, tightened interval)
- **SearXNG-Bot-Bypass** → `src/lib/server/http.ts` (extraHeaders)
- **Traefik-Wildcard** → `docker-compose.prod.yml` (tls.domains Labels)
- **Thumbnail-Cache in SQLite** → `003_thumbnail_cache.sql` + `searxng.ts`
Git-Log ist die Wahrheit; diese Datei ist eine Orientierung.