docs: Konfigurationsvorlagen vollstaendig und uebernahmefaehig machen

01-configmap.yaml und 02-secret.example.yaml sind jetzt so geschrieben, dass
sie sich als Produktivkonfiguration uebernehmen lassen: in drei Abschnitte
gegliedert (ANPASSEN / Vorgaben / optional), jeder Wert mit der Angabe, was
ohne ihn nicht funktioniert. Anzupassen sind genau zwei Werte in der
ConfigMap und die mit "CHANGE-ME" markierten im Secret; alles Uebrige traegt
eine brauchbare Vorgabe oder darf leer bleiben.

Beide Dateien decken zusammen jede Umgebungsvariable ab, die die Anwendung
liest, und jeder Schluessel steht in genau einer der beiden. Bisher war das
Handarbeit und driftete: GOOGLE_PLACES_API_KEY lag in der unverschluesselten
ConfigMap, APICORE_API_URL unnoetig im Secret.

Neu ist deshalb src/lib/k8s-manifest-env.test.ts. Der Test liest die
process.env-Zugriffe unter src/ und die Schluessel der beiden Manifeste und
prueft vier Zusagen: keine gelesene Variable fehlt, kein Schluessel steht
doppelt (sonst entschiede allein die Reihenfolge der envFrom-Eintraege,
welcher Wert gewinnt), kein Schluessel ist tot, und die committete
Secret-Vorlage enthaelt keine ausgefuellten Werte.

Beim ersten Lauf hat der Test zwei Dinge gefunden. ADMIN_EMAIL und
ADMIN_PASSWORD liest nicht die App, sondern prisma/seed.ts — das ist
legitim und steht jetzt begruendet in einer Ausnahmeliste, zusammen mit
DB_PASSWORD fuer das Datenbank-StatefulSet. STRIPE_PUBLISHABLE_KEY dagegen
wird von keiner einzigen Code-Stelle gelesen: der Checkout laeuft
vollstaendig serverseitig ueber eine Stripe-Checkout-Session. Der Schluessel
ist aus Secret-Vorlage, .env.example, docker-compose.yml und README
entfernt, mit Hinweis, wofuer man ihn braeuchte.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5 committed 2026-09-17 03:06:18 +02:00
1 parent 990a16e654
commit 479ace1b88
6 files changed
+323 -87

No files matched your search

+78 -36
View File
@@ -1,46 +1,88 @@
# Nicht-geheime Konfiguration. Geheimnisse (Passwoerter, API-Keys) gehoeren
# in 02-secret.yaml (aus 02-secret.example.yaml kopiert), NIE hierher.
# Nicht-geheime Konfiguration. Geheimnisse (Passwoerter, API-Schluessel)
# gehoeren in 02-secret.yaml, NIE hierher: eine ConfigMap ist unverschluesselt
# und in der Cluster-Oberflaeche im Klartext lesbar.
#
# ANPASSEN vor dem ersten Ausrollen: NEXT_PUBLIC_SITE_URL und SMTP_FROM. Alle
# uebrigen Werte sind sinnvolle Vorgaben und koennen so bleiben; die leeren
# ("") gehoeren zu optionalen Funktionen, die dann einfach ruhen.
#
# Zusammen mit 02-secret.yaml deckt diese Datei JEDE Umgebungsvariable ab, die
# die Anwendung liest. Die Vollstaendigkeit wird automatisch geprueft, siehe
# src/lib/k8s-manifest-env.test.ts — kommt im Code eine neue Variable dazu,
# faellt das beim naechsten "pnpm test" auf, nicht erst im Betrieb.
apiVersion: v1
kind: ConfigMap
metadata:
name: wabenhain-config
namespace: wabenhain
data:
# "postgresql" oder "mysql" — muss zum Service passen, den du unter
# 03-postgres.yaml/03-mysql.yaml tatsaechlich ausrollst, UND zum
# DATABASE_URL-Host in 02-secret.yaml. Ein einzelnes Image traegt beide
# ---------------------------------------------------------------------
# ANPASSEN
# ---------------------------------------------------------------------
# Oeffentliche Adresse des Shops. Einzige Quelle fuer Sitemap,
# Canonical-Tags, JSON-LD und JEDEN Link in JEDER E-Mail (src/lib/url.ts).
# Muss zum "host" in 06-ingress.yaml passen — mit https, ohne
# abschliessenden Schraegstrich.
NEXT_PUBLIC_SITE_URL: "https://wabenhain.com"
# Absenderadresse aller Transaktions-E-Mails. Sollte auf einer eigenen
# Domain liegen, sonst landen die Mails regelmaessig im Spam-Ordner.
SMTP_FROM: "Wabenhain <[email protected]>"
# ---------------------------------------------------------------------
# VORGABEN — koennen so bleiben
# ---------------------------------------------------------------------
NODE_ENV: "production"
# "postgresql" oder "mysql" — muss zu dem Datenbank-Manifest passen, das du
# tatsaechlich ausrollst (03-postgres.yaml bzw. 03-mysql.yaml), UND zum
# Schema in DATABASE_URL im Secret. Ein einzelnes Image traegt beide
# Prisma-Client-Varianten, siehe src/lib/prisma.ts.
DATABASE_PROVIDER: "postgresql"
NODE_ENV: "production"
# Oeffentliche URL des Shops (fuer absolute Links in E-Mails/Sitemaps/CSP).
# Muss zum "host" in 06-ingress.yaml passen, sonst zeigen Sitemap,
# Canonical-Tags und jeder Link in jeder Bestellbestaetigung ins Leere.
NEXT_PUBLIC_SITE_URL: "https://wabenhain.com"
# Port des SMTP-Anbieters. 587 ist STARTTLS (der Normalfall), 465 waere
# implizites TLS.
SMTP_PORT: "587"
SMTP_FROM: "Wabenhain <[email protected]>"
# Optional, siehe README-Abschnitte "Google-Rezensionen"/"CAPTCHA" — leer
# lassen, wenn nicht genutzt, die App faellt automatisch zurueck.
#
# Nur die Place-ID steht hier: sie ist oeffentlich, sie steckt in jeder
# Google-Maps-Adresse des Eintrags. Der zugehoerige GOOGLE_PLACES_API_KEY
# ist dagegen ein abrechenbarer Zugangsschluessel und liegt im Secret —
# eine ConfigMap ist unverschluesselt und in der Cluster-Oberflaeche im
# Klartext lesbar.
GOOGLE_PLACE_ID: ""
NEXT_PUBLIC_TURNSTILE_SITE_KEY: ""
# Adresse der ApiCore-API fuer den Stammdaten-Sync (Ernten, Produkte,
# Chargen — siehe src/lib/apicore-client.ts). Nur die URL; die Zugangsdaten
# des Sync-Kontos liegen im Secret.
APICORE_API_URL: ""
# Threads fuer das Umrechnen hochgeladener Videos. Nicht gesetzt bedeutet 2.
# NICHT ohne Not erhoehen: ffmpeg teilt sich das Speicherlimit dieses Pods
# mit dem Node-Prozess, und jeder zusaetzliche Thread haelt eigene
# Bildpuffer (bei 4K rund 12 MB je Bild). Siehe src/lib/video-transcode.ts.
VIDEO_TRANSCODE_THREADS: "2"
# "sandbox" bis DHL-Versandmarken getestet sind, danach "production".
DHL_ENVIRONMENT: "sandbox"
# Verzeichnis fuer hochgeladene Bilder (Inhalte-/Produkt-/Erntenfotos,
# siehe app/api/admin/upload/route.ts) — MUSS exakt dem mountPath des
# "uploads"-Volumes in 05-app-deployment.yaml entsprechen. Die App selbst
# weiss nicht, was fuer ein Volume dort gemountet ist; siehe .env.example.
# Verzeichnis fuer hochgeladene Bilder und Videos. MUSS exakt dem mountPath
# des "uploads"-Volumes in 05-app-deployment.yaml entsprechen — die App
# weiss nicht, was dort gemountet ist, sie schreibt nur in diesen Pfad.
# Fehlt der Wert, schreibt die App nach "./uploads" relativ zum
# Arbeitsverzeichnis: das trifft im Image zufaellig denselben Ort, ist aber
# nirgends zugesichert. Deshalb hier ausdruecklich setzen.
UPLOAD_DIR: "/app/uploads"
# Threads fuer das Umrechnen hochgeladener Videos (Admin -> Inhalte ->
# Hero-Video). NICHT ohne Not erhoehen: ffmpeg teilt sich das Speicherlimit
# dieses Pods mit dem Node-Prozess, und jeder zusaetzliche Thread haelt
# eigene Bildpuffer — bei 4K-Material rund 12 MB je Bild. Zu viele Threads
# bringen den Pod beim Video-Upload um. Siehe src/lib/video-transcode.ts.
VIDEO_TRANSCODE_THREADS: "2"
# "sandbox", bis die DHL-Versandmarken getestet sind, danach "production".
# Wirkt nur, wenn die DHL-Zugangsdaten im Secret gesetzt sind.
DHL_ENVIRONMENT: "sandbox"
# ---------------------------------------------------------------------
# OPTIONAL — leer lassen, solange die Funktion nicht genutzt wird
# ---------------------------------------------------------------------
# Oeffentlicher Teil von Cloudflare Turnstile (CAPTCHA). Der geheime
# Gegenpart TURNSTILE_SECRET_KEY steht im Secret — beide setzen oder beide
# leer lassen. Leer = kein CAPTCHA-Widget, die Formulare bleiben nutzbar.
NEXT_PUBLIC_TURNSTILE_SITE_KEY: ""
# Google-Eintrag, aus dem die echten Rezensionen auf der Startseite kommen.
# Die Place-ID ist oeffentlich (sie steckt in jeder Google-Maps-Adresse des
# Eintrags) und darf deshalb hier stehen; der abrechenbare
# GOOGLE_PLACES_API_KEY liegt im Secret. Leer = statische Beispiel-Stimmen.
GOOGLE_PLACE_ID: ""
# Adresse der ApiCore-API fuer den Stammdaten-Sync (Ernten, Produkte,
# Chargen — siehe src/lib/apicore-client.ts), mit dem globalen Praefix
# "api", z.B. "https://apicore.example.org/api". Nur die Adresse; die
# Zugangsdaten des Sync-Kontos liegen im Secret. Leer = der Sync-Knopf im
# Admin meldet einen Konfigurationsfehler.
APICORE_API_URL: ""