Git-Deploy per Tag: deploy.sh, Version aus RELEASE, Worker-Pruefung
- dev/betrieb/deploy.sh: Tag auschecken, composer, Caches leeren (bewusst
kein route:/config:cache wegen hostabhaengiger Routen und env()-Aufrufen),
horizon:terminate, FPM-Reload, Worker-Pruefung am Ende
- dev/betrieb/README.md: Einrichtung Server, Umstellung des Live-Verzeichnisses,
Release, Deploy, Rollback
- config('app.version') aus RELEASE, php artisan about --only=mivita
- monitoring:queue-probe prueft nach dem Deploy, ob Horizon Jobs abholt
- Vorlage dev/git-deploy-monitoring aus Pressekonto
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b703194f99
commit
e1c6178bc7
45 changed files with 3554 additions and 0 deletions
175
dev/betrieb/deploy.sh
Executable file
175
dev/betrieb/deploy.sh
Executable file
|
|
@ -0,0 +1,175 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Deploy eines Git-Tags auf Live (mivita.care, Ploi). Anleitung: dev/betrieb/README.md
|
||||
#
|
||||
# Aufruf im App-Verzeichnis auf dem Server:
|
||||
#
|
||||
# cd /home/ploi/mivita.care
|
||||
# bash dev/betrieb/deploy.sh v1.2.0 # ohne neue Migrationen
|
||||
# bash dev/betrieb/deploy.sh v1.2.0 --migrations # mit Migrationen, DB-Backup vorher!
|
||||
#
|
||||
# Optionen:
|
||||
# --migrations Neue Migrationen ausführen. Ohne diese Option bricht das
|
||||
# Skript ab, sobald der Tag neue Migrationen mitbringt.
|
||||
# --clear-schedule php artisan schedule:clear-cache (alte withoutOverlapping-Sperren).
|
||||
#
|
||||
# Umgebung (optional):
|
||||
# DEPLOY_FPM_RELOAD Befehl zum Neuladen von PHP-FPM (Standard siehe unten).
|
||||
# Leer setzen, wenn FPM von Hand in Ploi neu geladen wird —
|
||||
# dann wartet das Skript auf Enter. Nie „Restart“: der
|
||||
# entfernt kurz den Socket, Nginx liefert 502.
|
||||
#
|
||||
# Exit-Codes: 0 fertig, 1 abgebrochen (siehe Ausgabe), 2 falscher Aufruf,
|
||||
# 3 live, aber Horizon hat den Test-Job nicht abgeholt.
|
||||
#
|
||||
# Rollback: dasselbe Skript mit dem vorherigen Tag.
|
||||
#
|
||||
# Bewusst KEIN route:cache und KEIN config:cache:
|
||||
# - Die Routen hängen vom Host ab (RouteServiceProvider::loadDomainAwareRoutes).
|
||||
# Ein auf der Kommandozeile erzeugter Cache enthielte nur die main-Routen —
|
||||
# Shop, CRM und Portal wären weg.
|
||||
# - Mehrere Stellen lesen env() außerhalb von config/ (z. B. env('APP_KEY') in
|
||||
# App\User::isPasswort). Mit gecachter Config liefert env() dort null.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ---- Projekt-Einstellungen --------------------------------------------------
|
||||
DEPLOY_SCRIPT="dev/betrieb/deploy.sh" # Pfad dieses Skripts im Repository
|
||||
PHP_FPM_SERVICE="php8.4-fpm" # systemd-Name des FPM-Dienstes
|
||||
ABOUT_SECTION="mivita" # Abschnitt aus AboutCommand::add (AppServiceProvider)
|
||||
QUEUE_PROBE=1 # 1 = monitoring:queue-probe am Ende
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Git ersetzt diese Datei beim Checkout. Bash liest Skripte stückweise, deshalb
|
||||
# läuft der Deploy aus einer Kopie.
|
||||
if [[ -z "${DEPLOY_COPY:-}" ]]; then
|
||||
copy="$(mktemp)"
|
||||
cp "$0" "$copy"
|
||||
DEPLOY_COPY="$copy" exec bash "$copy" "$@"
|
||||
fi
|
||||
trap 'rm -f "$DEPLOY_COPY"' EXIT
|
||||
|
||||
tag="${1:-}"
|
||||
shift || true
|
||||
run_migrations=0
|
||||
clear_schedule=0
|
||||
|
||||
for option in "$@"; do
|
||||
case "$option" in
|
||||
--migrations) run_migrations=1 ;;
|
||||
--clear-schedule) clear_schedule=1 ;;
|
||||
*) echo "Unbekannte Option: $option" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ ! "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Aufruf: bash $DEPLOY_SCRIPT vX.Y.Z [--migrations] [--clear-schedule]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ ! -f artisan || ! -d .git ]]; then
|
||||
echo "Im App-Verzeichnis aufrufen (artisan und .git fehlen hier)." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
step() { printf '\n==> %s\n' "$*"; }
|
||||
|
||||
step "Arbeitsverzeichnis prüfen"
|
||||
drift="$(git status --porcelain)"
|
||||
if [[ -n "$drift" ]]; then
|
||||
echo "Das Arbeitsverzeichnis weicht vom ausgecheckten Stand ab:" >&2
|
||||
echo "$drift" >&2
|
||||
echo "Erst klären (git diff), dann deployen. Nichts wurde verändert." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
step "Tags von origin holen"
|
||||
git fetch --quiet --tags origin
|
||||
|
||||
if ! git rev-parse --quiet --verify "refs/tags/$tag^{commit}" > /dev/null; then
|
||||
echo "Tag $tag gibt es auf origin nicht." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
previous="$(cat RELEASE 2>/dev/null || git describe --tags --always)"
|
||||
echo "Live: $previous → Ziel: $tag"
|
||||
|
||||
if [[ "$(git rev-parse HEAD)" == "$(git rev-parse "$tag^{commit}")" ]]; then
|
||||
echo "$tag ist bereits ausgecheckt. Nichts zu tun."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
new_migrations="$(git diff --name-only --diff-filter=A HEAD "$tag" -- database/migrations)"
|
||||
if [[ -n "$new_migrations" ]]; then
|
||||
echo "Neue Migrationen:"
|
||||
echo "$new_migrations" | sed 's/^/ /'
|
||||
if [[ "$run_migrations" -ne 1 ]]; then
|
||||
echo "Abbruch: DB-Backup ziehen und mit --migrations erneut aufrufen. Nichts wurde verändert." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
trap 'echo; echo "!! Deploy abgebrochen. Die Seite bleibt im Wartungsmodus." >&2; echo "!! Rollback: bash $DEPLOY_SCRIPT $previous" >&2; rm -f "$DEPLOY_COPY"' ERR
|
||||
|
||||
step "Wartungsmodus"
|
||||
php artisan down --retry=60
|
||||
|
||||
step "Checkout $tag"
|
||||
git checkout --quiet --detach "$tag"
|
||||
echo "$tag" > RELEASE
|
||||
|
||||
step "Composer"
|
||||
composer install --no-dev --optimize-autoloader --no-interaction
|
||||
|
||||
if [[ -n "$new_migrations" ]]; then
|
||||
step "Migrationen"
|
||||
php artisan migrate --force
|
||||
fi
|
||||
|
||||
step "Caches leeren"
|
||||
php artisan config:clear
|
||||
php artisan route:clear
|
||||
php artisan event:clear
|
||||
php artisan view:clear
|
||||
|
||||
if [[ "$clear_schedule" -eq 1 ]]; then
|
||||
php artisan schedule:clear-cache
|
||||
fi
|
||||
|
||||
step "Horizon und PHP-FPM"
|
||||
# Horizon hält den alten Code im Speicher; terminate beendet es nach den
|
||||
# laufenden Jobs, Supervisor (Ploi-Daemon) startet es neu.
|
||||
php artisan horizon:terminate
|
||||
php artisan queue:restart
|
||||
default_fpm_reload="echo '' | sudo -S service $PHP_FPM_SERVICE reload"
|
||||
fpm_reload="${DEPLOY_FPM_RELOAD-$default_fpm_reload}"
|
||||
if [[ -n "$fpm_reload" ]]; then
|
||||
bash -c "$fpm_reload"
|
||||
else
|
||||
echo "PHP-FPM jetzt in Ploi neu laden (Reload, nicht Restart), danach Enter."
|
||||
read -r
|
||||
fi
|
||||
|
||||
step "Wartungsmodus aus"
|
||||
php artisan up
|
||||
|
||||
step "Version"
|
||||
php artisan about --only="$ABOUT_SECTION"
|
||||
|
||||
trap - ERR
|
||||
|
||||
# Läuft Horizon wieder? `horizon:terminate` oben beendet es; neu starten muss
|
||||
# es Supervisor. Läuft Supervisor nicht, steht die Queue still, ohne dass es
|
||||
# jemand merkt.
|
||||
if [[ "$QUEUE_PROBE" -eq 1 ]]; then
|
||||
step "Queue-Worker"
|
||||
if ! php artisan monitoring:queue-probe --wait=60; then
|
||||
echo
|
||||
echo "!! Deploy $tag ist live, aber Horizon hat den Test-Job nicht abgeholt." >&2
|
||||
echo "!! Jetzt php artisan horizon:status und den Horizon-Daemon in Ploi prüfen — sonst bleiben Jobs und Mails liegen." >&2
|
||||
exit 3
|
||||
fi
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Deploy $tag fertig (vorher $previous). Weiter mit den Prüfungen aus dev/betrieb/README.md."
|
||||
Loading…
Add table
Add a link
Reference in a new issue