#!/bin/sh # ============================================================================= # QR-Code-Generator aktualisieren # # ./update.sh neuen Stand holen und, falls es Änderungen gibt, # den Stack neu bauen und starten # ./update.sh --force auch ohne neue Commits neu bauen # ./update.sh --prune danach verwaiste Docker-Images aufräumen # ./update.sh --help diese Hilfe # ============================================================================= set -eu FORCE=0 PRUNE=0 usage() { cat <<'HILFE' QR-Code-Generator aktualisieren. ./update.sh neuen Stand holen und, falls es Änderungen gibt, den Stack neu bauen und starten ./update.sh --force auch ohne neue Commits neu bauen ./update.sh --prune danach verwaiste Docker-Images aufräumen ./update.sh --help diese Hilfe HILFE } while [ $# -gt 0 ]; do case "$1" in --force) FORCE=1 ;; --prune) PRUNE=1 ;; -h|--help) usage; exit 0 ;; *) echo "Unbekannte Option: $1" >&2 echo "Aufruf: ./update.sh [--force] [--prune]" >&2 exit 2 ;; esac shift done # Immer im Projektverzeichnis arbeiten, egal von wo das Skript gestartet wird. cd "$(dirname "$0")" info() { echo "→ $*"; } ok() { echo "✓ $*"; } fail() { echo "✗ $*" >&2; exit 1; } # --- Voraussetzungen -------------------------------------------------------- command -v git >/dev/null 2>&1 || fail "git ist nicht installiert." command -v docker >/dev/null 2>&1 || fail "docker ist nicht installiert." docker compose version >/dev/null 2>&1 || fail "docker compose ist nicht verfügbar (Docker Compose v2 wird benötigt)." [ -f .env ] || fail ".env fehlt. Anlegen mit: cp .env.example .env" # Ein "git pull" auf einem verändertem Arbeitsverzeichnis endet im Konflikt. if [ -n "$(git status --porcelain)" ]; then echo "✗ Es gibt lokale Änderungen im Arbeitsverzeichnis:" >&2 git status --short >&2 echo "" >&2 fail "Bitte zuerst committen oder mit 'git stash' beiseitelegen." fi # --- Neuen Stand holen ------------------------------------------------------ BEFORE="$(git rev-parse HEAD)" BRANCH="$(git rev-parse --abbrev-ref HEAD)" info "Hole neuen Stand von origin/$BRANCH …" git fetch --quiet origin # --ff-only: lieber abbrechen als eine Merge-Situation erzeugen, die auf einem # Server niemand auflösen will. if ! git merge --ff-only --quiet "origin/$BRANCH" 2>/dev/null; then echo "" >&2 echo "✗ Der lokale Stand lässt sich nicht ohne Merge aktualisieren." >&2 echo "" >&2 if git merge-base --is-ancestor HEAD "origin/$BRANCH" 2>/dev/null; then # Kann eigentlich nicht sein — dann wäre das Vorspulen geglückt. echo " Grund: unklar. Bitte 'git status' und 'git log --oneline -5' ansehen." >&2 elif ! git merge-base HEAD "origin/$BRANCH" >/dev/null 2>&1; then # Kein gemeinsamer Vorfahre: Die Historie wurde neu geschrieben, etwa # durch einen Force-Push. Der Inhalt ist derselbe, die Commits haben # nur neue Kennungen. cat >&2 <<'HINWEIS' Grund: Der Verlauf auf dem Server wurde neu geschrieben (Force-Push). Alte und neue Commits haben keinen gemeinsamen Vorfahren mehr. Der lokale Stand lässt sich verwerfen und durch den vom Server ersetzen: git fetch origin git reset --hard origin/main ./update.sh --force Das betrifft nur versionierte Dateien. Die .env und alles, was nicht im Repository liegt, bleibt unberührt. HINWEIS else echo " Grund: Hier liegen eigene Commits, die es auf dem Server nicht gibt:" >&2 git --no-pager log --oneline "origin/$BRANCH..HEAD" | sed 's/^/ /' >&2 echo "" >&2 echo " Entweder pushen — oder mit 'git reset --hard origin/$BRANCH' verwerfen." >&2 fi echo "" >&2 exit 1 fi AFTER="$(git rev-parse HEAD)" if [ "$BEFORE" = "$AFTER" ]; then ok "Bereits auf dem neuesten Stand ($(git rev-parse --short HEAD))." else ok "Aktualisiert: $(git rev-parse --short "$BEFORE") → $(git rev-parse --short "$AFTER")" echo "" git --no-pager log --oneline "$BEFORE..$AFTER" echo "" fi # --- Stack neu bauen -------------------------------------------------------- RUNNING="$(docker compose ps --status running --quiet 2>/dev/null | wc -l | tr -d ' ')" if [ "$BEFORE" = "$AFTER" ] && [ "$FORCE" -eq 0 ] && [ "$RUNNING" -gt 0 ]; then ok "Der Stack läuft und es gibt nichts Neues — kein Neubau nötig." echo " (Mit ./update.sh --force lässt sich ein Neubau erzwingen.)" exit 0 fi info "Baue und starte den Stack neu — das dauert einige Minuten …" docker compose up -d --build # --- Warten, bis die Anwendung antwortet ------------------------------------ # Nur die Ziffern behalten — so stören weder Anführungszeichen noch ein # Wagenrücklauf aus einer unter Windows bearbeiteten .env. APP_PORT="$(grep -m1 '^APP_PORT=' .env | cut -d= -f2 | tr -cd '0-9')" [ -n "$APP_PORT" ] || APP_PORT=3000 probe() { if command -v curl >/dev/null 2>&1; then curl --silent --fail --max-time 5 --output /dev/null "http://localhost:$APP_PORT/" elif command -v wget >/dev/null 2>&1; then wget --quiet --timeout=5 --tries=1 --spider "http://localhost:$APP_PORT/" else return 0 # Kein Prüfwerkzeug vorhanden — dann eben ohne Prüfung. fi } info "Warte darauf, dass die Anwendung antwortet …" ATTEMPT=0 until probe; do ATTEMPT=$((ATTEMPT + 1)) if [ "$ATTEMPT" -ge 60 ]; then echo "" docker compose logs --tail 40 app >&2 fail "Die Anwendung antwortet nach 2 Minuten nicht. Protokoll siehe oben." fi sleep 2 done ok "Die Anwendung antwortet auf http://localhost:$APP_PORT" # --- Ergebnis --------------------------------------------------------------- echo "" info "Datenbankmigrationen beim Start:" docker compose logs app 2>/dev/null | grep -E "migration|Migration|pending" | tail -n 5 | sed 's/^/ /' echo "" docker compose ps if [ "$PRUNE" -eq 1 ]; then echo "" info "Räume verwaiste Docker-Images auf …" docker image prune --force fi echo "" ok "Fertig — Version $(git rev-parse --short HEAD)"