196 lines
10 KiB
Markdown
196 lines
10 KiB
Markdown
# 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. **Erledigt 06.10.2026 (status.adametz.media).** **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.
|