149 lines
6.1 KiB
Markdown
149 lines
6.1 KiB
Markdown
|
|
# 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.
|