Übernimmt je Owner den Namen seiner Legacy-Agentur-Firma(en) ins neue Profilfeld (A2 des Umbaus Agentur-Attribution → User-Profil). Regeln: zuletzt aktualisierter Datensatz gewinnt bei Mehrfach-Namen (alle Varianten im Report), bereits gesetzte Werte werden nie überschrieben, Owner-lose Agentur-Firmen werden nur reportet. --dry-run + JSON-Report nach storage/app/private/migration/; im Deploy-Runbook (Erst-Einrichtung) nach dem Firmen-/User-Import eingereiht. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
157 lines
6.5 KiB
Markdown
157 lines
6.5 KiB
Markdown
# Deploy-Runbook — Pressekonto (Laravel-System)
|
||
|
||
**Stand:** 03.07.2026 · **Gilt für:** das neue Laravel-System (Hub pressekonto + Spokes presseecho/businessportal24).
|
||
`FTP-LIVE-DEPLOY.md` betrifft nur das Alt-Symfony-System und ist für dieses System **nicht** anwendbar.
|
||
|
||
> Referenz-Plan: `dev/release/entwicklungsplan-release.md` (R5.2). Externe Voraussetzungen
|
||
> (Stripe-Live-Keys, echte Mail-/Kontaktadressen, Favicons, finale Rechtstexte) siehe dort.
|
||
|
||
---
|
||
|
||
## 1. Server-Voraussetzungen
|
||
|
||
- PHP 8.4 (FPM) mit Extensions: `pdo_mysql`, `mbstring`, `intl`, `gd` (Bildvarianten), `curl`, `zip`
|
||
- MySQL/MariaDB (Prod: `DB_CONNECTION=mysql`; SQLite ist nur Dev)
|
||
- Node.js (LTS) + npm — nur zum Bauen der Assets (Builds sind **gitignored**, siehe Schritt 3)
|
||
- Webserver-vHosts für **alle drei Domains** auf dasselbe `public/` (Host-Routing macht die App)
|
||
- HTTPS-Zertifikate für alle drei Domains
|
||
- Prozess-Manager (systemd/Supervisor) für den Queue-Worker
|
||
|
||
**Wichtig (SEO):** Keine statische `public/robots.txt` anlegen — robots.txt und sitemap.xml
|
||
sind dynamische, host-aware Laravel-Routen; eine echte Datei würde sie verschatten.
|
||
|
||
## 2. `.env` (Produktion) — Solltabelle
|
||
|
||
Basis: `.env.example` kopieren und anpassen. Abweichend von den Dev-Defaults zwingend:
|
||
|
||
| Key | Prod-Wert |
|
||
|---|---|
|
||
| `APP_ENV` | `production` |
|
||
| `APP_DEBUG` | `false` |
|
||
| `APP_KEY` | `php artisan key:generate` (einmalig) |
|
||
| `APP_URL` / `APP_PORTAL_*` / `APP_PRESSEECHO_*` / `APP_BUSINESSPORTAL_*` | echte Domains (pressekonto.de, presseecho.de, businessportal24.com) |
|
||
| `SANCTUM_STATEFUL_DOMAINS` | echte Domains, kommasepariert |
|
||
| `DB_CONNECTION` + `DB_*` | MySQL-Zugangsdaten |
|
||
| `MAIL_MAILER` + `MAIL_*` | echter Mailer — **Default `log` versendet nichts!** |
|
||
| `MAIL_FROM_ADDRESS` | echte Absenderadresse |
|
||
| `SESSION_SECURE_COOKIE` | `true` |
|
||
| `BASIC_AUTH_ENABLED` | Staging: `true` + User/Passwort · Live: `false` |
|
||
| `BILLING_ENFORCE_BOOKING` | `true` (Default) |
|
||
| `STRIPE_KEY/SECRET/WEBHOOK_SECRET` + `STRIPE_PRICE_SINGLE_PM` | **Live**-Werte [EXTERN R1.2] |
|
||
| `BILLING_OWN_VAT_ID` | echte USt-IdNr. |
|
||
| `OPENAI_API_KEY` | Prod-Key (ohne ihn keine Klassifikation/kein Score) |
|
||
| `CONTACT_EMAIL_*` | echte Empfängeradressen [EXTERN R2.1] |
|
||
| `GOOGLE_CLIENT_ID/SECRET/REDIRECT_URI` | Live-OAuth-Client (Redirect-URI exakt) |
|
||
|
||
## 3. Build & Release-Schritte
|
||
|
||
```bash
|
||
# 1) Code auslegen (git pull / Release-Verzeichnis)
|
||
composer install --no-dev --optimize-autoloader
|
||
|
||
# 2) Assets bauen (Builds sind gitignored — auf dem Server oder in CI bauen
|
||
# und public/build/ mit ausliefern)
|
||
npm ci
|
||
npm run build # baut portal + web (public/build/portal, public/build/web)
|
||
|
||
# 3) Storage-Symlink (Firmenlogos, PM-Bilder)
|
||
php artisan storage:link
|
||
|
||
# 4) Datenbank
|
||
php artisan migrate --force
|
||
|
||
# 5) Seeder — NUR die Stammdaten-Seeder. DatabaseSeeder ist prod-sicher
|
||
# (Demo-User nur in local/testing), aber explizit ist besser:
|
||
php artisan db:seed --class=RolesAndPermissionsSeeder --force
|
||
php artisan db:seed --class=AdminPresetSeeder --force
|
||
php artisan db:seed --class=PlanSeeder --force
|
||
php artisan db:seed --class=PaymentOptionSeeder --force
|
||
|
||
# 6) Caches bauen (bei JEDEM Deploy neu)
|
||
php artisan config:cache
|
||
php artisan route:cache
|
||
php artisan view:cache
|
||
php artisan event:cache
|
||
|
||
# 7) Queue-Worker neu starten (lädt neuen Code)
|
||
php artisan queue:restart
|
||
```
|
||
|
||
## 4. Queue-Worker (Pflicht)
|
||
|
||
Die KI-Jobs (`ClassifyPressRelease`, `ScorePressRelease`) laufen auf der
|
||
database-Queue **`classification`** — ohne Worker bleibt jede eingereichte PM
|
||
in der Prüfung hängen.
|
||
|
||
systemd-Beispiel (`/etc/systemd/system/pressekonto-worker.service`):
|
||
|
||
```ini
|
||
[Unit]
|
||
Description=Pressekonto Queue Worker
|
||
After=network.target
|
||
|
||
[Service]
|
||
User=www-data
|
||
Restart=always
|
||
ExecStart=/usr/bin/php /var/www/pressekonto/artisan queue:work database \
|
||
--queue=classification,default --tries=3 --max-time=3600
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
```
|
||
|
||
## 5. Scheduler / Cron (Pflicht)
|
||
|
||
Crontab des Web-Users:
|
||
|
||
```cron
|
||
* * * * * cd /var/www/pressekonto && php artisan schedule:run >> /dev/null 2>&1
|
||
```
|
||
|
||
Aktive Tasks (`php artisan schedule:list`):
|
||
|
||
| Zeitplan | Task | Ohne Cron passiert … |
|
||
|---|---|---|
|
||
| alle 5 Min | `press-releases:publish-scheduled` | **geplante PMs werden nie veröffentlicht** |
|
||
| täglich 03:00 | `magic-links:purge --days=30` | Token-Tabelle wächst |
|
||
| täglich 04:30 | `billing:generate-manual-invoices` | manuelle Rechnungen fehlen |
|
||
| So 04:00 | `press-releases:purge-drafts --days=180` | Alt-Entwürfe bleiben liegen |
|
||
| monatl. 1. 00:05 | `press-releases:reset-monthly-quota` | **Kontingente werden nie zurückgesetzt** |
|
||
|
||
## 6. Stripe-Webhook
|
||
|
||
Endpoint in Stripe (Live-Mode) registrieren: `https://pressekonto.de/stripe/webhook`
|
||
→ Signing-Secret als `STRIPE_WEBHOOK_SECRET` in die `.env`.
|
||
Events: die Cashier-Defaults (Subscriptions, Invoices, Payment-Intents).
|
||
|
||
## 7. Erst-Einrichtung (nur beim allerersten Go-Live)
|
||
|
||
1. Legacy-Datenimport (Reihenfolge beachten, read-only-Quellen konfiguriert):
|
||
`legacy:import` → `legacy:fix-timestamps` → `legacy:migrate-media`
|
||
(bzw. `legacy:sync-company-logos` / `legacy:sync-press-release-images`)
|
||
→ `legacy:archive-invoices` → `legacy:grandfather-subscriptions`.
|
||
2. Agentur-Attribution (läuft NACH dem Firmen-/User-Import aus Schritt 1):
|
||
`legacy:migrate-agency-names` — erst mit `--dry-run` (Report unter
|
||
`storage/app/private/migration/` prüfen, insbes. Mehrfach-Namen und
|
||
Owner-lose Agentur-Firmen), dann scharf. Nie überschreibend.
|
||
3. Ersten Admin-User anlegen (Prod hat keine Demo-User):
|
||
Registrierung + Rolle via Tinker/DB oder dedizierter Befehl.
|
||
4. Favicons je Domain deployen (Platzhalter-Generator reicht nicht) [EXTERN R5.4].
|
||
5. Smoke-Test: siehe Abschnitt 8.
|
||
|
||
## 8. Smoke-Test nach jedem Deploy
|
||
|
||
- [ ] Alle drei Domains laden (Hub-Landing, 2× Spoke-Home) über HTTPS
|
||
- [ ] `/robots.txt` + `/sitemap.xml` je Domain liefern hostkorrekte Inhalte
|
||
- [ ] Login am Portal funktioniert; `/admin` erreichbar
|
||
- [ ] Eine Test-PM einreichen → Klassifikation läuft durch (Worker-Check)
|
||
- [ ] Kontaktformular + Widerrufs-/Kündigungsformular versenden Mails (echter Mailer!)
|
||
- [ ] Stripe-Checkout im Live-Mode mit kleiner Testbuchung + Webhook-Eingang prüfen
|
||
- [ ] `storage/logs/laravel.log` auf Fehler prüfen
|
||
|
||
## 9. Rollback
|
||
|
||
- Code: vorheriges Release auschecken → Schritte 6–7 aus Abschnitt 3 wiederholen
|
||
(`config:cache` … `queue:restart`).
|
||
- Migrationen sind additiv gehalten; kein automatisches `migrate:rollback` in Prod —
|
||
im Fehlerfall DB-Backup einspielen (Backup **vor** jedem Deploy mit Migrationen!).
|