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