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

9.1 KiB

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.