# 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. Ersten Admin-User anlegen (Prod hat keine Demo-User): Registrierung + Rolle via Tinker/DB oder dedizierter Befehl. 3. Favicons je Domain deployen (Platzhalter-Generator reicht nicht) [EXTERN R5.4]. 4. 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!).