presseportale/dev/archive/optimize/cutover-runbook.md
Kevin Adametz 6a4c4d8837 docs: dev-Doku für den Live-Betrieb neu sortiert — Archiv + Betrieb + Roadmap
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>
2026-07-21 07:52:38 +00:00

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.