Files
kochwas/docs/OPERATIONS.md
hsiegeln 2289547503 docs(review): fix table names, IMAGE_DIR, image endpoints
- ARCHITECTURE.md: ingredient/step (waren faelschlich recipe_*)
- OPERATIONS.md: IMAGE_DIR (statt IMAGES_PATH)
- session-handoff: /api/recipes/[id]/image POST/DELETE ergaenzt

Findings aus REVIEW-2026-04-18.md / docs-vs-code.md
2026-04-18 22:13:15 +02:00

7.4 KiB
Raw Blame 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

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):

- 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!).

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:

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

docker exec kochwas-kochwas-1 sqlite3 /data/kochwas.db 'DELETE FROM thumbnail_cache;'

Oder gezielt eine URL:

docker exec kochwas-kochwas-1 sqlite3 /data/kochwas.db \
  "DELETE FROM thumbnail_cache WHERE url = 'https://www.chefkoch.de/rezepte/...';"

Datenbank-Backup manuell

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
IMAGE_DIR 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-FixDockerfile (localhost → 127.0.0.1, tightened interval)
  • SearXNG-Bot-Bypasssrc/lib/server/http.ts (extraHeaders)
  • Traefik-Wildcarddocker-compose.prod.yml (tls.domains Labels)
  • Thumbnail-Cache in SQLite003_thumbnail_cache.sql + searxng.ts

Git-Log ist die Wahrheit; diese Datei ist eine Orientierung.

PWA / Offline-Modus

Kochwas ist eine installierbare PWA. Erkennbar an:

  • static/manifest.webmanifest (Manifest + Icons: SVG + 192×192 + 512×512, alle maskable)
  • src/service-worker.ts (Cache + Sync)

Caches im Browser (siehe DevTools → Application → Cache Storage):

  • kochwas-shell-<version> — App-Shell (JS/CSS/Static-Icons), cache-first
  • kochwas-data-v1 — Rezept-HTMLs + API-JSON (SWR)
  • kochwas-images-v1 — Bilder (cache-first)
  • kochwas-meta — Cache-Manifest (Liste der gecachten Rezept-IDs unter /__cache-manifest__)

Sync-Verhalten:

  • Initial-Sync (nach erstem Install): SW lädt alle Rezepte + Bilder im Hintergrund. Fortschritt im SyncIndicator-Pill unten rechts.
  • Update-Sync (bei jedem App-Start online): Diff gegen Cache-Manifest, nur Delta nachladen, gelöschte IDs räumen.
  • Storage-Quota-Check: < 100 MB frei → abbrechen mit Fehler-Toast.

Bei SW-Problemen Debug-Pfad:

  1. Admin → „App"-Tab → „Offline-Cache leeren" (destructive, zweistufig bestätigt)
  2. Alternative: DevTools → Application → Service Workers → Unregister, dann Seite neu laden.

E2E-Tests (Playwright): npm run test:e2e. Setzt npm run build voraus (Playwright startet automatisch npm run preview).

Icons einmalig rendern: npm run render:icons (schreibt nach static/icon-*.png, committen).