presseportale/dev/betrieb/git-deploy.md
Kevin Adametz 129de4fc76 deploy: Git-Deploy per Tag — dev/betrieb/deploy.sh, Ablauf und einmalige Umstellung in git-deploy.md, Runbook §10; public/build wird versioniert
- deploy.sh: bricht ohne Änderung ab bei Abweichung im Checkout oder neuen
  Migrationen ohne --migrations; down, Checkout, RELEASE, Composer, Caches,
  queue:restart, FPM, up; Rollback = derselbe Aufruf mit dem vorherigen Tag
- git-deploy.md: Forgejo-Zugang, Umwandlung von ~/pressekonto.com in einen
  Checkout von v1.1.0 ohne Dateiänderung, Release lokal, Prüfungen, Rollback
- Assets werden lokal gebaut und committet, auf dem Server kein Node

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 10:12:09 +00:00

9.2 KiB

Git-Deploy

Stand: 06.10.2026 · Status: vorbereitet, Umstellung steht aus (erstes Release über Git: v1.1.1)

Bis v1.1.0 gingen Deploys als Paket per SFTP auf den Server (deploy-v1.1.0.md). Künftig ist ~/pressekonto.com ein Git-Checkout, und ausgeliefert wird ein Tag aus origin.

Grundsätze

  • Remotes: origin = ssh://git@forge.adametz.media:222/kadmin/pressekonto.git (Forgejo auf Hetzner, privat). Von dort holt der Live-Server. NAS = git.adametz.media im lokalen Netz, nur als Backup.
  • Ausgeliefert wird nur ein Tag (vX.Y.Z, SemVer wie bisher), nie ein Branch-Stand.
  • Deploy nur von Hand per SSH mit deploy.sh. Ploi-„Quick Deploy" und Push-Webhooks bleiben aus.
  • Assets werden lokal gebaut und committet. public/build ist versioniert (2,4 MB, 18 Dateien). Auf dem Server gibt es kein Node.
  • Auf dem Server wird nichts von Hand geändert. Das Skript bricht ab, sobald der Checkout vom Tag abweicht. Hotfixes laufen ebenfalls über Commit, Tag und Deploy.
  • Kein git clean auf Live. .env, storage/, vendor/, auth.json, RELEASE und public/storage sind ignoriert und bleiben unberührt.
  • Kein Zero-Downtime-Modus mit Release-Verzeichnissen. storage/app (Archiv, Quarantäne, Exporte) bleibt an seinem Ort. Deploy im selben Verzeichnis mit php artisan down, wie bisher.
  • dev/, tests/ und die Doku liegen damit auch auf dem Server, aber außerhalb von public/. Ausgeliefert werden sie nicht.

A — Einmalig: Zugang vom Server zu Forgejo

  1. Auf dem Server (Benutzer der App) prüfen: git --version.

  2. Deploy-Key erzeugen und in Forgejo nur lesend am Repository eintragen (Repository ▸ Einstellungen ▸ Deploy-Schlüssel):

    ssh-keygen -t ed25519 -f ~/.ssh/forge_pressekonto -N "" -C "pressekonto-live"
    cat ~/.ssh/forge_pressekonto.pub
    
  3. ~/.ssh/config ergänzen:

    Host forge.adametz.media
        Port 222
        User git
        IdentityFile ~/.ssh/forge_pressekonto
        IdentitiesOnly yes
    
  4. Host-Key aufnehmen und den Fingerprint mit dem in Forgejo angezeigten vergleichen:

    ssh-keyscan -p 222 forge.adametz.media >> ~/.ssh/known_hosts
    ssh-keygen -lf <(ssh-keyscan -p 222 forge.adametz.media 2>/dev/null)
    
  5. Test: git ls-remote ssh://git@forge.adametz.media:222/kadmin/pressekonto.git zeigt refs/tags/v1.1.0.

  6. FPM-Neustart ohne Ploi testen: echo "" | sudo -S service php8.4-fpm reload. Geht das nicht, beim Deploy PK_FPM_RELOAD= setzen. Das Skript wartet dann, bis FPM in Ploi neu geladen ist.

B — Einmalig: Live-Verzeichnis in einen Checkout umwandeln

Live läuft auf v1.1.0. Das Verzeichnis wird an Ort und Stelle zum Checkout dieses Tags. Dabei ändert sich zunächst keine Datei. Git zeigt nur, wo der Server vom Tag abweicht.

  1. Dateibackup wie bei v1.1.0:

    cd ~ && tar czf backup-vor-git.tgz --exclude=./storage --exclude=./node_modules -C ~/pressekonto.com .
    
  2. Repository anlegen und auf v1.1.0 setzen, ohne Dateien anzufassen:

    cd ~/pressekonto.com
    git init -q
    git remote add origin ssh://git@forge.adametz.media:222/kadmin/pressekonto.git
    git fetch --tags origin
    git update-ref --no-deref HEAD "$(git rev-parse 'v1.1.0^{commit}')"
    git read-tree HEAD
    
  3. Abweichungen sichten:

    git status --porcelain | grep -v -E '^.. (dev/|docs/|_docs/|tests/|\.agents/|\.codex/|\.cursorrules|\.devcontainer/|\.github/|\.mcp\.json|AGENTS\.md|CLAUDE\.md|boost\.json|docker-compose\.yml|phpstan|phpunit\.xml|pint\.json)'
    

    Herausgefiltert sind die Entwicklerdateien, die nie im Paket waren. Was danach übrig bleibt:

    Zeile Bedeutung Vorgehen
    M datei Datei auf Live weicht vom Tag ab git diff -- datei ansehen. Fehlt die Änderung im Repository, erst lokal nachziehen, neuer Tag, dann weiter. Sonst ist es ein Paketrest und wird überschrieben.
    D datei Datei fehlt auf Live Meist Entwicklerdatei oder ein nie hochgeladener Teil. Einordnen, der Checkout legt sie an.
    ?? datei nur auf Live, nicht ignoriert Nicht löschen. Nach ~/vor-git/ verschieben, wenn sie nicht gebraucht wird. Wird sie gebraucht, gehört sie in die .gitignore (lokal, neuer Tag).
  4. Wenn nichts Unerklärtes übrig ist, auf den Tag auschecken:

    git checkout -q -f --detach v1.1.0
    git status --porcelain        # muss leer sein
    

    Der Code ist danach inhaltlich derselbe wie vorher. Ein Neustart ist nicht nötig.

C — Release lokal (jedes Mal)

  1. main sauber, volle Suite und Larastan grün (wie bisher).

  2. Assets bauen und mitcommitten:

    npm ci && npm run build
    git add public/build
    git diff --cached --quiet public/build || git commit -m "build: Assets für vX.Y.Z"
    

    Vite benennt Dateien nach ihrem Inhalt. Ohne Frontend-Änderung entsteht kein Diff.

  3. Tag setzen und auf beide Remotes pushen:

    git tag -a vX.Y.Z -m "vX.Y.Z — <Inhalt>"
    git push origin main --follow-tags
    git push NAS main --follow-tags
    

D — Deploy auf Live (jedes Mal)

  1. Bringt der Tag Migrationen mit: DB-Backup ziehen (ARCHIVE_KEY getrennt gesichert).

  2. Neue .env-Werte laut Release-Notiz vorher eintragen.

  3. Deploy:

    cd ~/pressekonto.com
    bash dev/betrieb/deploy.sh vX.Y.Z                   # ohne Migrationen
    bash dev/betrieb/deploy.sh vX.Y.Z --migrations      # mit Migrationen
    

    Das Skript macht Folgendes:

    • Es prüft, dass der Checkout sauber ist, und holt die Tags.
    • Es zeigt neue Migrationen an. Ohne --migrations bricht es dann ab, bevor etwas verändert ist.
    • Danach in dieser Reihenfolge: down, Checkout, RELEASE, composer install, gegebenenfalls migrate, Livewire-Assets, Caches, queue:restart, FPM, up, Version.

    --clear-schedule setzt zusätzlich die withoutOverlapping-Sperren zurück. Das ist nur nötig, wenn sich Sperrdauern im Scheduler geändert haben. Darf eine Migration länger auf Sperren warten: DB_LOCK_WAIT_TIMEOUT=300 bash dev/betrieb/deploy.sh vX.Y.Z --migrations.

  4. Prüfungen:

    • /health mit Token liefert "version":"vX.Y.Z".
    • Admin ▸ Reports ▸ Betriebsüberwachung ist grün.
    • storage/logs zeigt keine neuen Fehler.
    • Smoke-Test aus dem Runbook (Abschnitt 8).
    • Hat sich der Scheduler geändert: php artisan schedule:list.

Schlägt ein Schritt fehl, bleibt die Seite im Wartungsmodus. Das Skript nennt den Rollback-Befehl.

E — Rollback

cd ~/pressekonto.com
bash dev/betrieb/deploy.sh <vorheriger Tag>

Liegt die Abbruchstelle nach dem Checkout, ist der Checkout zwar sauber, aber vendor/ und die Caches können halb erneuert sein. Der Rollback-Aufruf baut beides für den alten Tag neu auf. Migrationen sind additiv. Der alte Code läuft mit den neuen Spalten, ein migrate:rollback gibt es auf Live nicht. Nur bei Datenproblemen das DB-Backup einspielen und danach Runbook Abschnitt 9 (Archiv) befolgen.

F — Erste Umstellung mit v1.1.1

v1.1.1 dient nur dazu, den neuen Weg zu testen. Das Release enthält keine Migration und keine Fachänderung.

Schritt Inhalt
lokal /public/build aus der .gitignore; deploy.sh, diese Datei und Runbook §10 committen; Doku-Commits seit v1.1.0; Build-Commit; Tag v1.1.1; Push auf beide Remotes
Server A (Zugang), B (Umwandlung auf v1.1.0), dann bash dev/betrieb/deploy.sh v1.1.1
Prüfung D 17. Zusätzlich git status --porcelain: Bis v1.1.0 war public/build ignoriert, ab v1.1.1 ist es versioniert. Alte Build-Reste auf Live (z. B. public/build/admin/, public/build/assets/, public/build/manifest.json aus der stillgelegten vite.config.js) erscheinen erst jetzt als ??. Nach ~/vor-git/ verschieben, sonst bricht der nächste Deploy an der Abweichungsprüfung ab.
danach ~/deploy-v1.1.0.tgz, ~/backup-vor-v1.1.0.tgz und nach ein paar stabilen Tagen ~/backup-vor-git.tgz löschen

Später: Changelog

Der Changelog-Entwurf (Decision-Update, Stufe 2) dockt in deploy.sh nach up an. Ein Artisan-Befehl liest die Commits zwischen dem vorherigen und dem neuen Tag. Die liegen jetzt auf dem Server vor. Daraus legt er die Entwürfe an. Der LLM-Schritt läuft als Queue-Job, damit ein Ausfall des Anbieters keinen Deploy abbricht.

Protokoll

Datum Schritt Ergebnis
06.10.2026 Vorbereitung origin auf Forgejo (Hetzner) umgestellt, NAS als zweites Remote, Push erledigt. deploy.sh gegen ein simuliertes Repository geprüft: Abbruch bei neuen Migrationen ohne Option, Deploy, wiederholter Aufruf, Rollback, unbekannter Tag, Fehler mitten im Lauf. Die Umwandlung eines bestehenden Verzeichnisses (B) wurde ebenso simuliert: Abweichungen werden sichtbar, .env und ungetrackte Dateien bleiben, ignorierte Builds werden beim Checkout überschrieben.
06.10.2026 v1.1.1 lokal /public/build versioniert. Alte Build-Reste von 2025 lokal entfernt (nicht mehr referenziert). Suite 2.316 grün, einer übersprungen (parallel, 96 s); Larastan ohne Meldung. Build-Commit und Tag v1.1.1 gesetzt, Push auf origin/NAS nach Freigabe.