#!/usr/bin/env bash # # Eine hochgeladene Veröffentlichung aktivieren. # # Läuft auf dem Webserver als Benutzer `deploy`. Die Gitea-Strecke ruft das Skript # nach dem Hochladen auf; von Hand geht es genauso: # # /var/www/ekdos/deploy/release.sh 2f9c1ab # # Erwartete Verzeichnisse: # # $ROOT/releases//public das Vite-Bündel # $ROOT/releases//backend das PHP-Backend (noch ohne vendor/) # $ROOT/shared/.env die Konfiguration, überlebt jedes Ausrollen # $ROOT/current -> releases/ der Symlink, den nginx liest # # Beide Hälften werden mit einem einzigen Symlink-Tausch gemeinsam sichtbar. # Schlägt vorher etwas fehl, bricht das Skript ab und die laufende Fassung # bleibt unberührt. set -euo pipefail ROOT="${EKDOS_ROOT:-/var/www/ekdos}" KEEP="${EKDOS_KEEP:-5}" FPM_SERVICE="${EKDOS_FPM:-php8.5-fpm}" HEALTH_URL="${EKDOS_HEALTH_URL:-http://127.0.0.1/api/health}" RELEASE="${1:-}" [ -n "$RELEASE" ] || { echo "Aufruf: release.sh " >&2; exit 2; } RELEASE_DIR="$ROOT/releases/$RELEASE" BACKEND="$RELEASE_DIR/backend" step() { printf '\n\033[1m==> %s\033[0m\n' "$1"; } fail() { printf '\033[31mFEHLER: %s\033[0m\n' "$1" >&2; exit 1; } # ── 1. Prüfen, was da hochgeladen wurde ────────────────────────────────────── step "Veröffentlichung $RELEASE prüfen" [ -d "$RELEASE_DIR" ] || fail "$RELEASE_DIR gibt es nicht." [ -f "$RELEASE_DIR/public/index.html" ] || fail "Im Bündel fehlt public/index.html." [ -f "$BACKEND/public/index.php" ] || fail "Im Backend fehlt public/index.php." [ -f "$ROOT/shared/.env" ] || fail "$ROOT/shared/.env fehlt. Einmalig aus backend/.env.example anlegen." # ── 2. Gemeinsame Dateien einhängen ────────────────────────────────────────── # Die .env gehört dem Server, nicht dem Repository, und wird nie mit ausgerollt. step "Konfiguration verknüpfen" ln -sfn "$ROOT/shared/.env" "$BACKEND/.env" # var/cache bleibt bewusst je Veröffentlichung eigen: dort liegt der von PHP-DI # kompilierte Container. Geteilt über mehrere Veröffentlichungen hinweg würde er # auf Klassen der alten Fassung zeigen. mkdir -p "$BACKEND/var/cache" chmod 2775 "$BACKEND/var" "$BACKEND/var/cache" # ── 3. Abhängigkeiten auf der Zielmaschine auflösen ────────────────────────── # Bewusst hier und nicht im Gitea-Runner: nur hier stimmen PHP-Version und # Erweiterungen (pdo_pgsql, redis), sodass die Plattformprüfung von Composer # etwas Echtes prüft statt übersprungen zu werden. step "Composer-Abhängigkeiten installieren" ( cd "$BACKEND" && composer install --no-dev --no-interaction --no-progress --prefer-dist --optimize-autoloader --classmap-authoritative ) # ── 4. Schema fortschreiben, bevor die neue Fassung sichtbar wird ──────────── step "Datenbankschema fortschreiben" php "$BACKEND/bin/ekdos" migrate # ── 5. Umschalten ──────────────────────────────────────────────────────────── # ln -sfn legt bei einem vorhandenen Symlink sonst *im* Ziel an, deshalb erst # einen neuen Namen anlegen und dann mit mv -T atomar darüberschieben. step "Auf $RELEASE umschalten" PREVIOUS="$(readlink "$ROOT/current" 2>/dev/null || echo '')" ln -sfn "releases/$RELEASE" "$ROOT/current.new" mv -Tf "$ROOT/current.new" "$ROOT/current" [ -n "$PREVIOUS" ] && printf '%s\n' "$PREVIOUS" > "$ROOT/previous" || true # ── 6. php-fpm neu laden ───────────────────────────────────────────────────── # Der Pool fährt mit opcache.validate_timestamps=0. Ohne Neuladen liefe weiter # der alte Quelltext aus dem Opcache. step "$FPM_SERVICE neu laden" sudo systemctl reload "$FPM_SERVICE" # ── 7. Zwischenspeicher verwerfen ──────────────────────────────────────────── # Zwischengespeicherte n8n-Antworten passen womöglich nicht mehr zur neuen # Feldabbildung. Sitzungen bleiben unberührt -- niemand wird abgemeldet. step "n8n-Cache verwerfen" php "$BACKEND/bin/ekdos" cache:flush # ── 8. Nachsehen, ob es läuft ──────────────────────────────────────────────── step "Gesundheitsprüfung" for attempt in 1 2 3 4 5; do if curl -fsS --max-time 5 "$HEALTH_URL" > /dev/null; then echo " $HEALTH_URL antwortet." break fi [ "$attempt" -eq 5 ] && fail "$HEALTH_URL antwortet nicht. Zurück mit: $ROOT/deploy/rollback.sh" echo " Versuch $attempt fehlgeschlagen, neuer Versuch …" sleep 2 done # ── 9. Aufräumen ───────────────────────────────────────────────────────────── # Die aktuelle und die vorige Veröffentlichung bleiben in jedem Fall liegen. step "Alte Veröffentlichungen aufräumen (die letzten $KEEP bleiben)" KEEP_LIST="$(printf '%s\n%s\n' "$RELEASE" "$(basename "${PREVIOUS:-none}")")" # find statt ls: kein unexpandiertes */-Glob, das unter `pipefail` den ganzen # Lauf nach einem bereits erfolgreichen Ausrollen scheitern liesse. OBSOLETE="$(cd "$ROOT/releases" && find . -mindepth 1 -maxdepth 1 -type d -printf '%T@ %f\n' \ | sort -rn | cut -d' ' -f2- | tail -n "+$((KEEP + 1))" || true)" if [ -n "$OBSOLETE" ]; then printf '%s\n' "$OBSOLETE" | while read -r old; do [ -n "$old" ] || continue # Die aktive und die vorige Veröffentlichung bleiben in jedem Fall liegen. if printf '%s\n' "$KEEP_LIST" | grep -qxF "$old"; then continue fi echo " entferne $old" rm -rf -- "$ROOT/releases/$old" done else echo " nichts aufzuräumen" fi step "$RELEASE ist aktiv."