#!/bin/bash # # Holt die neueste Version aus dem konfigurierten Git-Repo und fuehrt die noetigen Updates # fuer die Docker-Compose-Entwicklungs-/Laufzeitumgebung durch (siehe README.md/Visualization.md # Abschnitt "Entwicklung nur mit Docker"): PHP-Abhaengigkeiten aktualisieren, Images neu bauen, # Dienste neu starten, Testsuiten laufen lassen. SQLite-Migrationen brauchen keinen eigenen # Schritt - api/src/Db/Database::migrate() wendet sie bei jedem API-Start automatisch an. # # Konfiguration ueber Umgebungsvariablen (Defaults passen fuer den ueblichen Fall): # UPDATE_REMOTE - Git-Remote, aus dem geholt wird (Default: origin) # UPDATE_BRANCH - Branch, der aktualisiert wird (Default: aktuell ausgecheckter Branch) # UPDATE_RUN_TESTS - "0" setzen, um die Testsuiten nach dem Update zu ueberspringen (Default: 1) # UPDATE_ALLOW_DIRTY - "1" setzen, um auch bei lokalen Aenderungen an versionierten Dateien # weiterzumachen (Default: 0, d.h. kontrollierter Abbruch) set -euo pipefail REPO_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)" cd "$REPO_DIR" log() { printf '[update.sh] %s\n' "$1" } fail() { printf '[update.sh] Fehler: %s\n' "$1" >&2 exit 1 } command -v git >/dev/null 2>&1 || fail "git ist nicht installiert." command -v docker >/dev/null 2>&1 || fail "docker ist nicht installiert." git -C "$REPO_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1 \ || fail "$REPO_DIR ist kein Git-Repository." UPDATE_REMOTE="${UPDATE_REMOTE:-origin}" UPDATE_BRANCH="${UPDATE_BRANCH:-$(git rev-parse --abbrev-ref HEAD)}" UPDATE_RUN_TESTS="${UPDATE_RUN_TESTS:-1}" UPDATE_ALLOW_DIRTY="${UPDATE_ALLOW_DIRTY:-0}" # Unversionierte Dateien blockieren das Update bewusst NICHT: neben dem Repo liegen auf einem # Produktionsserver Laufzeit-/Konfigurationsdateien (hosts.conf, monitor-api.conf, ...), die # beim ff-only-Pull ohnehin unangetastet bleiben - stuende eine davon einem neuen Commit im # Weg, bricht git selbst kontrolliert ab. Blockierend sind nur Aenderungen an versionierten # Dateien, weil der Pull die verlieren koennte. DIRTY_TRACKED="$(git status --porcelain --untracked-files=no)" if [ -n "$DIRTY_TRACKED" ]; then printf '%s\n' "$DIRTY_TRACKED" >&2 if [ "$UPDATE_ALLOW_DIRTY" = "1" ]; then log "Warnung: lokale Aenderungen an versionierten Dateien (siehe oben), UPDATE_ALLOW_DIRTY=1 - fahre trotzdem fort." else fail "Lokale Aenderungen an versionierten Dateien (siehe oben) - bitte erst committen/stashen (oder UPDATE_ALLOW_DIRTY=1 setzen). Breche ab, um nichts zu ueberschreiben." fi fi UNTRACKED="$(git ls-files --others --exclude-standard)" if [ -n "$UNTRACKED" ]; then log "Hinweis: unversionierte Dateien im Arbeitsbaum, sie bleiben unveraendert:" printf '%s\n' "$UNTRACKED" | sed 's/^/ /' fi log "Hole Aenderungen von ${UPDATE_REMOTE}/${UPDATE_BRANCH}..." git fetch "$UPDATE_REMOTE" "$UPDATE_BRANCH" # --ff-only statt reset --hard: bricht kontrolliert ab, falls der lokale Branch abweicht # (z. B. eigene, noch nicht gepushte Commits), statt Historie zu ueberschreiben. git checkout "$UPDATE_BRANCH" git pull --ff-only "$UPDATE_REMOTE" "$UPDATE_BRANCH" DOCKER_COMPOSE="docker compose" if ! docker compose version >/dev/null 2>&1; then command -v docker-compose >/dev/null 2>&1 || fail "Weder 'docker compose' noch 'docker-compose' verfuegbar." DOCKER_COMPOSE="docker-compose" fi log "Aktualisiere PHP-Abhaengigkeiten (composer install)..." $DOCKER_COMPOSE run --rm api composer install --no-interaction --no-progress log "Baue Docker-Images neu (falls Dockerfile/Abhaengigkeiten sich geaendert haben)..." $DOCKER_COMPOSE build # Tests laufen bewusst VOR dem Neustart der Dienste, in einem temporaeren Container # (docker compose run --rm), der die produktiven api/web/sync-Container nicht anfasst. Liefe # "up -d" zuerst, wuerden fehlschlagende Tests das Skript zwar per "set -e" abbrechen, die # Produktion liefe aber bereits mit dem fehlerhaften Stand weiter - und deploy-webhook.py # meldet einen solchen Abbruch nirgends aktiv, nur still im Log. Bei einem Fehlschlag hier # bleiben die alten Container unveraendert im Einsatz. if [ "$UPDATE_RUN_TESTS" = "1" ]; then log "Fuehre PHPUnit-Tests (API) aus..." $DOCKER_COMPOSE run --rm api composer test log "Fuehre Frontend-Tests aus..." $DOCKER_COMPOSE run --rm frontend else log "UPDATE_RUN_TESTS=0 gesetzt - ueberspringe Testsuiten." fi log "Starte Dienste neu (api, web, sync)..." $DOCKER_COMPOSE up -d api web sync # Meldet das Update als Event (POST /api/v1/events/system, Admin-Key aus der .env). Die API ist # direkt nach "up -d" evtl. noch nicht bereit, daher mehrere Versuche; ein Fehlschlag bricht das # (bereits erfolgreiche) Update nicht ab. report_update_event() { command -v curl >/dev/null 2>&1 || { log "Hinweis: curl fehlt, Update-Event wird nicht gemeldet."; return 0; } local env_file="$REPO_DIR/.env" admin_key api_base env_value() { [ -f "$env_file" ] || return 0 sed -n "s/^[[:space:]]*$1=//p" "$env_file" | tail -n1 \ | sed -e 's/^"\(.*\)"$/\1/' -e "s/^'\(.*\)'\$/\1/" } admin_key="${MONITOR_ADMIN_API_KEY:-$(env_value MONITOR_ADMIN_API_KEY)}" api_base="${MONITOR_API_BASE:-$(env_value MONITOR_API_BASE)}" api_base="${api_base:-http://localhost:8091}" api_base="${api_base%/}" api_base="${api_base%/api/v1}" if [ -z "$admin_key" ]; then log "Hinweis: MONITOR_ADMIN_API_KEY fehlt, Update-Event wird nicht gemeldet." return 0 fi local payload i payload="{\"name\":\"Monitor\",\"message\":\"Update eingespielt ($(git rev-parse --short HEAD))\",\"severity\":\"warning\"}" for i in 1 2 3 4 5 6 7 8 9 10; do if curl -sf --max-time 10 -X POST "$api_base/api/v1/events/system" \ -H "Content-Type: application/json" -H "X-Admin-Key: $admin_key" \ -d "$payload" >/dev/null 2>&1; then log "Update-Event gemeldet." return 0 fi sleep 3 done log "Warnung: Update-Event konnte nicht gemeldet werden (API unter $api_base nicht erreichbar oder Key ungueltig)." } report_update_event log "Update abgeschlossen (${UPDATE_BRANCH} @ $(git rev-parse --short HEAD))."