diff --git a/README.md b/README.md index fc94e27..0d18e70 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Regionale Imkerei-Website und Shop für Wabenhain (Altenmünster) — MVP-Stufe. +> **Neu einrichten?** Alle Secrets, die Microsoft-Anmeldung, den Mailversand über Microsoft 365, Stripe und das Ausrollen beschreibt Schritt für Schritt **[docs/EINRICHTUNG.md](docs/EINRICHTUNG.md)**. + ## Tech-Stack | Bereich | Technologie | diff --git a/docs/EINRICHTUNG.md b/docs/EINRICHTUNG.md new file mode 100644 index 0000000..b8a5db4 --- /dev/null +++ b/docs/EINRICHTUNG.md @@ -0,0 +1,437 @@ +# Wabenhain einrichten — von null bis live + +Diese Anleitung führt durch alles, was außerhalb des Codes eingerichtet werden muss, damit der Shop in Kubernetes vollständig läuft: Geheimnisse (Secrets), Microsoft-Anmeldung, Mailversand über Microsoft 365, Stripe und die optionalen Dienste. Sie setzt nichts voraus, was nicht hier steht. + +**Zeitbedarf:** etwa 2 Stunden für Pflicht und Mailversand, plus Wartezeiten bei Microsoft (bis zu 30 Minuten) und Stripe. + +**Reihenfolge:** + +1. [Werkzeuge und Zugänge bereitlegen](#1-werkzeuge-und-zugänge) +2. [Grundwerte erzeugen](#2-grundwerte-erzeugen) (Datenbank, Sitzungen, Admin) +3. [Microsoft Entra ID: App-Registrierung](#3-microsoft-entra-id-app-registrierung) (Anmeldung **und** Mailversand) +4. [Microsoft 365: Absender-Postfach freischalten](#4-microsoft-365-absender-postfach-freischalten) +5. [Stripe](#5-stripe) +6. [Optionale Dienste](#6-optionale-dienste) +7. [Secret und ConfigMap ausfüllen](#7-secret-und-configmap-ausfüllen) +8. [Ausrollen](#8-ausrollen) +9. [Prüfen](#9-prüfen) +10. [Wartung: was abläuft und was zu tun ist](#10-wartung) +11. [Fehlerbehebung](#11-fehlerbehebung) + +> **Sicherheitsregel für das ganze Dokument:** Echte Werte gehören ausschließlich in `k8s/02-secret.yaml` (per `.gitignore` ausgeschlossen) bzw. direkt ins Cluster. Nie committen, nie in Chats, Tickets oder Screenshots kopieren. Die ConfigMap (`k8s/01-configmap.yaml`) ist unverschlüsselt und im Cluster für jeden mit Lesezugriff im Klartext sichtbar — dort steht nichts Geheimes. + +--- + +## 1. Werkzeuge und Zugänge + +**Auf deinem Rechner:** + +| Werkzeug | Wofür | Prüfen mit | +|---|---|---| +| `kubectl` mit Zugriff auf das Cluster | Secrets und Manifeste anwenden | `kubectl get nodes` | +| `openssl` | Zufallswerte erzeugen | `openssl version` | +| Node.js | Datenbank-Passwort URL-kodieren | `node --version` | +| PowerShell mit Modul `ExchangeOnlineManagement` | Postfach für den Mailversand freischalten | `Get-Module -ListAvailable ExchangeOnlineManagement` | + +Modul einmalig installieren (PowerShell): + +```powershell +Install-Module ExchangeOnlineManagement -Scope CurrentUser +``` + +**Zugänge, die du brauchst:** + +| Zugang | Rolle | Wofür | +|---|---|---| +| Microsoft Entra Admin Center (entra.microsoft.com) | Anwendungsadministrator oder Globaler Administrator | App-Registrierung, Administratorzustimmung | +| Microsoft 365 Admin Center (admin.microsoft.com) | Exchange-Administrator | Freigegebenes Postfach anlegen und freischalten | +| Stripe-Dashboard (dashboard.stripe.com) | Administrator | API-Schlüssel, Webhook | +| Gitea (`git.encry.de`) | Schreibrecht am Repository | Image-Build (siehe `k8s/README.md`) | + +--- + +## 2. Grundwerte erzeugen + +Diese Werte denkst du dir nicht aus, sondern lässt sie erzeugen. Notiere sie vorübergehend in einem Passwort-Manager, nicht in einer Textdatei. + +### Datenbank-Passwort (`DB_PASSWORD` und `DATABASE_URL`) + +```bash +openssl rand -base64 32 | tr -d '=+/' | head -c 32 +``` + +Das Passwort steht an **zwei** Stellen im Secret: + +- `DB_PASSWORD`: **roh**, genau wie erzeugt. +- `DATABASE_URL`: **URL-kodiert**. Sonderzeichen würden sonst als Trennzeichen der URL gelesen. + ```bash + node -e "console.log(encodeURIComponent('HIER-DAS-PASSWORT'))" + ``` + Ergebnis einsetzen: + `postgresql://wabenhain:@wabenhain-postgres:5432/wabenhain?schema=public` + +Bei MySQL statt Postgres: `mysql://wabenhain:@wabenhain-mysql:3306/wabenhain` und in der ConfigMap `DATABASE_PROVIDER: "mysql"`. + +### Sitzungsschlüssel (`SESSION_SECRET`) + +```bash +openssl rand -base64 32 +``` + +Signiert alle Login-Cookies. Wird er bekannt, lassen sich Sitzungen fälschen — auch die eines Admins. Ihn zu ändern meldet alle nur ab. + +### Erstes Admin-Konto (`ADMIN_EMAIL`, `ADMIN_PASSWORD`) + +- `ADMIN_EMAIL`: eine **echte, nicht erratbare** Adresse, z. B. `n.rosenloecher-admin@wabenhain.com` — **nicht** `admin@…` oder `info@…`. Sonst könnte jemand diese Adresse vorher als Kundenkonto registrieren. An diese Adresse gehen außerdem die Benachrichtigungen über eingegangene Widerrufe. +- `ADMIN_PASSWORD`: + ```bash + openssl rand -base64 24 | tr -d '=+/' | head -c 28 + ``` + +Das Konto wird beim ersten Start angelegt. Beim ersten Login verlangt der Admin-Bereich die Einrichtung der Zwei-Faktor-Authentifizierung (Authenticator-App bereithalten). + +--- + +## 3. Microsoft Entra ID: App-Registrierung + +**Eine** App-Registrierung erledigt zwei Aufgaben: + +- **„Mit Microsoft anmelden“** (Single Sign-on im Kundenkonto) +- **Mailversand** über Microsoft 365 per OAuth 2.0 — ohne Passwort + +Eine zweite Unternehmensanwendung ist nicht nötig. + +### 3.1 App registrieren + +1. **Entra Admin Center** → Identität → Anwendungen → **App-Registrierungen** → **Neue Registrierung**. +2. **Name:** z. B. `Wabenhain Shop`. +3. **Unterstützte Kontotypen:** **„Nur Konten in diesem Organisationsverzeichnis“** (Single Tenant). +4. **Umleitungs-URI:** Plattform **Web**, Adresse **`https://wabenhain.com/api/auth/entra/callback`** — zeichengenau, mit `https`, ohne Schrägstrich am Ende. +5. **Registrieren**. + +Auf der Seite **Übersicht** notieren: + +| Angezeigt als | Gehört in | +|---|---| +| Anwendungs-ID (Client-ID) | `ENTRA_CLIENT_ID` | +| Verzeichnis-ID (Mandanten-ID) | `ENTRA_TENANT_ID` | + +`ENTRA_REDIRECT_URI` ist die Adresse aus Schritt 4: `https://wabenhain.com/api/auth/entra/callback`. + +### 3.2 Geheimen Clientschlüssel anlegen + +1. In der App → **Zertifikate & Geheimnisse** → **Geheime Clientschlüssel** → **Neuer geheimer Clientschlüssel**. +2. Beschreibung z. B. `Shop 2026`, Ablauf **24 Monate** (Maximum). +3. **Sofort** die Spalte **„Wert“** kopieren → `ENTRA_CLIENT_SECRET`. + +> ⚠ **Nicht die „Geheimnis-ID“** kopieren — das ist der häufigste Fehler (Microsoft meldet dann `AADSTS7000215`). Der Wert wird nur dieses eine Mal angezeigt; ist er weg, einen neuen Schlüssel anlegen. +> +> **Ablaufdatum sofort in den Kalender eintragen**, mit Erinnerung vier Wochen vorher. Läuft der Schlüssel ab, fallen Microsoft-Anmeldung **und** Mailversand gleichzeitig aus (siehe [Wartung](#10-wartung)). + +### 3.3 API-Berechtigungen + +In der App → **API-Berechtigungen**. Es braucht zwei Arten: + +| Berechtigung | API | Typ | Wofür | +|---|---|---|---| +| `openid`, `email`, `profile`, `User.Read` | Microsoft Graph | **Delegiert** | Anmeldung („Mit Microsoft anmelden“) | +| `SMTP.SendAsApp` | **Office 365 Exchange Online** | **Anwendung** | Mailversand | + +`User.Read` ist meist schon vorhanden. Die übrigen hinzufügen: + +**Für die Anmeldung** (falls noch nicht vorhanden): +1. **Berechtigung hinzufügen** → **Microsoft Graph** → **Delegierte Berechtigungen**. +2. `openid`, `email`, `profile` anhaken → **Berechtigungen hinzufügen**. + +**Für den Mailversand:** +1. **Berechtigung hinzufügen** → Reiter **„Von meiner Organisation verwendete APIs“**. +2. Nach **Office 365 Exchange Online** suchen und auswählen. (Die Berechtigung liegt **nicht** unter Microsoft Graph.) +3. **Anwendungsberechtigungen** (nicht „Delegierte“) → **`SMTP.SendAsApp`** anhaken → **Berechtigungen hinzufügen**. + +**Danach:** **„Administratorzustimmung für <Organisation> erteilen“** klicken und bestätigen. In der Liste muss bei jeder Berechtigung ein grünes **„Erteilt für …“** stehen. + +> **Was `SMTP.SendAsApp` erlaubt:** Die App darf per SMTP senden — aber **nur** mit Postfächern, die ihr in Exchange ausdrücklich freigegeben werden (Abschnitt 4). Ohne diese Freigabe kann sie nichts senden. + +### 3.4 Objekt-ID der Unternehmensanwendung notieren + +1. Entra → Identität → Anwendungen → **Unternehmensanwendungen**. +2. Filter „Anwendungstyp“ auf **Alle Anwendungen** stellen, nach `Wabenhain Shop` suchen, öffnen. +3. Auf der **Übersicht** die **Objekt-ID** kopieren. + +> ⚠ Das ist **eine andere Objekt-ID** als die auf der Seite der App-Registrierung. Für Exchange zählt nur die der **Unternehmensanwendung**. + +--- + +## 4. Microsoft 365: Absender-Postfach freischalten + +Alle Shop-Mails (Bestellbestätigung, Versandmeldung, Passwort-Reset, Widerrufsbestätigung …) gehen von **einem** Postfach aus, z. B. `hallo@wabenhain.com`. + +**Empfohlen: ein freigegebenes Postfach (Shared Mailbox).** Es braucht keine Lizenz und hat kein Passwort, mit dem sich jemand anmelden könnte. Die App sendet ohne Anmeldung einer Person. + +### 4.1 Freigegebenes Postfach anlegen (falls noch nicht vorhanden) + +1. **Microsoft 365 Admin Center** → **Teams und Gruppen** → **Freigegebene Postfächer** → **Freigegebenes Postfach hinzufügen**. +2. Name `Wabenhain`, E-Mail `hallo@wabenhain.com` → **Änderungen speichern**. +3. Optional: Mitglieder hinzufügen, die Antworten der Kundschaft lesen sollen. + +### 4.2 App für genau dieses Postfach freischalten + +In **PowerShell** (als Exchange-Administrator). Platzhalter ersetzen: + +- `` = `ENTRA_CLIENT_ID` aus 3.1 +- `` = Objekt-ID der **Unternehmensanwendung** aus 3.4 + +```powershell +Connect-ExchangeOnline + +# 1. App in Exchange bekannt machen +New-ServicePrincipal -AppId -ObjectId -DisplayName "Wabenhain Shop" + +# 2. Zugriff NUR auf das Absender-Postfach +Add-MailboxPermission -Identity "hallo@wabenhain.com" -User -AccessRights FullAccess + +# 3. Authentifiziertes SMTP für dieses Postfach erlauben (auch OAuth läuft darüber) +Set-CASMailbox -Identity "hallo@wabenhain.com" -SmtpClientAuthenticationDisabled $false +``` + +**Empfohlene Absicherung:** Authentifiziertes SMTP für alle **anderen** Postfächer abschalten. Die Einstellung am Absender-Postfach aus Schritt 3 hat Vorrang, der Shop sendet weiter. + +```powershell +Set-TransportConfig -SmtpClientAuthenticationDisabled $true +``` + +### 4.3 Kontrolle + +```powershell +Get-ServicePrincipal | Where-Object DisplayName -eq "Wabenhain Shop" +Get-MailboxPermission -Identity "hallo@wabenhain.com" | Where-Object { $_.User -like "*Wabenhain*" } +Get-CASMailbox -Identity "hallo@wabenhain.com" | Format-List SmtpClientAuthenticationDisabled # muss False sein +Disconnect-ExchangeOnline -Confirm:$false +``` + +Änderungen in Exchange brauchen **bis zu 30 Minuten**, bis sie greifen. + +**Ergebnis für die Konfiguration:** + +| Wert | Wo | Inhalt | +|---|---|---| +| `SMTP_USER` | Secret | `hallo@wabenhain.com` | +| `SMTP_FROM` | ConfigMap | `Wabenhain ` — **dieselbe Adresse** wie `SMTP_USER` | + +Server (`smtp.office365.com`), Port (587, STARTTLS) und Anmeldeverfahren (OAuth 2.0) sind fest im Code — dafür gibt es keine Einstellungen. Ein SMTP-Passwort gibt es nicht. + +--- + +## 5. Stripe + +Im **Stripe-Dashboard**, **Live-Modus** (Schalter oben rechts, nicht „Testmodus“). + +### 5.1 API-Schlüssel + +**Entwickler** → **API-Schlüssel**: + +| Angezeigt als | Gehört in | Wo | +|---|---|---| +| Veröffentlichbarer Schlüssel (`pk_live_…`) | `STRIPE_PUBLISHABLE_KEY` | ConfigMap (nicht geheim) | +| Geheimer Schlüssel (`sk_live_…`) | `STRIPE_SECRET_KEY` | Secret | + +Sicherer als der volle geheime Schlüssel ist ein **eingeschränkter Schlüssel** (`rk_live_…`) mit Schreibrecht auf Checkout Sessions, Refunds und Leserecht auf Charges/Payment Intents. + +> Beide Schlüssel müssen aus **demselben Modus** stammen. Mit `sk_test` sehen Bestellungen erfolgreich aus, ohne dass Geld fließt. + +### 5.2 Webhook + +**Entwickler** → **Webhooks** → **Endpunkt hinzufügen**: + +- **URL:** `https://wabenhain.com/api/checkout/webhook` +- **Ereignisse:** + - `checkout.session.completed` + - `checkout.session.async_payment_succeeded` + - `checkout.session.async_payment_failed` + - `checkout.session.expired` + - `charge.refunded` + - `charge.dispute.created` + +Nach dem Anlegen **Signaturgeheimnis** anzeigen (`whsec_…`) → `STRIPE_WEBHOOK_SECRET` im Secret. + +> Ohne Webhook-Secret wird keine Zahlung bestätigt: Bestellungen bleiben auf „Neu“, es gibt weder Rechnung noch Bestätigungsmail. + +### 5.3 Zahlungsarten + +**Einstellungen** → **Zahlungsmethoden**: gewünschte Arten aktivieren (Karte, SEPA-Lastschrift, PayPal, Klarna, Apple Pay, Google Pay). Der Shop zeigt automatisch an, was dort aktiv ist. Für Apple Pay die Domain `wabenhain.com` unter **Apple Pay** registrieren. + +--- + +## 6. Optionale Dienste + +Alle folgenden Werte dürfen leer bleiben. Die Funktion ruht dann, der Shop läuft weiter. Ausführliche Anleitungen stehen in `.env.example` im jeweiligen Abschnitt. + +| Dienst | Werte | Ohne sie | Einrichtung | +|---|---|---|---| +| **Cloudflare Turnstile** (CAPTCHA) — *dringend empfohlen* | `TURNSTILE_SECRET_KEY` (Secret), `NEXT_PUBLIC_TURNSTILE_SITE_KEY` (ConfigMap) | Keine Bot-Abwehr an Formularen | dash.cloudflare.com → Turnstile → Widget für `wabenhain.com` anlegen. Beide setzen oder beide leer. | +| **Google-Rezensionen** | `GOOGLE_PLACES_API_KEY` (Secret), `GOOGLE_PLACE_ID` (ConfigMap) | Statische Beispiel-Stimmen | Google Cloud: „Places API (New)“ aktivieren, Abrechnung an, API-Schlüssel ohne Referrer-Beschränkung | +| **DHL** (Versandmarken, Packstation, Sendungsverfolgung) | `DHL_API_KEY`, `DHL_API_SECRET`, `DHL_GKP_USERNAME`, `DHL_GKP_PASSWORD`, `DHL_ACCOUNT_NUMBER`, `DHL_TRACKING_API_KEY` (Secret), `DHL_ENVIRONMENT` (ConfigMap) | Kein Etikettendruck, keine Packstation im Checkout | developer.dhl.com, Geschäftskundenvertrag — Schritt für Schritt in `.env.example` | +| **ApiCore-Synchronisierung** | `APICORE_SYNC_EMAIL`, `APICORE_SYNC_PASSWORD` (Secret), `APICORE_API_URL` (ConfigMap) | Sync-Knopf meldet Konfigurationsfehler | Eigenes, MFA-freies Service-Konto mit nur lesenden Rechten, siehe `.env.example` | + +--- + +## 7. Secret und ConfigMap ausfüllen + +### 7.1 Secret (`k8s/02-secret.yaml`) + +```bash +cp k8s/02-secret.example.yaml k8s/02-secret.yaml +``` + +`k8s/02-secret.yaml` öffnen und jeden Wert aus der Tabelle eintragen. Werte in **doppelten Anführungszeichen** lassen (`stringData` — Kubernetes kodiert selbst, **kein** base64 von Hand). + +| Schlüssel | Pflicht | Inhalt | Quelle | +|---|---|---|---| +| `DATABASE_URL` | ja | Verbindungs-URL mit **URL-kodiertem** Passwort | Abschnitt 2 | +| `DB_PASSWORD` | ja | Datenbank-Passwort **roh** | Abschnitt 2 | +| `SESSION_SECRET` | ja | ≥ 32 zufällige Zeichen | Abschnitt 2 | +| `ADMIN_EMAIL` | ja | nicht erratbare Admin-Adresse | Abschnitt 2 | +| `ADMIN_PASSWORD` | ja | ≥ 12 Zeichen, zufällig | Abschnitt 2 | +| `STRIPE_SECRET_KEY` | ja | `sk_live_…` bzw. `rk_live_…` | 5.1 | +| `STRIPE_WEBHOOK_SECRET` | ja | `whsec_…` | 5.2 | +| `SMTP_USER` | ja | `hallo@wabenhain.com` | 4 | +| `ENTRA_TENANT_ID` | ja* | Verzeichnis-ID | 3.1 | +| `ENTRA_CLIENT_ID` | ja* | Anwendungs-ID | 3.1 | +| `ENTRA_CLIENT_SECRET` | ja* | **Wert** des Clientschlüssels | 3.2 | +| `ENTRA_REDIRECT_URI` | ja* | `https://wabenhain.com/api/auth/entra/callback` | 3.1 | +| `TURNSTILE_SECRET_KEY` | empfohlen | Turnstile-Secret | 6 | +| `GOOGLE_PLACES_API_KEY` | nein | Google-API-Schlüssel | 6 | +| `APICORE_SYNC_EMAIL`, `APICORE_SYNC_PASSWORD` | nein | Service-Konto | 6 | +| `DHL_*` (6 Werte) | nein | DHL-Zugangsdaten | 6 | + +\* Ohne die `ENTRA_*`-Werte gibt es weder „Mit Microsoft anmelden“ **noch Mailversand** — keine Bestellbestätigungen, keine Passwort-Resets. Für den Live-Betrieb daher Pflicht. + +**Nicht mehr verwendet** (aus älteren Secrets entfernen): `SMTP_HOST`, `SMTP_PORT`, `SMTP_PASSWORD`, `SMTP_OAUTH_TENANT_ID`, `SMTP_OAUTH_CLIENT_ID`, `SMTP_OAUTH_CLIENT_SECRET`. + +### 7.2 ConfigMap (`k8s/01-configmap.yaml`) + +Nur diese Werte anpassen, der Rest sind sinnvolle Vorgaben: + +| Schlüssel | Inhalt | +|---|---| +| `NEXT_PUBLIC_SITE_URL` | `https://wabenhain.com` — muss zu `host` in `k8s/06-ingress.yaml` passen | +| `SMTP_FROM` | `Wabenhain ` — dieselbe Adresse wie `SMTP_USER` | +| `DATABASE_PROVIDER` | `postgresql` (oder `mysql`, passend zu `DATABASE_URL`) | +| `STRIPE_PUBLISHABLE_KEY` | `pk_live_…` | +| `NEXT_PUBLIC_TURNSTILE_SITE_KEY` | Turnstile-Site-Key (empfohlen) | +| `VAT_RATE_PERCENT*` | Umsatzsteuersätze prüfen (Honig 7 %, Kerzen 19 %, Propolis je nach Einordnung) | +| `DHL_ENVIRONMENT` | `sandbox`, bis DHL getestet ist, dann `production` | +| `GOOGLE_PLACE_ID`, `APICORE_API_URL` | nur bei Nutzung | + +--- + +## 8. Ausrollen + +Erstmalig, in dieser Reihenfolge (Details und Hintergründe: `k8s/README.md`): + +```bash +kubectl apply -f k8s/00-namespace.yaml +kubectl apply -f k8s/01-configmap.yaml +kubectl apply -f k8s/02-secret.yaml + +# Nur EINE Datenbank, passend zu DATABASE_PROVIDER: +kubectl apply -f k8s/03-postgres.yaml +kubectl wait --for=condition=ready pod -l app=wabenhain-postgres -n wabenhain --timeout=120s + +kubectl apply -f k8s/04-uploads-pvc.yaml +kubectl apply -f k8s/05-app-deployment.yaml +kubectl apply -f k8s/06-ingress.yaml +kubectl apply -f k8s/07-backup-cronjob.yaml + +kubectl rollout status deployment/wabenhain-app -n wabenhain +``` + +Datenbank-Migrationen und das Anlegen des Admin-Kontos laufen beim Start des Pods automatisch. + +**Später einen Wert ändern:** Datei anpassen, erneut anwenden und den Pod neu starten — die App liest Secrets und ConfigMap nur beim Start: + +```bash +kubectl apply -f k8s/02-secret.yaml # bzw. 01-configmap.yaml +kubectl rollout restart deployment/wabenhain-app -n wabenhain +``` + +Einzelnen Wert ohne Datei ändern bzw. entfernen: + +```bash +kubectl -n wabenhain patch secret wabenhain-secrets --type=merge -p '{"stringData":{"SMTP_USER":"hallo@wabenhain.com"}}' +kubectl -n wabenhain patch secret wabenhain-secrets --type=json -p '[{"op":"remove","path":"/data/SMTP_PASSWORD"}]' +``` + +> Wer `k8s/02-secret.yaml` lokal pflegt, trägt jede Änderung auch dort ein — sonst setzt das nächste `kubectl apply` den alten Stand zurück. + +--- + +## 9. Prüfen + +Der Reihe nach: + +1. **Vollständigkeit der Konfiguration** im laufenden Pod: + ```bash + kubectl -n wabenhain exec deploy/wabenhain-app -- node_modules/.bin/tsx scripts/preflight.ts + ``` + Jeder fehlende Pflichtwert erscheint als Blocker mit Begründung. +2. **Shop erreichbar:** `https://wabenhain.com` öffnen. +3. **Admin-Login:** `https://wabenhain.com/konto` mit `ADMIN_EMAIL`/`ADMIN_PASSWORD` anmelden → Zwei-Faktor-Authentifizierung einrichten → Admin-Bereich öffnet sich. +4. **Mailversand:** **Admin → E-Mail-Test** → „Test-Mail senden“. Erfolg: grüne Meldung und eine Mail an die Admin-Adresse. Fehler: die Seite zeigt die Meldung von Microsoft und was zu tun ist (siehe [Fehlerbehebung](#11-fehlerbehebung)). +5. **Microsoft-Anmeldung:** abmelden, auf `/konto` „Mit Microsoft anmelden“ wählen. +6. **Firmendaten:** **Admin → Inhalte → Firmendaten** — Name, Anschrift und eine **gültige** E-Mail-Adresse eintragen. Sie erscheinen im Impressum und in der Widerrufsbelehrung. +7. **Bezahlung:** eine kleine echte Bestellung aufgeben, bezahlen, im Admin prüfen: Status „Bezahlt“, Rechnung vorhanden, Bestätigungsmail angekommen. Danach im Admin erstatten. +8. **Widerruf:** im Footer „Vertrag widerrufen“ testen → Eingangsbestätigung kommt an, Eintrag erscheint unter **Admin → Widerrufe**. + +--- + +## 10. Wartung + +| Was | Läuft ab / ändert sich | Folge, wenn verpasst | Was tun | +|---|---|---|---| +| **Entra-Clientschlüssel** (`ENTRA_CLIENT_SECRET`) | nach höchstens **24 Monaten** | Microsoft-Anmeldung **und** Mailversand fallen gleichzeitig aus | Vier Wochen vorher neuen Schlüssel anlegen (3.2), Secret ersetzen, Pod neu starten, **E-Mail-Test**, danach alten Schlüssel in Entra löschen | +| **Stripe-Schlüssel** | bei Verdacht auf Abfluss | Fremde könnten Erstattungen auslösen | Im Stripe-Dashboard „Schlüssel rollen“, Secret ersetzen, Pod neu starten | +| **`SESSION_SECRET`** | bei Verdacht auf Abfluss | Sitzungen fälschbar | Neuen Wert erzeugen, ersetzen, neu starten (meldet alle ab) | +| **DHL-/Google-/Turnstile-Schlüssel** | je nach Anbieter | Funktion ruht | Beim Anbieter erneuern, Secret ersetzen | +| **Postfach-Freigabe** | bei Umbenennung oder neuem Absender-Postfach | Mailversand scheitert („Exchange lässt die App nicht …“) | Abschnitt 4.2 für das neue Postfach wiederholen, `SMTP_USER` und `SMTP_FROM` anpassen | + +Nach **jeder** Änderung an Secret oder ConfigMap: `kubectl rollout restart deployment/wabenhain-app -n wabenhain` und kurz **Admin → E-Mail-Test**. + +--- + +## 11. Fehlerbehebung + +### Mailversand (Anzeige in **Admin → E-Mail-Test**) + +| Meldung / Ursache | Lösung | +|---|---| +| „nicht vollständig eingerichtet“ + Liste fehlender Werte | Genannte Werte ins Secret, Pod neu starten | +| `AADSTS7000215` — Client-Secret falsch | In `ENTRA_CLIENT_SECRET` steht die Geheimnis-ID statt des **Werts** → neuen Schlüssel anlegen (3.2) | +| `AADSTS7000222` — Client-Secret abgelaufen | Neuen Schlüssel anlegen (3.2), ersetzen, neu starten | +| `AADSTS700016` — App nicht gefunden | `ENTRA_CLIENT_ID` und `ENTRA_TENANT_ID` gehören nicht zusammen | +| `AADSTS90002` / `900023` — Tenant ungültig | `ENTRA_TENANT_ID` prüfen (Verzeichnis-ID) | +| „Exchange lässt die App nicht mit dem Postfach senden“ (`535 5.7.3`) | (1) `SMTP.SendAsApp` erteilt? (3.3) (2) `New-ServicePrincipal` mit der Objekt-ID der **Unternehmensanwendung**? (3.4/4.2) (3) `Add-MailboxPermission` für genau `SMTP_USER`? (4) 30 Minuten gewartet? | +| „Authentifiziertes SMTP ausgeschaltet“ (`SmtpClientAuthentication is disabled`) | `Set-CASMailbox -Identity "hallo@wabenhain.com" -SmtpClientAuthenticationDisabled $false` | +| „SendAsDenied“ | `SMTP_FROM` ist nicht die Adresse aus `SMTP_USER` → angleichen | +| „nicht erreichbar“ (Netzwerk) | Cluster muss ausgehend `smtp.office365.com:587` und `login.microsoftonline.com:443` erreichen (Firewall, NetworkPolicy) | + +Ausführliche Fehler stehen außerdem im Pod-Log: + +```bash +kubectl logs deploy/wabenhain-app -n wabenhain | grep "\[mail\]" +``` + +### Microsoft-Anmeldung + +| Symptom | Lösung | +|---|---| +| Knopf „Mit Microsoft anmelden“ fehlt | Einer der vier `ENTRA_*`-Werte fehlt | +| Microsoft meldet „redirect_uri mismatch“ (`AADSTS50011`) | `ENTRA_REDIRECT_URI` und die Umleitungs-URI in der App-Registrierung (3.1) müssen zeichengenau gleich sein | + +### Sonstiges + +| Symptom | Lösung | +|---|---| +| Pod startet nicht, Log nennt Datenbankfehler | `DATABASE_URL`: Passwort URL-kodiert? Host passend zum Datenbank-Manifest? | +| Bestellungen bleiben auf „Neu“ | Stripe-Webhook (5.2): URL, Ereignisse und `STRIPE_WEBHOOK_SECRET` prüfen; im Stripe-Dashboard unter Webhooks die Zustellversuche ansehen | +| Bilder weg nach Neustart | `k8s/README.md`, Abschnitt „Bilder/Videos verschwinden nach jedem Redeploy“ | diff --git a/k8s/README.md b/k8s/README.md index 44953c0..56a4be4 100644 --- a/k8s/README.md +++ b/k8s/README.md @@ -3,6 +3,10 @@ Rohe Manifeste, mit `kubectl apply -f k8s/` einzeln oder als Ordner anwendbar. Nummerierung = empfohlene Reihenfolge. +Woher jeder Wert in `02-secret.yaml` und `01-configmap.yaml` kommt (Entra ID, +Microsoft 365, Stripe, optionale Dienste) und wie man danach prueft, dass +alles laeuft: [../docs/EINRICHTUNG.md](../docs/EINRICHTUNG.md). + ## Einmalige Vorbereitung 1. **Gitea Actions**: `.gitea/workflows/docker-build.yml` baut bei jedem