Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 2m34s
Echter Datei-Upload statt CSS-Platzhaltergrafiken, gespeichert unter UPLOAD_DIR (Laufzeit-Verzeichnis, weiss nichts von seiner tatsaechlichen Speicher-Anbindung — in Produktion ein von der Betreiberin selbst per Kubernetes eingebundenes Volume, z.B. SMB; lokal ein simples ./uploads). - lib/uploads.ts: Kernlogik. Bildformat wird ausschliesslich aus den tatsaechlichen Bilddaten erkannt (Magic-Bytes via sharp), nie aus Client-Dateiname/MIME-Type. Zufaellige, nicht erratbare Dateinamen (Path-Traversal-/Overwrite-Schutz). Re-Encoding validiert Pixel-Daten vollstaendig und entfernt eingebettete Metadaten (EXIF/ICC/XMP) als Nebeneffekt. Content-Type beim Ausliefern kommt ausschliesslich aus einer festen Endung->MIME-Zuordnung, nie aus Dateiinhalt/-namen. - app/api/admin/upload/route.ts: Admin-only Upload-Endpunkt (CSRF- + Auth- + Rate-Limit-Pruefung, max. 8 MB, JPEG/PNG/WebP/AVIF). - app/uploads/[...path]/route.ts: liefert hochgeladene Bilder aus (public/ wird zur Build-Zeit fest ins Image gebacken, kann also keine zur Laufzeit hinzugefuegten Dateien bedienen) — mit Path-Traversal-Schutz und nosniff-Header. - content-registry.ts: neuer "image"-Feldtyp fuer Hero-/Startseite- Grafiken, admin/inhalte hat dafuer ein Upload-Widget. - Produkt-/Ernten-Admin-Formulare: Mehrfach-Bild-Upload fuer Product.images/Harvest.images (beide Felder existierten im Schema, hatten aber bislang keine Admin-Oberflaeche). Oeffentliche Seiten (Shop, Ernten, Chargen-QR-Seite) zeigen jetzt echte Fotos statt Platzhalter, sobald vorhanden. - Kubernetes: nur ein einfacher volumeMount in 05-app-deployment.yaml (Platzhalter-emptyDir, klar als CHANGE-ME markiert) — die eigentliche Volume-Quelle (SMB o.ae.) bindet die Betreiberin selbst ein, wie gewuenscht keine SMB/CSI-spezifischen Manifeste hinzugefuegt. Verifiziert: tsc --noEmit clean, 139/139 Tests gruen (21 neu), pnpm build clean. Upload-/Serving-Pipeline live gegen einen laufenden Server getestet (Auth-/CSRF-Gates, Path-Traversal-Ablehnung, Format-Ablehnung) — ein vollstaendiger authentifizierter Upload end-to-end (braucht echten Admin-Login inkl. 2FA) wurde nicht live durchgespielt, nur per Code-Review verifiziert. Co-Authored-By: Claude Sonnet 5 <[email protected]>
102 lines
3.9 KiB
YAML
102 lines
3.9 KiB
YAML
# image: unten mit der tatsaechlichen Registry/Tag aus der Gitea-Actions-
|
|
# Pipeline ersetzen. Kein separater Migrations-Job mehr noetig: der
|
|
# Container fuehrt "prisma migrate deploy" + "prisma db seed" selbst aus,
|
|
# BEVOR er den Server startet (siehe Dockerfile-CMD in der runner-Stage) —
|
|
# beide Schritte sind idempotent, laufen also bei jedem Neustart gefahrlos
|
|
# erneut. Readiness-/Liveness-Probes greifen automatisch erst, sobald Port
|
|
# 3000 offen ist, also nach abgeschlossener Migration/Seed.
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: wabenhain-app
|
|
namespace: wabenhain
|
|
spec:
|
|
# ACHTUNG bevor du hochskalierst: lib/rate-limit.ts haelt Login-/2FA-/
|
|
# Checkout-Limits aktuell im Prozessspeicher, nicht in einem gemeinsamen
|
|
# Store. Mit replicas > 1 verteilt sich derselbe Nutzer per Load-Balancing
|
|
# auf mehrere Prozesse mit je eigenem Zaehler — das Limit wird effektiv um
|
|
# den Skalierungsfaktor schwaecher (2 Replicas = 2x mehr Versuche moeglich),
|
|
# ohne dass das im Frontend sichtbar waere. Fuer replicas > 1 zuerst
|
|
# lib/rate-limit.ts auf einen gemeinsamen Store (z.B. Redis) umstellen.
|
|
replicas: 1
|
|
selector:
|
|
matchLabels:
|
|
app: wabenhain-app
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: wabenhain-app
|
|
spec:
|
|
containers:
|
|
- name: app
|
|
image: git.encry.de/nick.rosenloecher/wabenhain:latest
|
|
ports:
|
|
- containerPort: 3000
|
|
envFrom:
|
|
- configMapRef:
|
|
name: wabenhain-config
|
|
- secretRef:
|
|
name: wabenhain-secrets
|
|
volumeMounts:
|
|
# Muss auf denselben Pfad zeigen wie UPLOAD_DIR in
|
|
# 01-configmap.yaml. Das "uploads"-Volume selbst wird HIER
|
|
# bewusst nicht definiert (kein PV/PVC in diesem Manifest) — das
|
|
# haengt vom tatsaechlichen Storage-Backend ab (SMB/NFS/
|
|
# hostPath/...), das die Betreiberin bereitstellt. Unten im
|
|
# "volumes:"-Block einen passenden Eintrag mit demselben Namen
|
|
# ("uploads") ergaenzen, z.B. ueber eine
|
|
# persistentVolumeClaim.claimName, sonst startet der Pod nicht
|
|
# (fehlende Volume-Referenz).
|
|
- name: uploads
|
|
mountPath: /app/uploads
|
|
readinessProbe:
|
|
httpGet:
|
|
path: /
|
|
port: 3000
|
|
initialDelaySeconds: 10
|
|
periodSeconds: 10
|
|
livenessProbe:
|
|
httpGet:
|
|
path: /
|
|
port: 3000
|
|
# initialDelaySeconds/failureThreshold grosszuegiger als sonst
|
|
# ueblich: der Container migriert/seedet die DB selbst vor dem
|
|
# Serverstart (siehe Kommentar oben) — genug Spielraum, damit ein
|
|
# laengerer erster Migrationslauf nicht faelschlich als haengender
|
|
# Prozess gewertet und der Container neu gestartet wird.
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 30
|
|
failureThreshold: 5
|
|
resources:
|
|
requests:
|
|
cpu: 100m
|
|
memory: 256Mi
|
|
limits:
|
|
memory: 512Mi
|
|
volumes:
|
|
# Platzhalter — die Betreiberin ergaenzt hier ihre eigene
|
|
# Volume-Quelle fuer den Namen "uploads" (z.B. ein PVC, das gegen
|
|
# ein SMB-/NFS-Backend oder einen hostPath gebunden ist), etwa:
|
|
# - name: uploads
|
|
# persistentVolumeClaim:
|
|
# claimName: wabenhain-uploads-pvc
|
|
- name: uploads
|
|
# CHANGE-ME: obige emptyDir-Zeile durch die echte Volume-Quelle
|
|
# ersetzen. emptyDir ist NICHT persistent (Inhalt geht bei jedem
|
|
# Pod-Neustart verloren) und dient hier nur als Platzhalter, damit
|
|
# dieses Manifest ohne weitere Aenderung "kubectl apply"-faehig
|
|
# bleibt.
|
|
emptyDir: {}
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: wabenhain-app
|
|
namespace: wabenhain
|
|
spec:
|
|
selector:
|
|
app: wabenhain-app
|
|
ports:
|
|
- port: 80
|
|
targetPort: 3000
|