From 263f030a6ec021d082a41a85852578250ddbafa3 Mon Sep 17 00:00:00 2001 From: Kevin Adametz Date: Mon, 20 Jul 2026 10:50:59 +0000 Subject: [PATCH] docs: Runbooks auf den echten Migrationsstand nachgezogen (16.07.) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cutover-Runbook: Legacy-DBs liegen bei KAS und sind als lokale Snapshot- Kopien eingespielt (Delta-Läufe brauchen vorher frische Dumps); privatize- media läuft mit --prune-missing; retire-agency-type als Satellit ergänzt; verlorene Presseecho-PM-Bilder (~22k) als bewusste Entscheidung dokumentiert. Deploy-Runbook: Seeder-Liste um LegalDocumentSeeder/RejectionCategorySeeder ergänzt, billing:sync-stripe-plans nach frischem Seeding, Tinker-Befehl für die Admin-Rolle. Co-Authored-By: Claude Fable 5 --- dev/optimize/cutover-runbook.md | 34 ++++++++++++++-- dev/release/deploy-runbook.md | 72 ++++++++++++++++++++++++++++++++- 2 files changed, 100 insertions(+), 6 deletions(-) diff --git a/dev/optimize/cutover-runbook.md b/dev/optimize/cutover-runbook.md index 864695a1..33edd743 100644 --- a/dev/optimize/cutover-runbook.md +++ b/dev/optimize/cutover-runbook.md @@ -27,7 +27,16 @@ Alle Commands laufen über Sail: `vendor/bin/sail artisan …` (bzw. ohne Sail 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. Sonst diesen Schritt durch vollen `--force`-Reimport ersetzen. + 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. --- @@ -59,9 +68,12 @@ Reihenfolge ist in `legacy:import --step=all` hart verdrahtet (idempotent — beliebig oft wiederholbar). - [ ] `artisan legacy:migrate-media --portal=all` (Datei-Bytes aus `dev/migration/...` → **public**-Disk + Varianten). -- [ ] **`artisan press-releases:privatize-media`** +- [ ] **`artisan press-releases:privatize-media --prune-missing`** (verschiebt PM-Medien public → **private** Disk — Security-Hardening; - MUSS nach `legacy:migrate-media` laufen). + 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): @@ -70,6 +82,9 @@ Reihenfolge ist in `legacy:import --step=all` hart verdrahtet - [ ] `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. --- @@ -81,13 +96,18 @@ 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`** (zieht auch geänderte Bestandsdatensätze nach; uuid + slug von PMs bleiben stabil → öffentliche URLs brechen nicht): `artisan legacy:import --source=presseecho --force` - [ ] Medien-Delta: `artisan legacy:migrate-media --portal=presseecho --force` (`--force` erzwingt Neukopie + Varianten geänderter Bilder) → - danach erneut `artisan press-releases:privatize-media`. + 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. @@ -121,6 +141,12 @@ nachziehen. Businessportal24 braucht hier nichts (Dump ist statisch). ## 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). diff --git a/dev/release/deploy-runbook.md b/dev/release/deploy-runbook.md index 9860deaa..529a1567 100644 --- a/dev/release/deploy-runbook.md +++ b/dev/release/deploy-runbook.md @@ -68,6 +68,12 @@ 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 +php artisan db:seed --class=LegalDocumentSeeder --force +php artisan db:seed --class=RejectionCategorySeeder --force + +# 5a) Nach frischem Seeding (Erst-Einrichtung oder DB-Reset): Stripe-Produkte/ +# Preise neu verknüpfen — die Price-IDs stehen in der plans-Tabelle: +# php artisan billing:sync-stripe-plans # 6) Caches bauen (bei JEDEM Deploy neu) php artisan config:cache @@ -179,6 +185,24 @@ Endpoint in Stripe (Live-Mode) registrieren: `https://pressekonto.de/stripe/webh Events: die Cashier-Defaults (Subscriptions, Invoices, Payment-Intents). ## 7. Erst-Einrichtung (nur beim allerersten Go-Live) +# kopieren der Bilddaten per rync #@jk:8#PN§!4V02wR:rV# + +rsync -avz --partial ssh-w01eb4f6@w008b36f.kasserver.com:/www/htdocs/w01eb4f6/presseecho.de/web/thumbnails/ \ + dev/migration/presseecho/thumbnails/ + + +rsync -avz --partial ssh-w01eb4f6@w008b36f.kasserver.com:/www/htdocs/w01eb4f6/presseecho.de/web/uploads/ \ + dev/migration/presseecho/uploads/ + + + +rsync -avz --partial ssh-w01eb4f6@w008b36f.kasserver.com:/www/htdocs/w01eb4f6/businessportal24.com/web/thumbnails/ \ + dev/migration/businessportal24/thumbnails/ + + +rsync -avz --partial ssh-w01eb4f6@w008b36f.kasserver.com:/www/htdocs/w01eb4f6/businessportal24.com/web/uploads/ \ + dev/migration/businessportal24/uploads/ + 1. Legacy-Datenimport (Reihenfolge beachten, read-only-Quellen konfiguriert): `legacy:import` → `legacy:fix-timestamps` → `legacy:migrate-media` @@ -191,11 +215,55 @@ Events: die Cashier-Defaults (Subscriptions, Invoices, Payment-Intents). Owner-lose Agentur-Firmen und geschützte Fälle), dann scharf. Namen werden nie überschrieben; gelöscht wird nur soft und nur ohne PMs/Kontakte/Dritt-Mitgliedschaften/Zahlungsoptions-Verknüpfung. -3. Ersten Admin-User anlegen (Prod hat keine Demo-User): - Registrierung + Rolle via Tinker/DB oder dedizierter Befehl. +3. Ersten Admin-User anlegen (Prod hat keine Demo-User): normal registrieren + (inkl. E-Mail-Verifizierung), dann Rolle via Tinker setzen — + `syncRoles` ersetzt dabei alle bisherigen Rollen des Users: + + ```bash + php artisan tinker --execute 'App\Models\User::where("email", "mail@example.com")->firstOrFail()->syncRoles(["admin"]);' + ``` 4. Favicons je Domain deployen (Platzhalter-Generator reicht nicht) [EXTERN R5.4]. 5. Smoke-Test: siehe Abschnitt 8. + +1. Medien migrieren + +# Erst prüfen, ob die Ordnerstruktur stimmt (zählt "Datei fehlt"/"Ziel fehlt") + php artisan legacy:migrate-media --portal=all --dry-run + +# Dann scharf (kopiert + erzeugt Bildvarianten — dauert bei der Menge eine Weile) + php artisan legacy:migrate-media --portal=all +Kleine „Datei fehlt"-Restmengen sind bei Altbeständen normal (verwaiste DB-Einträge); große Zahlen heißen Ordnerproblem — dann melde dich mit der Ausgabe. + +2. PM-Medien privatisieren (Pflicht, direkt danach) + +php artisan press-releases:privatize-media +Verschiebt die PM-Bilder von der public- auf die private-Disk (Security-Hardening). Muss nach migrate-media laufen — und nach jedem späteren Medien-Lauf erneut. + +3. Timestamps korrigieren (bewusst NACH den Medien) + +php artisan legacy:fix-timestamps +Setzt created_at/updated_at/published_at authoritativ aus den Legacy-DBs. Nach den Medien deshalb, weil die Medien-Migration die Datensätze anfasst und updated_at neu stempelt — der Fix räumt das wieder auf. Wichtig fürs Frontend: Die Sortierung hängt an published_at. + +4. Satelliten-Migrationen (jeweils erst --dry-run, dann scharf) + +php artisan legacy:import-api-keys --dry-run # lokal waren es 406 Keys — Zahl vergleichen +php artisan legacy:archive-invoices --dry-run +php artisan legacy:grandfather-subscriptions --dry-run # MUSS nach archive-invoices (leitet sich aus dem Rechnungsarchiv ab) +php artisan legacy:import-billing-addresses --dry-run # Report: Kunden mit leeren/kaputten Alt-Adressen → manuell nachpflegen +php artisan legacy:migrate-agency-names --dry-run +php artisan companies:retire-agency-type --dry-run # NACH migrate-agency-names +Die Reports landen unter storage/app/private/migration/ — besonders bei den letzten beiden reinschauen (Mehrfach-Namen, Owner-lose Agentur-Firmen), bevor du sie scharf laufen lässt. + +5. Das Gate: Gesamtverifikation + +php artisan legacy:verify +JSON-Report prüfen → das ist deine Go/No-Go-Entscheidung für den Cutover. Wenn du magst, reich mir den Report hier rein, ich gehe ihn mit dir durch. + +Danach: Stichproben im Browser +Newsroom-Übersicht, eine alte PM-Detailseite (lädt Bild + Anhang?), Firmenlogo, Archiv — und ein Blick in Admin ▸ Reports ▸ Betriebsüberwachung (alle Kacheln grün, besonders „Medien-Privatisierung"). + + ## 8. Smoke-Test nach jedem Deploy - [ ] Alle drei Domains laden (Hub-Landing, 2× Spoke-Home) über HTTPS