presseportale/dev/betrieb/deploy-v1.1.0.md

196 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Deploy v1.1.0 — Checkliste
> **Live seit 05.10.2026** — Abschluss vom Betreiber bestätigt. Protokoll am Ende.
**Stand:** 05.10.2026 · **Live bisher:** Stand `98c465b` (Deploy 28.09.2026, ca. 14:34)
**Inhalt:** alles danach — Blöcke 9 und 10 aus dem Audit
([`ENTWICKLUNGSPLAN.md`](../2026-09-11-audit/ENTWICKLUNGSPLAN.md), Deploy-Plan
dort), Betriebsüberwachung Phase 1 ([`monitoring-plan.md`](monitoring-plan.md)),
Composer-Update, Versionsnummer.
Umfang gegenüber Live: 8 additive Migrationen, 2 neue Composer-Pakete
(`sentry/sentry-laravel`, Abhängigkeiten), 48 Paket-Updates, neue `.env`-Werte,
neue Assets (beide Builds), 2 neue Scheduler-Tasks (21 statt 19). Keine
gelöschten Code-Dateien.
Die Schritte aus dem Deploy-Plan der Blöcke 9/10 sind hier eingeordnet und
mit **(B9/10 Nr.)** gekennzeichnet.
**Auf dem Server wird kein Git gebraucht.** Git läuft nur lokal (Abschnitt A):
Tag, Versionsdatei, Paket. Der Server bekommt das fertige Paket und braucht
nur `tar`, `php` und `composer`.
## A — Lokal vorbereiten
1. Arbeitskopie committen, `main` per Fast-Forward nachziehen, Tag setzen:
`git tag -a v1.1.0 -m "v1.1.0 — Audit-Blöcke 9/10, Betriebsüberwachung Phase 1"`.
Push von `main` und Tag nach `origin`.
2. Auf dem Tag: `composer install`, `npm ci && npm run build`, volle Testsuite
(`php artisan test --compact --parallel`), Larastan.
3. Versionsdatei und Paket bauen (im Projektverzeichnis, auf dem Tag):
```bash
git describe --tags --always --dirty > RELEASE # muss genau "v1.1.0" ergeben
git -c core.quotepath=off diff --name-only --diff-filter=AMR 98c465b v1.1.0 \
-- . ':!dev' ':!docs' ':!tests' ':!.mcp.json' ':!.codex' ':!phpunit.xml' ':!phpstan-baseline.neon' \
> deploy-files.txt
tar czf deploy-v1.1.0.tgz -T deploy-files.txt public/build RELEASE
```
Ergibt `RELEASE` etwas anderes als `v1.1.0` (z. B. `-dirty`), ist die
Arbeitskopie nicht sauber — erst klären. Im Devcontainer fehlt `docs/`
(nicht eingebunden), das allein macht `-dirty`; dort nach Prüfung mit
`git describe --tags --always` schreiben.
**Erledigt 05.10.2026:** Paket `deploy-v1.1.0.tgz` gebaut (148 Dateien +
`public/build` + `RELEASE` = `v1.1.0`), liegt im Projektverzeichnis.
4. Paket per SFTP nach `~/` auf den Server laden.
## B — Live vorbereiten (vor dem Wartungsfenster)
5. **DB-Backup** ziehen; `ARCHIVE_KEY` liegt getrennt gesichert vor. (B9/10 Nr. 4)
6. **Dateibackup** für den Rollback (inkl. `vendor/`, ohne `storage/`):
```bash
cd ~ && tar czf backup-vor-v1.1.0.tgz --exclude=./storage --exclude=./node_modules -C ~/pressekonto.com .
```
7. **`.env` auf Live ergänzen**:
| Variable | Wert | Herkunft |
|---|---|---|
| `NEWSROOM_API_ASSIGNMENT_USER_IDS` | `13993` (vorher prüfen: `select id from users where email = 'info@connektar.de'`) | E19 |
| `NEWSLETTER_SENDING_ENABLED` | `false` | E23 |
| `BOUNCE_UNSIGNED_KIND` | `policy` | E25/O01 |
| `SENTRY_LARAVEL_DSN` | DSN Projekt 1 (errors.adametz.media) | M1 |
| `SENTRY_BROWSER_DSN` | DSN Projekt 2 | M1 |
| `SENTRY_ENVIRONMENT` | `production` | |
| `SENTRY_RELEASE` / `APP_VERSION` | **leer lassen** — kommt aus `RELEASE` | |
| `MONITORING_NTFY_URL` | `https://push.adametz.media` | M3 |
| `MONITORING_NTFY_TOPIC` | `pressekonto-ops` | |
| `MONITORING_NTFY_TOKEN` | Token von `pressekonto-alerts` | |
| `MONITORING_HEALTH_TOKEN` | **neuer** Zufallswert, nicht der lokale (`openssl rand -hex 24`) | 1.5 |
| `MONITORING_ALERT_EMAIL` | Ops-Postfach prüfen (leer = `LEGAL_CONTACT_EMAIL`) | |
| `UMAMI_PORTAL_ENABLED` | `false` — erst nach DSE-Anpassung | M6 |
| `UMAMI_IDENTIFY_USERS` | `false` — erst nach DSE-Anpassung | M6 |
Optionale Werte (Standards greifen) siehe B9/10 Nr. 1.
8. **Hauptkategorien** gegen `config/canonical.php` prüfen:
`select id, slug from category_translations where locale = 'de' and category_id between 1 and 14`. (B9/10 Nr. 2)
9. **Uploadgrenzen** PHP `upload_max_filesize`/`post_max_size` und nginx
`client_max_body_size` ≥ 20M (Ploi: PHP-Einstellungen bzw. nginx-Konfiguration). (B9/10 Nr. 3)
10. **Ausgehend erreichbar** vom Server: `curl -sI https://errors.adametz.media` und
`curl -sI https://push.adametz.media` (beide antworten).
## C — Deploy (SSH, Wartungsfenster ca. 3–5 Minuten)
```bash
cd ~/pressekonto.com
php artisan down --retry=60
tar xzf ~/deploy-v1.1.0.tgz
composer install --no-dev --optimize-autoloader --no-interaction
php artisan migrate --force # 8 Migrationen, additiv (B9/10 Nr. 6)
[ -d public/vendor/livewire ] && php artisan vendor:publish --tag=livewire:assets --force
php artisan config:cache
php artisan route:cache
php artisan view:clear && php artisan view:cache
php artisan event:cache
php artisan schedule:clear-cache # alte 24-h-Sperren (B9/10 Nr. 7)
php artisan queue:restart
# PHP-FPM neu laden (Ploi: Server ▸ PHP ▸ Restart) — OPcache hält sonst alten Code
php artisan up
```
Schlägt `composer install` oder `migrate` fehl: **nicht** `up`, sondern Rollback (Abschnitt G).
## D — Direkt danach (Prüfungen)
11. `php artisan about --only=pressekonto` → Version `v1.1.0`.
12. Admin ▸ Reports ▸ Betriebsüberwachung: Version `v1.1.0`; nach 1–2 Minuten
Scheduler grün, Plattenplatz grün; `archive_key`, `archive_pending`,
`archive_sweep` grün (nach Nr. 14).
13. `curl -s -H "Authorization: Bearer <MONITORING_HEALTH_TOKEN>" https://pressekonto.com/health`
→ `"status":"ok"`, `"version":"v1.1.0"`. Direkt nach dem Deploy kann der
Scheduler noch fehlen (503) — eine Minute warten.
14. `php artisan archive:retry-pending` einmal von Hand. (B9/10 Nr. 8)
15. `php artisan company-memberships:api-assignments` sichten; `--remove` nur bei
Treffern ohne Freischaltung. (B9/10 Nr. 9)
16. `php artisan press-releases:assign-canonicals`. (B9/10 Nr. 10)
17. `php artisan press-release-images:sweep-orphan-originals` (Trockenlauf) und
`php artisan archive:sweep-orphans --dry-run`. (B9/10 Nr. 11, 12)
18. `php artisan schedule:list` → **21** Tasks, darunter `monitoring:heartbeat`
(minütlich), `queue:prune-failed` (03:55), `press-release-images:sweep-orphan-originals`
(So 04:45), `press-releases:purge-events` (03:50). (B9/10 Nr. 13)
19. **GlitchTip:** `php artisan tinker --execute 'report(new RuntimeException("Deploy-Test v1.1.0"));'`
→ erscheint in Projekt 1 mit Umgebung `production`, Release `v1.1.0`.
20. **ntfy:** `php artisan tinker --execute 'App\Support\OperationalAlert::send("Deploy v1.1.0 live");'`
→ Push auf dem Handy (und Mail an das Ops-Postfach).
21. `storage/logs/laravel-*.log` und `monitoring-*.log` auf neue Fehler prüfen.
## E — Abnahme mit dem Testkonto
22. Anmelden per Passwort, Magic Link und Google; danach einige Seiten
aufrufen. Ein eingeloggter Nutzer, der einen eigenen Magic Link öffnet,
landet auf dem Ziel. (B9/10 Nr. 14)
23. Einmal absichtlich falsches Passwort → Admin ▸ Reports ▸ Verlauf zeigt
„Anmeldung fehlgeschlagen … falsches Passwort“. (1.7)
24. Titelbild mit 13–15 MiB hochladen. (B9/10 Nr. 15)
25. `sitemap.xml`: englische Meldungen unter `/en/press-release/…`;
`article:modified_time` = Veröffentlichung/letzte Textänderung. (B9/10 Nr. 16)
26. Admin ▸ Newsletter: Sperrhinweis, „Senden“ aus. (B9/10 Nr. 17)
27. Nach dem nächsten `mail:fetch-bounces`: unsignierte Rückläufer als Richtlinie
„(unsigniert)“, ohne Sperre. (B9/10 Nr. 18)
28. Der Smoke-Test aus dem Runbook (Abschnitt 8): drei Domains, robots/sitemap,
Test-PM durch die Klassifikation, Formulare mit Mail.
## F — Danach
29. **Externer Monitor** (Uptime Kuma o. Ä.) auf `https://pressekonto.com/health`,
Header `Authorization: Bearer …`, 60 s, 2 Wiederholungen, Benachrichtigung über
ntfy (`push.adametz.media`, `pressekonto-ops`). (1.5)
30. ~~**KAS-Passwort rotieren**~~ (B9/10 Nr. 19) — erledigt 06.10.2026, SSH-Zugang deaktiviert.
31. **Datenschutzerklärung** (Betreiber/Anwalt): Rückläufer-Protokoll (B9/10 Nr. 20),
Fehlerprotokollierung GlitchTip (User-ID, URL, Browser, Fehlermeldung, 90 Tage),
fehlgeschlagene Anmeldungen im Verlauf (IP nur als HMAC). Danach — und erst
dann — `UMAMI_PORTAL_ENABLED`/`UMAMI_IDENTIFY_USERS` per `.env` + `config:cache`.
32. Restore-Übung (B9/10 Nr. 21).
33. Protokolle ergänzen: Deploy-Datum und Ergebnisse in `ENTWICKLUNGSPLAN.md`
(B9/10 Nr. 22) und `monitoring-plan.md`.
34. Backups `~/deploy-v1.1.0.tgz` und `~/backup-vor-v1.1.0.tgz` nach ein paar
stabilen Tagen löschen.
## G — Rollback
```bash
cd ~/pressekonto.com
php artisan down
tar xzf ~/backup-vor-v1.1.0.tgz -C ~/pressekonto.com # alter Code inkl. vendor/
php artisan config:cache && php artisan route:cache && php artisan view:clear && php artisan view:cache && php artisan event:cache
php artisan queue:restart # + PHP-FPM neu laden
php artisan up
```
Die Migrationen sind additiv; der alte Code läuft mit den neuen Spalten und
Tabellen. Nur bei Datenproblemen das DB-Backup aus Nr. 5 einspielen und danach
die Archiv-Schritte aus Runbook Abschnitt 9 befolgen.
## Protokoll
**05.10.2026 — Deploy v1.1.0 live** (Paket aus Abschnitt A, Commit `df7ebdc` = Tag `v1.1.0`).
Abschnitt D, Ausgaben auf Live:
| Nr. | Ergebnis |
|---|---|
| 11 | Zunächst Version „—“: Datei `RELEASE` fehlte bzw. Config-Cache war älter. Behoben mit `echo v1.1.0 > RELEASE` + `config:cache`. Eine lokal neu erzeugte `RELEASE` hatte `v1.1.0-2-g8a64bd7-dirty` ergeben (Doku-Commits nach dem Tag, `docs/` nicht eingebunden) — Lehre: `RELEASE` aus dem Paket nehmen, nicht neu erzeugen |
| 14 | `archive:retry-pending`: 0 Aufträge, 0 offen |
| 15 | `company-memberships:api-assignments`: 3 Mitgliedschaften (connektar, User 13993), alle freigeschaltet — kein `--remove` |
| 16 | `assign-canonicals`: 9.887 Paare, 342 geändert, 13 veraltete Verweise zurückgesetzt, Caches geleert |
| 17 | `sweep-orphan-originals` (Trockenlauf): 6 geprüft, 0 verwaist; `archive:sweep-orphans --dry-run`: 0 |
| 18 | `schedule:list`: 21 Tasks inkl. `monitoring:heartbeat`, `queue:prune-failed` 03:55, `sweep-orphan-originals` So 04:45 |
| 19, 20 | GlitchTip- und ntfy-Test ausgeführt; nach `config:cache` wiederholt |
Abschnitt E (Abnahme Testkonto) vom Betreiber nicht einzeln gemeldet.
Abschnitt F offen: externer Monitor auf `/health` (29),
Datenschutzerklärung (31), Restore-Übung (32); dazu Context7-Schlüssel rotieren
(stand bis `df7ebdc` im Repository). KAS-Passwort (30) erledigt 06.10.2026.