Files
kaffeekasse-saas/docs/deployment.md
T
clemensandClaude Opus 5 9f9e4c7cd0 PayPal-Mailabruf: Parken statt Buchen, echter Probelauf, Regressionstest
Die Mail-Verarbeitung ignorierte bisher die PayPal-Schalter: eingehende
Zahlungen wurden auch dann automatisch gutgeschrieben, wenn der Betreiber
die Funktion gesperrt oder der Mandant PayPal abgeschaltet hatte. Jetzt
wird die Zahlung in dem Fall geparkt - gespeichert, aber ohne Buchung.
Wegwerfen liesse eine echte Zahlung unbemerkt verschwinden, buchen
widersprache der Abschaltung; die Zuordnungsseite bleibt fuer offene
Zahlungen ja erreichbar.

--dry-run war bisher irrefuehrend: es liess die Verarbeitung samt Buchung
laufen und uebersprang nur das Setzen des Gelesen-Flags - ausgerechnet beim
ersten Testlauf haette es also echtes Geld verbucht. Der Probelauf nutzt
jetzt paypal_preview(), das nichts schreibt und meldet, was passieren
wuerde (would_book/would_queue/would_park/duplicate).

scripts/check-paypal-inbox-flow.php deckt die Kette ohne IMAP ab:
Absenderpruefung, Token, Parser, Zuordnung, Netto-Buchung, Dedup, beide
Park-Faelle und die Schreibfreiheit der Vorschau.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 20:43:58 +02:00

310 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Deployment und Go-Live
Stand: 2026-08-06
Dieses Dokument beschreibt, wie die Anwendung auf den Netcup-Webspace
(Plesk) ausgerollt und in Betrieb genommen wird. Es ergänzt
`docs/betrieb-backup-monitoring.md` (Backup/Monitoring) und
`docs/betrieb-mail.md` (Mailversand).
## Umgebungen
| Umgebung | Host | Webroot | Zweck |
| --- | --- | --- | --- |
| Staging | `testumgebung.kaffeeliste.de` | `/testumgebung.kaffeeliste.de/httpdocs/` | Vollständiger Durchlauf vor jedem Produktiv-Deploy |
| Produktion | `app.kaffeeliste.de` (App) + `kaffeeliste.de` (Marketing) | noch einzurichten | Echtbetrieb |
Beide Umgebungen brauchen **eigene Datenbanken** und **eigene**
`env.local.php`. Die Staging-Umgebung läuft mit Stripe-Testkeys, die
Produktion mit Live-Keys.
### Host-Split beachten
`index.php` prüft den Request-Host gegen `APP_HOST`: stimmt er nicht
überein, wird statt des Dashboards die Landingpage ausgeliefert. Auf
Staging muss deshalb `APP_HOST=testumgebung.kaffeeliste.de` gesetzt sein,
sonst kommt man nie ins Dashboard.
In Produktion zeigen beide Vhosts (`kaffeeliste.de` und
`app.kaffeeliste.de`) auf dasselbe Webroot; `APP_HOST=app.kaffeeliste.de`
sorgt dann automatisch dafür, dass die Marketing-Domain die Landingpage
und die App-Domain das Dashboard zeigt.
## Was deployt wird — und was nicht
Der Upload läuft über den FTP-Sync (`sync_config.jsonc`, Umgebung
`netcup`). Die Ausschlussliste dort ist sicherheitsrelevant und darf nicht
geleert werden:
Nicht ausliefern: `.git`, `.gitignore`, `.claude`, `.local`, `.vscode`,
`.env.local`, `env.local.php`, `sync_config.jsonc`, `var/`, `docs/`,
`README.md`.
- `.git` wäre sonst komplett herunterladbar (kompletter Quellcode plus
Historie).
- `.env.local` enthält die Zugangsdaten der Entwicklungsdatenbank.
- `sync_config.jsonc` enthält das FTP-Passwort im Klartext.
- `var/` ist Laufzeitzustand des Servers (Sessions, Uploads) und würde
vom lokalen Stand überschrieben.
`scripts/` und `database/` **werden** mit ausgeliefert — die
Plesk-Scheduled-Tasks führen sie vom Webspace aus. Per URL sind sie durch
die `.htaccess` gesperrt.
Zusätzlich sperrt die `.htaccess` `var|database|scripts|docs|app|lib`,
die Vendor-Verzeichnisse `TCPDF|PHPMailer|DataTables`, Dotfile-Ordner
sowie Konfigurationsdateien und `.sql`/`.md`/`.jsonc`/`.sh`/`.log`-Dateien.
Sie erzwingt außerdem HTTPS — ohne Redirect käme ein Nutzer per `http` an
und bekäme eine Session-Cookie ohne `secure`-Flag.
## Erstinbetriebnahme einer Umgebung
### 1. Hosting-Voraussetzungen prüfen
In Plesk unter *PHP-Einstellungen* bzw. über eine temporäre
`phpinfo()`-Datei kontrollieren:
- PHP ≥ 8.1
- Extensions: `pdo_mysql`, `fileinfo` (CSV-Upload), `openssl`, `imap`
(nur für den PayPal-Mailabruf nötig)
- **`allow_url_fopen = On`** — `app/stripe.php` und `app/dolibarr.php`
sprechen ihre APIs bewusst über PHP-Streams statt cURL an und fallen
ohne diese Einstellung aus
- `display_errors = Off`, `log_errors = On` (siehe
`docs/betrieb-backup-monitoring.md`)
- Let's-Encrypt-Zertifikat für den Host ausgestellt
Die temporäre `phpinfo()`-Datei danach wieder löschen.
### 2. Datenbank anlegen
In Plesk eine leere MySQL-Datenbank samt eigenem Benutzer anlegen. Die
Zugangsdaten werden gleich in `env.local.php` eingetragen.
Die vorhandene Entwicklungsdatenbank **nicht** übernehmen: sie enthält
ausschließlich Testdaten inklusive des migrierten Default-Mandanten aus
der Legacy-App.
### 3. Dateien hochladen
FTP-Sync ausführen. Danach prüfen, dass folgende URLs **403/404** liefern
und nicht etwa Inhalt:
```
https://<host>/sync_config.jsonc
https://<host>/.env.local
https://<host>/.git/config
https://<host>/scripts/migrate.php
https://<host>/docs/deployment.md
https://<host>/app/bootstrap.php
```
Liefert eine davon Inhalt, greift die `.htaccess` nicht (z. B. weil
`AllowOverride` deaktiviert ist) — dann erst weitermachen, wenn das
geklärt ist.
### 4. `env.local.php` anlegen
`env.local.example.php` als Vorlage nehmen, ausfüllen und **direkt per
FTP** als `env.local.php` ins Webroot legen. Die Datei ist bewusst nicht
im Repository und wird nicht mit deployt, damit ein Deploy sie nie
überschreibt.
Für Staging mindestens:
```php
putenv('APP_ENV=prod'); // 'dev' würde Mails nur ins Log schreiben
putenv('APP_HOST=testumgebung.kaffeeliste.de');
putenv('APP_TIMEZONE=Europe/Berlin');
putenv('APP_DB_DRIVER=mysql');
putenv('DB_HOST=localhost');
putenv('DB_NAME=…'); putenv('DB_USER=…'); putenv('DB_PASS=…');
putenv('APP_SESSION_PATH=' . __DIR__ . '/var/sessions');
putenv('APP_MAIL_TRANSPORT=mail');
putenv('APP_MAIL_FROM=noreply@kaffeeliste.de');
```
Stripe-, Dolibarr- und IMAP-Werte je Umgebung ergänzen (siehe unten).
### 5. Schreibrechte für `var/`
`app_start_session()` legt `var/sessions` bei Bedarf selbst an, braucht
dafür aber Schreibrechte im Webroot. Nach dem ersten Aufruf prüfen, dass
`var/sessions` existiert und beschreibbar ist; ebenso `var/uploads` für
den CSV-Import. Beide sind per `.htaccess` von außen gesperrt.
### 6. Migrationen einspielen
Es gibt 24 versionierte Migrationen in `database/migrations/`, angewendet
über `scripts/migrate.php`. Das Skript ist idempotent und wendet nur
fehlende Migrationen an.
Ohne SSH-Zugang läuft das über **Plesk → Geplante Aufgaben (Scheduled
Tasks)** als einmalig ausgeführter Task vom Typ *PHP-Skript ausführen*:
```
Skriptpfad: /httpdocs/scripts/migrate.php
```
Der Task muss nach dem Ausführen wieder deaktiviert oder gelöscht werden.
Die Ausgabe (`Applied migration: …` bzw. `No new migrations.`) lässt sich
per E-Mail-Benachrichtigung des Tasks kontrollieren.
Alternative ohne Scheduled Task: die Migrationsdateien der Reihe nach in
phpMyAdmin importieren — dann fehlt allerdings der Eintrag in der
Versionstabelle, den `migrate.php` pflegt. Der Scheduled-Task-Weg ist
deutlich vorzuziehen.
**Nach jedem Deploy mit neuen Migrationen wiederholen.**
### 7. Ersten Mandanten anlegen
Über `register.php` auf dem konfigurierten Host registrieren. Für
Plattform-Administrationsrechte (Back-Office über alle Mandanten hinweg)
anschließend `scripts/grant-platform-admin.php` als einmaligen
Plesk-Task ausführen.
## Wiederkehrende Aufgaben (Plesk Scheduled Tasks)
Ohne diese Tasks fehlen Zahlungserinnerungen, die automatische
PayPal-Verbuchung und — kritisch — die Backups.
| Task | Skript / Befehl | Empfohlener Takt |
| --- | --- | --- |
| Zahlungserinnerungen | `/httpdocs/scripts/send-payment-reminders.php` | täglich, z. B. 07:00 |
| PayPal-Mailabruf | `/httpdocs/scripts/fetch-paypal-payments.php` | alle 1530 Minuten |
| Datenbank-Backup | Shell-Befehl, siehe `docs/betrieb-backup-monitoring.md` | täglich nachts |
Zum Backup-Task: Das Zielverzeichnis muss **außerhalb** von `httpdocs`
liegen (z. B. `/backups` auf Vhost-Ebene), sonst wären die Dumps
öffentlich abrufbar. Zusätzlich mindestens eine Kopie an einen anderen
Ort übertragen — ein Backup, das nur auf demselben Webspace liegt,
schützt nicht gegen den Ausfall des Anbieters.
Vor der Scharfschaltung beide PHP-Tasks einmal manuell auslösen.
`send-payment-reminders.php` unterstützt dafür `--dry-run`.
## Mailversand und DNS
Der Versand läuft über PHPs `mail()` (`APP_MAIL_TRANSPORT=mail`, siehe
`docs/betrieb-mail.md`). Das betrifft Registrierungsbestätigung,
E-Mail-Verifikation, Passwort-Reset, Mitglieder-Einladungen,
Zahlungserinnerungen und den Massenmailversand — also den kompletten
Onboarding-Pfad.
Vor Go-Live für die Absenderdomain einrichten und prüfen:
- **SPF**-Eintrag, der den Mailserver des Webspace autorisiert
- **DKIM**-Signierung (in Plesk pro Domain aktivierbar)
- **DMARC**-Eintrag
- `APP_MAIL_FROM` auf eine echte Adresse der eigenen Domain setzen —
eine fremde Absenderdomain bricht SPF/DKIM
Testen mit einer Registrierung auf eine externe Adresse (Gmail/Outlook)
und Kontrolle, ob die Mail im Posteingang statt im Spam landet.
Fehlversuche stehen in der Tabelle `outbound_emails` mit
`status = 'failed'`.
## Stripe und Dolibarr
Details zur Funktionsweise stehen in `docs/billing.md`. Für den Go-Live:
1. **Staging** mit Stripe-**Test**-Keys betreiben und den kompletten
Ablauf einmal durchspielen: Checkout, Upgrade, Downgrade, Kündigung,
Customer Portal.
2. Webhook-Endpunkt im Stripe-Dashboard auf
`https://<host>/stripe-webhook.php` registrieren (je Umgebung einen
eigenen) und das dort erzeugte Signing Secret als
`STRIPE_WEBHOOK_SECRET` eintragen. Ohne gültiges Secret weist
`stripe-webhook.php` alle Ereignisse ab.
3. Für Produktion auf **Live**-Keys wechseln und den Webhook erneut
registrieren — Test- und Live-Modus haben getrennte Webhooks und
getrennte Secrets.
4. Dolibarr: `DOLIBARR_URL` und `DOLIBARR_API_KEY` eintragen. Achtung,
das ist die **produktive** Buchhaltungsinstanz des Kunden, es gibt
keine Sandbox. Der `validate()`-Aufruf beim Rechnungsabschluss ist
laut `docs/billing.md` bewusst nie live getestet worden — der erste
echte Zahlungseingang ist dessen erster Test. Danach in Dolibarr
kontrollieren, ob die Rechnung korrekt angelegt und validiert wurde.
## PayPal-Postfach
Für die automatische Verbuchung weitergeleiteter PayPal-Zahlungsmails
muss das Postfach aus `PAYPAL_INBOX_BASE` (Vorgabe
`zahlungen@kaffeeliste.de`) existieren und **Plus-Adressierung an
dieselbe Mailbox zustellen** (Catch-All), damit die pro Mandant
generierten Adressen `zahlungen+<token>@…` ankommen. IMAP-Zugangsdaten in
`env.local.php` eintragen und die `imap`-Extension auf dem Host prüfen.
### Erster Testlauf
`scripts/fetch-paypal-payments.php --dry-run` fasst nichts an: es meldet
Verbindung, erkannten Mandanten und was mit jeder Mail passieren *würde*
(`would_book` / `would_queue` / `would_park` / `duplicate`), speichert
aber nichts, bucht nichts und markiert keine Mail als gelesen. Erst der
Lauf ohne `--dry-run` verbucht.
Reihenfolge für die Inbetriebnahme:
1. Eingangsadresse des Mandanten holen — sie steht auf
`paypal-zuordnung.php` („PayPal-Zahlungen").
2. Eine echte PayPal-Zahlungsmail dorthin weiterleiten.
3. Task mit `--dry-run` auslösen, Ausgabe kontrollieren.
4. Task ohne `--dry-run` auslösen, Ergebnis in der App prüfen
(Journal-Vorschau bzw. Warteschlange).
5. Erst danach den Task auf den regelmäßigen Takt stellen.
Ohne IMAP lässt sich die Verarbeitung auch mit einer gespeicherten Mail
prüfen:
```
php scripts/fetch-paypal-payments.php --file=mail.html \
--recipient=zahlungen+<token>@kaffeeliste.de --from=service@paypal.de [--dry-run]
```
Die Verarbeitungskette selbst (Absenderprüfung, Token, Parser, Zuordnung,
Buchung, Dedup, Parken) deckt `scripts/check-paypal-inbox-flow.php` als
Regressionstest ab — alles außer der IMAP-Verbindung.
## Deploy-Ablauf für ein Update
1. Lokal committen und pushen.
2. FTP-Sync ausführen (Ausschlussliste greift automatisch).
3. Falls neue Migrationen dabei sind: Migrations-Task in Plesk einmal
auslösen.
4. Stichprobe: Login, Dashboard, eine Strich-Buchung, PDF-Export.
Ein echtes Rollback gibt es nicht; im Fehlerfall wird der vorherige
Commit-Stand erneut hochgeladen. Migrationen sind nicht rückwärts
lauffähig — vor einem Deploy mit Schemaänderungen daher immer ein
frisches Backup ziehen.
## Go-Live-Checkliste
Vor der Umstellung auf `app.kaffeeliste.de`:
- [ ] Kompletter Funktionsdurchlauf auf Staging erfolgreich
(Registrierung, Mailversand, Mitglieder, Striche, Einzahlungen,
CSV-Import, PDF-Export, Jahresauswertung, Stripe im Testmodus)
- [ ] Die sechs Abruf-Tests aus Schritt 3 liefern alle 403/404
- [ ] `display_errors` produktiv aus, `log_errors` an
- [ ] SPF/DKIM/DMARC gesetzt, Testmail landet nicht im Spam
- [ ] Backup-Task läuft und ein Restore wurde einmal testweise
eingespielt (`docs/betrieb-backup-monitoring.md`)
- [ ] Stripe-Live-Keys und Produktiv-Webhook eingetragen
- [ ] Impressum, AGB und Datenschutzerklärung inhaltlich freigegeben
- [ ] Auftragsverarbeitungsvertrag (AVV) für Kunden vorbereitet
- [ ] Frische Produktivdatenbank ohne Testdaten
## Bewusst offen
- **Content-Security-Policy**: nicht gesetzt, weil die Templates
durchgängig Inline-Styles nutzen. Begründung und Umfang in
`docs/m8-haertung.md`.
- **Automatisiertes Deployment** (CI/CD, Build-Schritt): nicht
eingerichtet. Bei einer flachen PHP-App ohne Build-Prozess und einem
einzelnen Deployer ist der FTP-Sync angemessen.
- **`DataTables/` und `js/`**: liegen im Repo, werden aber von keiner
Seite mehr eingebunden. Sie sind per `.htaccess` gesperrt und können
bei Gelegenheit entfernt werden.