docs: CLAUDE.md + Architecture + Operations für Session-Fortsetzung
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 53s
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 53s
Drei fokussierte Dokumente, damit eine frische Claude-Session direkt weiterarbeiten kann, ohne den gesamten Session-Kontext zu brauchen: - CLAUDE.md (Root): "read me first" — Gotchas, Workflow, No-Gos, Quickstart, Verweise auf die Tiefen-Docs. - docs/ARCHITECTURE.md: Stack, Verzeichnisbaum, Datenfluss (Import, Web-Suche, Confirm/Alert), Design-Entscheidungen, Test-Strategie. - docs/OPERATIONS.md: Deployment-Topologie (Cloudflare → Pi → Traefik → kochwas), Gitea-CI-Pipeline, Traefik-Labels mit Wildcard-Cert, Troubleshooting (TLS, SearXNG-403, Healthcheck, Thumbnail-Cache leeren, Backup), Env-Vars. Die bestehenden Specs und Plans unter docs/superpowers/ bleiben unangetastet — die sind Planungs-Artefakte, nicht Betriebsdoku.
This commit is contained in:
69
CLAUDE.md
Normal file
69
CLAUDE.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# Kochwas — Hinweise für Claude (Fortsetzung nach Session-Neustart)
|
||||||
|
|
||||||
|
> **Lies mich zuerst.** Wenn du eine neue Session öffnest und hier weiterarbeitest, steht hier das Wesentliche. Tiefer: `docs/ARCHITECTURE.md` (Code) und `docs/OPERATIONS.md` (Deployment).
|
||||||
|
|
||||||
|
## Was das ist
|
||||||
|
Selbstgehostete Rezept-PWA für die Familie Siegeln. Erreichbar unter `https://kochwas.siegeln.net`. Deutschsprachiges UI, ohne Login, Profile werden per Klick gewählt. Läuft in Docker auf einem Raspberry Pi 5 (arm64).
|
||||||
|
|
||||||
|
## Wichtigste Gotchas (wiederkehrende Stolpersteine)
|
||||||
|
|
||||||
|
| Thema | Regel |
|
||||||
|
|---|---|
|
||||||
|
| **Node-Binding** | `better-sqlite3` ist **synchron** und native — im `Dockerfile` gibt es einen Build-Stage, der das Native-Module explizit für arm64 baut. |
|
||||||
|
| **Healthcheck** | Muss `127.0.0.1` verwenden, nicht `localhost`. Node bindet nur IPv4; `localhost` wird oft zu `::1` aufgelöst und der Check schlägt fehl. Traefik filtert unhealthy Container raus → kein Routing, kein ACME. |
|
||||||
|
| **SearXNG Bot-Detection** | Bei Requests aus dem Docker-Netzwerk müssen `X-Forwarded-For: 127.0.0.1` und `X-Real-IP: 127.0.0.1` im Header stehen (`src/lib/server/http.ts` `extraHeaders`). Sonst 403. |
|
||||||
|
| **Traefik Cloudflare-Token** | Token muss `Edit zone DNS` Berechtigung für `siegeln.net` haben. Expired Tokens → DNS-Challenge failt → Let's-Encrypt-Rate-Limit nach 5 Versuchen in 1 h. |
|
||||||
|
| **Wildcard-Cert** | Für neue Subdomains auf siegeln.net sollten die Labels das Wildcard nutzen, nicht per-Host-Cert: `tls.domains[0].main=siegeln.net` + `sans=*.siegeln.net`. |
|
||||||
|
| **Migrations** | Werden via Vite `import.meta.glob('./migrations/*.sql', {eager, query:'?raw'})` gebundelt. Neue Migration einfach als `00N_name.sql` ablegen, kein Copy-in-Dockerfile nötig. |
|
||||||
|
| **$lib/server in Client** | Svelte-Import aus `$lib/server/*` in einem `.svelte`-Komponenten-Script bricht den Build. Pures JS/TS, das beidseitig funktioniert (z. B. Portionen-Scaler), gehört nach `$lib/`, nicht `$lib/server/`. |
|
||||||
|
| **Preview-Bilder** | `recipe.image_path` kann **absolute URL** (Preview-Modus) oder **lokaler Filename** sein. `RecipeView.svelte` prüft mit `/^https?:\/\//i`. |
|
||||||
|
|
||||||
|
## Dateien, die man typischerweise anfasst
|
||||||
|
|
||||||
|
- `src/routes/+page.svelte` — Startseite mit Live-Search + Quote
|
||||||
|
- `src/routes/+layout.svelte` — Header, mobile expand, Dropdown-Search auf Rezeptseiten
|
||||||
|
- `src/routes/recipes/[id]/+page.svelte` — Rezept-Detail mit allen Actions (Rating, Favorit, Cooked, Wunschliste, Kommentar, Umbenennen, Löschen)
|
||||||
|
- `src/routes/preview/+page.svelte` — importierte Vorschau vor dem Speichern
|
||||||
|
- `src/lib/server/search/searxng.ts` — Web-Suche + Thumbnail-Enrichment + SQLite-Cache
|
||||||
|
- `src/lib/server/recipes/importer.ts` — JSON-LD → Recipe, orchestriert Bild-Download
|
||||||
|
- `src/lib/server/db/migrations/*.sql` — Schema; bei Änderung immer **neue** Migration statt bestehende bearbeiten
|
||||||
|
|
||||||
|
## Arbeitsweise (wie wir es machen)
|
||||||
|
|
||||||
|
- **Terse Antworten auf Deutsch**; Code-Kommentare auf Englisch, sparsam.
|
||||||
|
- **Commits** kleinteilig, deutscher Body, englische Zeile, Subject unter 72 Zeichen.
|
||||||
|
- **Tests nach jeder Änderung**: `npm test` (vitest) + `npm run check` (svelte-check). Beides muss grün sein, bevor gepusht wird.
|
||||||
|
- **Push nach jedem Commit**, außer der Nutzer sagt explizit nein. CI baut dann das arm64-Image und published es nach `gitea.siegeln.net/claude/kochwas:latest`.
|
||||||
|
- **Keine Backwards-Compat-Krücken** für nicht-ausgelieferten Code. Direkt refactoren, alte Signaturen raus.
|
||||||
|
- **Nie mit `--no-verify`** committen. Wenn ein Hook fehlschlägt, den echten Grund beheben.
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install # erstes Mal
|
||||||
|
npm run dev # lokal auf http://localhost:5173
|
||||||
|
npm test # volle Vitest-Suite
|
||||||
|
npm run check # svelte-check Types
|
||||||
|
npm run format # Prettier
|
||||||
|
```
|
||||||
|
|
||||||
|
Lokaler Docker-Test des Prod-Builds:
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.prod.yml up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Was NICHT tun
|
||||||
|
|
||||||
|
- Keine neuen Top-Level-Docs erzeugen, wenn ein bestehendes Dokument (Specs, Plans, ARCHITECTURE, OPERATIONS) passt.
|
||||||
|
- Keine Emojis in Code/Commits — außer UI-Icons (🍽️, ⚙️, 🥘 etc.) sind explizit im UX-Design.
|
||||||
|
- Keine `alert()`/`confirm()` — wir haben `alertAction()` / `confirmAction()` in `src/lib/client/confirm.svelte.ts`.
|
||||||
|
- Keine hardcoded `localhost` in Healthchecks → `127.0.0.1`.
|
||||||
|
- Keinen sensiblen Output in Commits (Cloudflare-Tokens, acme.json).
|
||||||
|
|
||||||
|
## Offene Themen / Stand
|
||||||
|
|
||||||
|
Siehe Session-Handoff-Dokumente unter `docs/superpowers/` und dort besonders `session-handoff-2026-04-17.md`. Die Roadmap-Phasen liegen als `docs/superpowers/plans/*.md`. Was als „Later" markiert ist, ist nicht beauftragt.
|
||||||
|
|
||||||
|
## Auto-Memory (lokal, nicht im Repo)
|
||||||
|
|
||||||
|
Persönliche Präferenzen / projektspezifische Entscheidungen landen in deinem Auto-Memory unter `~/.claude/projects/C--Users-Hendrik-Documents-projects-kochwas/memory/`. Der aktuelle Index (`MEMORY.md`) hält fest: Deployment-Target, Registry. Bei Bedarf erweitern — nicht in dieser Datei dokumentieren, da sie versioniert ist.
|
||||||
114
docs/ARCHITECTURE.md
Normal file
114
docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
# Kochwas — Architektur
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
|
||||||
|
- **SvelteKit 2** + **Svelte 5 Runes** (`$state`, `$derived`, `$effect`, `$props`)
|
||||||
|
- **TypeScript strict**
|
||||||
|
- **SQLite** über `better-sqlite3` (synchron, native Binding arm64)
|
||||||
|
- **FTS5** Virtual Tables mit BM25-Ranking für Volltext-Suche
|
||||||
|
- **linkedom** für HTML-Parsing (JSON-LD-Extraktion, og:image-Enrichment)
|
||||||
|
- **zod** für API-Schema-Validierung
|
||||||
|
- Adapter: `@sveltejs/adapter-node` → Node 22 Alpine im Container
|
||||||
|
|
||||||
|
## Top-Level-Struktur
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── app.html, app.d.ts # Shell + Env-Types
|
||||||
|
├── service-worker.ts # PWA-Shell
|
||||||
|
├── lib/
|
||||||
|
│ ├── client/ # clientseitig: Profil-Store, Confirm-Dialog
|
||||||
|
│ ├── components/ # Svelte-Komponenten (RecipeView, StarRating, ConfirmDialog, ProfileSwitcher)
|
||||||
|
│ ├── recipes/ # shared: Portionen-Scaler (Client UND Server)
|
||||||
|
│ ├── server/ # nur Server-Code (nie in Client-Bundle!)
|
||||||
|
│ │ ├── db/ # openDb, Migrations, DB-Singleton
|
||||||
|
│ │ ├── domains/ # Whitelist-Repo
|
||||||
|
│ │ ├── http.ts # fetch-Wrapper mit Timeout / maxBytes / extraHeaders
|
||||||
|
│ │ ├── images/ # Download, SHA256-Dedup, Save
|
||||||
|
│ │ ├── parsers/ # json-ld-recipe.ts, iso8601-duration.ts
|
||||||
|
│ │ ├── profiles/ # Profile-Repo
|
||||||
|
│ │ ├── recipes/ # importer, actions, repository, search-local
|
||||||
|
│ │ ├── search/ # searxng.ts (Web-Suche + Thumbnail-Cache)
|
||||||
|
│ │ ├── wishlist/ # Repo
|
||||||
|
│ │ └── backup/ # ZIP-Export via archiver, Import via yauzl
|
||||||
|
│ ├── quotes.ts # 49 Flachwitze für die Homepage
|
||||||
|
│ └── types.ts # shared types
|
||||||
|
└── routes/
|
||||||
|
├── +layout.svelte # Header, Confirm-Dialog-Mount, Header-Search-Dropdown
|
||||||
|
├── +page.svelte # Home: Hero + Live-Search + Zuletzt-hinzugefügt
|
||||||
|
├── recipes/[id]/ # Rezept-Detail
|
||||||
|
├── preview/ # Vorschau vor dem Speichern
|
||||||
|
├── search/ # /search (lokal), /search/web (Internet)
|
||||||
|
├── wishlist/
|
||||||
|
├── admin/ # Whitelist, Profile, Backup/Restore
|
||||||
|
├── images/[filename] # Statische Auslieferung lokaler Bilder
|
||||||
|
└── api/ # REST-Endpoints
|
||||||
|
```
|
||||||
|
|
||||||
|
## Datenfluss
|
||||||
|
|
||||||
|
### Import (User klickt auf Web-Treffer)
|
||||||
|
|
||||||
|
1. User klickt auf Web-Hit → `/preview?url=...`
|
||||||
|
2. `/api/recipes/preview` → `importer.ts` lädt HTML, `parseHTML` von linkedom, `json-ld-recipe.ts` extrahiert `Recipe`-Objekt mit **externer** Bild-URL
|
||||||
|
3. Preview-Seite rendert das `Recipe` via `RecipeView.svelte` (erkennt externe URL und lädt direkt vom Original-CDN)
|
||||||
|
4. User klickt „Speichern" → `/api/recipes/import` → Importer lädt Bild (`images/downloader.ts`), SHA256-Hash-Dedup, speichert lokal, INSERT in `recipe` + `recipe_ingredient` + `recipe_step` + `recipe_tag`
|
||||||
|
5. Redirect zu `/recipes/[id]`
|
||||||
|
|
||||||
|
### Web-Suche
|
||||||
|
|
||||||
|
1. User tippt → 300 ms Debounce → `/api/recipes/search?q=...` (lokal FTS5)
|
||||||
|
2. Wenn 0 Treffer: automatisch `/api/recipes/search/web?q=...`
|
||||||
|
3. `searxng.ts` → SearXNG-API mit `site:domain OR site:domain2 ...`-Filter aus Whitelist
|
||||||
|
4. Filtert Non-Recipe-Pfade (Foren, Magazin, Listings) via `NON_RECIPE_PATH_PATTERNS`
|
||||||
|
5. Pro Treffer: parallel (max 6) `enrichThumbnail`:
|
||||||
|
- SQLite-Cache hit → return
|
||||||
|
- Sonst: Seite holen (max 512 KB, 4 s), `extractPageImage`: og:image → link rel=image_src → JSON-LD → erstes Content-img
|
||||||
|
- Ergebnis (auch null) in `thumbnail_cache` persistieren (30 Tage TTL)
|
||||||
|
- **Überschreibt** bestehendes SearXNG-Thumbnail, weil das meist LowRes ist
|
||||||
|
|
||||||
|
### Confirm / Alert
|
||||||
|
|
||||||
|
Promise-basiert statt `window.confirm`/`window.alert`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { confirmAction, alertAction } from '$lib/client/confirm.svelte';
|
||||||
|
|
||||||
|
if (await confirmAction({ title: 'Löschen?', destructive: true })) { /* ... */ }
|
||||||
|
await alertAction({ title: 'Fehler', message: 'xyz' });
|
||||||
|
```
|
||||||
|
|
||||||
|
Gemeinsame Komponente `ConfirmDialog.svelte` wird im Root-Layout einmal gemountet. Store (`confirmStore`) hält die Promise-Resolve-Funktion, Komponente rendert nur wenn `pending !== null`.
|
||||||
|
|
||||||
|
## Design-Entscheidungen
|
||||||
|
|
||||||
|
- **Kein Login, nur Profile**: Profile werden beim Start gewählt, in localStorage persistiert. Actions (Rating, Favorit, Cooked, Kommentar) brauchen aktives Profil → sonst Custom-Alert „Bitte Profil wählen".
|
||||||
|
- **FTS5 als Haupt-Suche**: statt externer Search-Engine-DB. Passt zu SQLite-only-Stack.
|
||||||
|
- **JSON-LD first**: Alle drei Ziel-Domains (Chefkoch, Emmi, Experimente) liefern `schema.org/Recipe` im JSON-LD. LLM-Fallback war geplant, aktuell nicht nötig.
|
||||||
|
- **SearXNG als Such-Engine**: Self-hosted, daher keine API-Keys. Das Bot-Detection-Theater wird mit gesetzten `X-Forwarded-For`-Headern aus Docker-IPs umgangen.
|
||||||
|
- **Thumbnail-Cache in SQLite**: 30 Tage TTL (per `KOCHWAS_THUMB_TTL_DAYS`). Negative Einträge (Seite ohne Bild) werden auch gecacht.
|
||||||
|
- **Svelte 5 Runes** — kein `$:` mehr, keine alten Stores außer `$app/stores`. Neue Stores via Klasse mit `$state`-Feldern.
|
||||||
|
- **Service Worker** rein zum Shell-Cachen für Offline-First-PWA, kein intelligentes Cache-Matching (keine externe Rezept-Seiten).
|
||||||
|
|
||||||
|
## Migrations-Workflow
|
||||||
|
|
||||||
|
Bei Schema-Änderung:
|
||||||
|
|
||||||
|
1. Neue Datei `src/lib/server/db/migrations/00N_beschreibung.sql` — nächste freie Nummer
|
||||||
|
2. SQL sollte nicht-destruktiv sein (nur `CREATE`, `ALTER ADD`); keine `DROP` auf bestehende Daten
|
||||||
|
3. `migrate.ts` liest via Vite-Glob und führt neue Einträge aus (über `schema_migrations`-Tabelle getrackt)
|
||||||
|
4. Tests anpassen: `db idempotent` zählt vorher/nachher — bleibt automatisch grün
|
||||||
|
|
||||||
|
## Test-Strategie
|
||||||
|
|
||||||
|
- **Unit**: `tests/unit/` — pure Funktionen (json-ld-recipe, iso8601-duration, quotes-random, smoke)
|
||||||
|
- **Integration**: `tests/integration/` — mit `openInMemoryForTest()` fresh SQLite pro Test. Externe HTTP via `node:http`-TestServer auf Port 0 gemockt.
|
||||||
|
- **Keine Svelte-Component-Tests** (bewusst, Aufwand/Nutzen stimmt nicht; UI wird manuell getestet)
|
||||||
|
- **Vor Commit**: `npm test && npm run check` muss grün sein.
|
||||||
|
|
||||||
|
## Was später kommt (laut Spec, aktuell nicht implementiert)
|
||||||
|
|
||||||
|
- LLM-Fallback für nicht-JSON-LD-Seiten
|
||||||
|
- Print-View ist nur rudimentär, könnte ein eigenes Print-CSS bekommen
|
||||||
|
- Tag-Editor im Admin
|
||||||
|
- Export-Format JSON zusätzlich zu ZIP
|
||||||
148
docs/OPERATIONS.md
Normal file
148
docs/OPERATIONS.md
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
# 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.
|
||||||
Reference in New Issue
Block a user