Das System ist seit 20.07.2026 live; die dev-Doku spiegelt jetzt den Betriebszustand statt der Release-Vorbereitung: - dev/betrieb/: Deploy-Runbook (neu: §10 Betrieb nach Go-Live mit SFTP-Deploy-Checkliste inkl. queue:restart-Pflicht und den Betriebs-Erkenntnissen der Go-Live-Woche), Legacy-API-Parität, Umami. - dev/roadmap.md: offene Nachlauf-Punkte (Go-Live-Mails, Merge nach main, Stripe-Test-Artefakte, Key-Rotation, Alt-System-Abschaltung) und Ideen-Backlog. - dev/archive/: abgeschlossene Phasen — Migration-2026-Planung, Cutover-Runbook (mit Erledigt-Vermerk), Release-Plan/QA-Checkliste, Optimierungsphasen, Frontend-Konzepte + Responsive-Audits vom 16.07., Altsystem-Reste. - dev/README.md als Wegweiser; gitignore-Pfad der SQL-Dumps mitgezogen (dev/archive/migration-2026/sql/ bleibt untracked, ebenso dev/migration/ und dev/api/). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
169 lines
9.1 KiB
Markdown
169 lines
9.1 KiB
Markdown
# Cutover-Runbook — Go-Live Pressekonto
|
|
|
|
> ✅ **ERLEDIGT — archiviert am 20.07.2026.** Die Migration ist vollständig
|
|
> durchgelaufen (`legacy:verify`: 0 failures, 0 warnings), die echten Domains
|
|
> sind live geschaltet, Stripe läuft im Live-Mode. Dieses Runbook bleibt als
|
|
> Referenz erhalten — z. B. für einen späteren `--since`-Delta-Lauf, falls
|
|
> das Alt-System doch noch einmal Daten liefern sollte (Abschnitt 3).
|
|
> Betriebs-Doku ab jetzt: `dev/betrieb/deploy-runbook.md`.
|
|
|
|
Stand: 2026-07-14 · Branch `fix/zeitzone-europe-berlin`
|
|
|
|
Abhakbare Checkliste für die Umschaltung vom Alt-System auf Pressekonto.
|
|
Alle Commands laufen über Sail: `vendor/bin/sail artisan …` (bzw. ohne Sail
|
|
`php artisan …`, je nach Deployment-Umgebung).
|
|
|
|
---
|
|
|
|
## 0. Grundverständnis (einmal lesen)
|
|
|
|
- **Zwei Legacy-Quellen, unterschiedlicher Charakter:**
|
|
- **Presseecho** = echte **Live-DB** (`mysql_presseecho`). Nur hier greift die
|
|
„offline nehmen → Delta nachziehen"-Choreografie.
|
|
- **Businessportal24** = **statischer Snapshot-Dump** (`mysql_businessportal`).
|
|
Ändert sich nicht mehr → einmal importieren reicht, kein Delta nötig.
|
|
- **Idempotenz:** Alle Importer schreiben per `updateOrCreate(legacy_portal, legacy_id)`
|
|
+ Crosswalk-Tabelle `legacy_import_map`. Mehrfaches Ausführen erzeugt **keine
|
|
Duplikate**. Bei Absturz mittendrin: einfach erneut starten (kein Rollback,
|
|
Sicherheit kommt aus Idempotenz, nicht aus Transaktionen).
|
|
- **Standard-Lauf überspringt bereits importierte Zeilen.** Zwischenzeitliche
|
|
**Änderungen** an Altdatensätzen werden nur mit `--force` nachgezogen.
|
|
- **Kein Delete-Sync:** Im Alt-System gelöschte Datensätze bleiben im neuen
|
|
System bestehen. Für das kurze Cutover-Fenster i. d. R. vernachlässigbar.
|
|
- ⚠️ **`migrate:presseecho` NICHT ausführen** — toter, kaputter Stub. Der einzige
|
|
gültige Weg ist `legacy:import`.
|
|
- **Voraussetzung Timestamp-Sync:** `legacy:fix-timestamps` nutzt Cross-Schema-
|
|
JOINs → beide Legacy-Schemas müssen auf **demselben MySQL-Server** liegen wie
|
|
die App-DB. **Umsetzung Live (16.07.):** Die KAS-Originale (All-Inkl) sind
|
|
von außen nicht joinbar → Dumps als lokale Snapshot-Kopien `legacy_presseecho`
|
|
/ `legacy_businessportal` auf dem Hetzner-Server eingespielt (Ploi-DBs,
|
|
`dbUserPressekonto` hat Zugriff), `DB_*_PRESSEECHO`/`DB_*_BUSINESSPORTAL`
|
|
zeigen auf die Kopien. Ein voller `--force`-Reimport ist als Ersatz ungeeignet:
|
|
er setzt die `path`-Spalte bereits migrierter Bildzeilen auf den Legacy-
|
|
Dateinamen zurück und macht die Medien-Migration kaputt.
|
|
- ⚠️ **Snapshot veraltet:** Alle Legacy-Commands lesen jetzt die lokalen Kopien.
|
|
Beim Cutover (Abschnitt 3) und vor jedem Delta-Lauf zuerst frische KAS-Dumps
|
|
ziehen und in die Kopien einspielen — sonst läuft das Delta gegen alte Daten.
|
|
|
|
---
|
|
|
|
## 1. Vorbereitung (Tage vorher, in Ruhe)
|
|
|
|
- [ ] Prod-`.env` gesetzt: `APP_ENV=production`, `APP_DEBUG=false`,
|
|
`SESSION_SECURE_COOKIE=true`, `QUEUE_CONNECTION=database` (o. Ä. dauerhaft).
|
|
- [ ] Legacy-Zugangsdaten in `.env` eingetragen:
|
|
`DB_HOST_PRESSEECHO / DB_DATABASE_PRESSEECHO / DB_USERNAME_PRESSEECHO / DB_PASSWORD_PRESSEECHO`
|
|
und `DB_*_BUSINESSPORTAL` (Snapshot-Dump auf denselben Server eingespielt).
|
|
- [ ] Secrets rotiert (OpenAI / Google / Mail) — nicht die Alt-Keys übernehmen.
|
|
- [ ] Legacy-Medien ins lokale Staging kopiert:
|
|
`dev/migration/<portal>/uploads/...` (rsync vom Alt-System).
|
|
- [ ] DB-Migrationen ausgeführt: `artisan migrate --force`.
|
|
Vorher prüfen: keine Duplikate in `invoices.stripe_invoice_id`
|
|
(Unique-Migration bricht sonst bewusst ab).
|
|
- [ ] Rollen/Permissions ausgerollt: `artisan db:seed --class=RolesAndPermissionsSeeder`.ç
|
|
- [ ] **Verbindungstest / Zählwerte:** `artisan legacy:import --source=all --dry-run`
|
|
→ erwartete Zeilenzahlen plausibel?
|
|
|
|
---
|
|
|
|
## 2. Bulk-Import (Presseecho noch live, ohne Downtime)
|
|
|
|
Reihenfolge ist in `legacy:import --step=all` hart verdrahtet
|
|
(categories → users → companies → contacts → press-releases → link-associations).
|
|
|
|
- [ ] `artisan legacy:import --source=all`
|
|
(idempotent — beliebig oft wiederholbar).
|
|
- [ ] `artisan legacy:migrate-media --portal=all`
|
|
(Datei-Bytes aus `dev/migration/...` → **public**-Disk + Varianten).
|
|
- [ ] **`artisan press-releases:privatize-media --prune-missing`**
|
|
(verschiebt PM-Medien public → **private** Disk — Security-Hardening;
|
|
MUSS nach `legacy:migrate-media` laufen). `--prune-missing` löscht
|
|
Legacy-Bildzeilen, deren Datei im Alt-System verloren ging (Import legt
|
|
pro Legacy-Bild eine Zeile an; ohne Datei bleibt sie sonst als Leiche
|
|
liegen und hält die Betriebsüberwachungs-Kachel rot).
|
|
- [ ] `artisan legacy:fix-timestamps`
|
|
(created_at/updated_at + published_at per Cross-DB-JOIN).
|
|
- [ ] Satelliten-Migrationen (Reihenfolge unkritisch, jeweils `--dry-run` zuerst):
|
|
- [ ] `artisan legacy:import-api-keys`
|
|
- [ ] `artisan legacy:archive-invoices`
|
|
- [ ] `artisan legacy:grandfather-subscriptions`
|
|
- [ ] `artisan legacy:import-billing-addresses`
|
|
- [ ] `artisan legacy:migrate-agency-names`
|
|
- [ ] `artisan companies:retire-agency-type` (läuft NACH `legacy:migrate-agency-names`;
|
|
Reports unter `storage/app/private/migration/` prüfen — Mehrfach-Namen,
|
|
Owner-lose Agentur-Firmen, geschützte Fälle; s. Deploy-Runbook §7)
|
|
- [ ] **Gate:** `artisan legacy:verify` → JSON-Report prüfen. Go / No-Go.
|
|
|
|
---
|
|
|
|
## 3. Cutover-Fenster (kurze Downtime)
|
|
|
|
Ziel: nur die seit dem Bulk-Import geänderten/neuen **Presseecho**-Daten
|
|
nachziehen. Businessportal24 braucht hier nichts (Dump ist statisch).
|
|
|
|
- [ ] **Presseecho** in **Read-Only / Wartung** schalten — **Lesezugriff behalten**,
|
|
der Delta-Import muss noch aus der Legacy-DB lesen können.
|
|
- [ ] **Frische KAS-Dumps ziehen und die lokalen Snapshot-Kopien neu einspielen**
|
|
(`legacy_presseecho`; Businessportal24 nur falls sich dort doch etwas
|
|
geändert hat) — die Legacy-Connections lesen die Kopien, nicht KAS direkt.
|
|
- [ ] Neues System in Wartung: `artisan down`.
|
|
- [ ] **Delta-Import mit `--force` + `--since`** (zieht auch geänderte
|
|
Bestandsdatensätze nach; uuid + slug von PMs bleiben stabil → öffentliche
|
|
URLs brechen nicht). `--since` auf das Datum des **Bulk-Imports minus
|
|
1 Tag Sicherheitsmarge** setzen — dann scannt der Lauf nur die seither
|
|
angelegten/geänderten Legacy-Zeilen (Sekunden statt Volldurchlauf):
|
|
`artisan legacy:import --source=presseecho --force --since="2026-07-19"`
|
|
Die Importer setzen Timestamps inzwischen selbst korrekt (forceFill) —
|
|
`legacy:fix-timestamps` danach ist Absicherung, kein Muss mehr.
|
|
⚠️ Für neue PM-Bilder vorher auch die **Medien** vom Alt-Server nachsyncen
|
|
(rsync-Delta nach `dev/migration/presseecho/…`), sonst werden ihre
|
|
Bildzeilen mangels Datei von `--prune-missing` entsorgt.
|
|
- [ ] Medien-Delta: `artisan legacy:migrate-media --portal=presseecho --force`
|
|
(`--force` erzwingt Neukopie + Varianten geänderter Bilder) →
|
|
danach erneut `artisan press-releases:privatize-media --prune-missing`
|
|
(der `--force`-Reimport legt die Bildzeilen verlorener Legacy-Dateien
|
|
erneut an — das Pruning räumt sie wieder ab).
|
|
- [ ] `artisan legacy:fix-timestamps --portal=presseecho`
|
|
- [ ] **Gate:** `artisan legacy:verify --portal=presseecho`.
|
|
- [ ] **DNS umstellen** auf das neue System.
|
|
- [ ] Queue-Worker läuft & wird überwacht → Admin ▸ **Reports ▸ Betriebsüberwachung**
|
|
(alle Kacheln grün, besonders „Queue-Worker" und „Medien-Privatisierung").
|
|
- [ ] `artisan up` — neues System live.
|
|
- [ ] Alt-System **komplett offline**.
|
|
|
|
---
|
|
|
|
## 4. Go-Live-Kommunikation
|
|
|
|
- [ ] Passwort-Reset-Mails versenden (Importer setzt **bewusst keine Passwörter**):
|
|
erst `artisan auth:send-go-live-mails --dry-run` (Anzahl prüfen),
|
|
dann `artisan auth:send-go-live-mails`.
|
|
- ⚠️ **Massen-Mailversand über die Queue** → vorher Worker-Status grün prüfen,
|
|
während des Versands Betriebsüberwachung im Blick behalten
|
|
(pending/failed jobs).
|
|
|
|
---
|
|
|
|
## 5. Nachlauf / Verifikation
|
|
|
|
- [ ] Stichproben: Login mit Reset-Link, PM-Detailseite (Bild + Anhang laden),
|
|
Newsroom-Übersicht, Archiv-Sichtbarkeit.
|
|
- [ ] Betriebsüberwachung: keine `failed_jobs`, kein „gestoppter Worker".
|
|
- [ ] `storage/logs` auf Fehler prüfen.
|
|
- [ ] Backup der neuen Prod-DB direkt nach erfolgreichem Cutover.
|
|
|
|
---
|
|
|
|
## Offene Punkte / bewusste Entscheidungen
|
|
|
|
- **Verlorene Presseecho-PM-Bilder akzeptiert (16.07.):** ~22k von ~32k Legacy-
|
|
Bilddateien existieren im Alt-System nicht mehr (nur Alt-Thumbnails retteten
|
|
~4,5k). Entscheidung: ohne Bild importieren — betrifft alte Archiv-Meldungen;
|
|
betroffene PMs erscheinen bildlos, kaputte Referenzen entstehen nicht
|
|
(`press-releases:privatize-media --prune-missing` räumt die Datensatz-Leichen ab).
|
|
|
|
- **Gelöschte Legacy-Datensätze** werden nicht synchronisiert (kein Delete-Sync).
|
|
Falls im Parallelbetrieb im Alt-System gelöscht wird → im neuen System nachher
|
|
manuell prüfen (i. d. R. vernachlässigbar bei kurzem Fenster).
|
|
- **`legacy:fix-timestamps`** setzt voraus, dass beide Legacy-Schemas auf demselben
|
|
MySQL-Server liegen. Andernfalls voller `--force`-Reimport statt Timestamp-Fix.
|