CLAUDE.md: zwei neue Gotchas (SW nur HTTPS, Icon-Rendering) + Erweiterung der "Dateien, die man anfasst"-Liste um SW-Pfade und Client-Stores. OPERATIONS.md: neuer Abschnitt "PWA / Offline-Modus" mit Caches, Sync-Verhalten, Debug-Pfad und E2E-Test-Kommando. ARCHITECTURE.md: neuer Abschnitt "Service Worker (PWA)" mit Zuständigkeiten, Cache-Strategien, Message-Protokoll, Stores und Komponenten. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7.4 KiB
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.netvon Let's Encrypt (DNS-01 Challenge über Cloudflare-API). - SearXNG läuft als Sidecar im kochwas-Compose. Kochwas spricht ihn über
http://searxng:8080intern an. - Gitea Registry
gitea.siegeln.net/claude/kochwashostet das arm64-Image. - Daten liegen im Volume
/opt/docker/kochwas/data/(SQLite + images/).
Build & Publish (Gitea Actions)
Workflow in .gitea/workflows/docker.yml:
- Trigger: push auf
main - Checkout, Setup QEMU + Buildx
- Login an
gitea.siegeln.netmit SecretREGISTRY_TOKEN(PAT mitwrite:package+read:packageScope) docker/build-push-actionbaut nativ arm64 (nicht via emuliertem amd64!), mitcache-from/to: type=registry,ref=...:buildcache- Push als
:latestund:${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/.envmitKOCHWAS_TAG=latest(optional) undSEARXNG_SECRET=.../opt/docker/kochwas/searxng/settings.yml— aus dem Repo, mitlimiter: falseundpublic_instance: false/opt/docker/kochwas/data/existiert (für SQLite + images)- Netzwerk
traefik_proxyexistiert, 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
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.sudo jq '.cloudflareResolver.Certificates | map(.domain.main)' /opt/docker/traefik/letsencrypt/acme.json— istsiegeln.net(mit SAN*.siegeln.net) dabei?docker logs traefik 2>&1 | grep -iE 'lego|acme|cloudflare|kochwas' | tail -60— Fehler?Invalid access token→ Cloudflare-API-Token abgelaufen, neu erstellen mitZone → DNS → EditScope,CF_DNS_API_TOKENim Traefik-Compose setzen,docker compose up -d traefik429 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 |
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.
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-firstkochwas-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:
- Admin → „App"-Tab → „Offline-Cache leeren" (destructive, zweistufig bestätigt)
- 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).