#!/bin/sh # GerbilManager Deploy-Skript (laeuft auf der TrueNAS Goldeye) # =========================================================== # Holt die neuen Images aus der Gitea-Registry und rollt den Compose-Stack neu aus. # Wird vom Gitea-Actions 'deploy'-Job (self-hosted Runner auf der NAS) aufgerufen # UND kann jederzeit manuell auf der NAS ausgefuehrt werden: # # sh /opt/gerbilmanager/deploy/truenas/scripts/deploy.sh # TAG= sh .../deploy.sh # Rollback auf ein bestimmtes Image # GIT_PULL=0 sh .../deploy.sh # ohne 'git pull' (nur Images neu ziehen) # # Konfiguration ueber Umgebungsvariablen (alle optional, sinnvolle Defaults): # COMPOSE_DIR Installationsverzeichnis (Default: /opt/gerbilmanager) # TAG Image-Tag (Default: latest; fuer Rollback z. B. ein git-SHA) # GIT_PULL 1 = vorher 'git pull' (Default), 0 = ueberspringen # HEALTH_TIMEOUT Sekunden auf api-Healthcheck warten (Default: 180) # REGISTRY / REGISTRY_USER / REGISTRY_TOKEN optionaler docker login vor dem Pull set -eu COMPOSE_DIR="${COMPOSE_DIR:-/opt/gerbilmanager}" COMPOSE_FILE="$COMPOSE_DIR/deploy/truenas/compose.yaml" TAG="${TAG:-latest}" GIT_PULL="${GIT_PULL:-1}" HEALTH_TIMEOUT="${HEALTH_TIMEOUT:-180}" REGISTRY="${REGISTRY:-git.rismer.de}" log() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1"; } fail() { log "FEHLER: $1"; exit 1; } export TAG # compose interpoliert ${TAG} in den Image-Referenzen log "=== Deploy gestartet (TAG=$TAG, dir=$COMPOSE_DIR) ===" [ -f "$COMPOSE_FILE" ] || fail "compose.yaml nicht gefunden: $COMPOSE_FILE" command -v docker >/dev/null 2>&1 || fail "docker nicht im PATH des Runners" docker compose version >/dev/null 2>&1 || fail "'docker compose' (v2) nicht verfuegbar" cd "$COMPOSE_DIR" # --- 1. Repo aktualisieren (compose.yaml / Skripte / Docs) --- if [ "$GIT_PULL" = "1" ] && [ -d "$COMPOSE_DIR/.git" ]; then log "git pull --ff-only ..." git -C "$COMPOSE_DIR" pull --ff-only || log "WARNUNG: git pull fehlgeschlagen — fahre mit vorhandenem Stand fort" fi # --- 2. Registry-Login (nur wenn Credentials uebergeben wurden) --- if [ -n "${REGISTRY_USER:-}" ] && [ -n "${REGISTRY_TOKEN:-}" ]; then log "docker login $REGISTRY ..." echo "$REGISTRY_TOKEN" | docker login "$REGISTRY" -u "$REGISTRY_USER" --password-stdin >/dev/null \ || fail "docker login fehlgeschlagen" fi # --- 3. Neue Images ziehen --- log "Ziehe Images (Tag: $TAG) ..." docker compose -f "$COMPOSE_FILE" pull || fail "docker compose pull fehlgeschlagen" # --- 4. Stack neu ausrollen (nur geaenderte Container werden neu erstellt) --- log "Rolle Stack aus (up -d) ..." docker compose -f "$COMPOSE_FILE" up -d --remove-orphans || fail "docker compose up fehlgeschlagen" # --- 5. Auf api-Healthcheck warten (impliziert db gesund) --- log "Warte auf api-Healthcheck (max ${HEALTH_TIMEOUT}s) ..." API_CID="$(docker compose -f "$COMPOSE_FILE" ps -q api)" [ -n "$API_CID" ] || fail "api-Container nicht gefunden" elapsed=0 while :; do state="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$API_CID" 2>/dev/null || echo unknown)" case "$state" in healthy) log "api ist gesund."; break ;; none|unknown) # Kein Healthcheck definiert/lesbar -> auf laufenden Zustand zurueckfallen. running="$(docker inspect --format '{{.State.Running}}' "$API_CID" 2>/dev/null || echo false)" [ "$running" = "true" ] && { log "api laeuft (kein Healthcheck-Status lesbar)."; break; } ;; esac if [ "$elapsed" -ge "$HEALTH_TIMEOUT" ]; then log "Letzte api-Logs:"; docker compose -f "$COMPOSE_FILE" logs --tail 40 api || true fail "api wurde nicht innerhalb ${HEALTH_TIMEOUT}s gesund (Status: $state)" fi sleep 5; elapsed=$((elapsed + 5)) done # --- 6. Alte/ungenutzte Images aufraeumen --- log "Raeume verwaiste Images auf ..." docker image prune -f >/dev/null 2>&1 || true log "Laufende Dienste:" docker compose -f "$COMPOSE_FILE" ps --format 'table {{.Service}}\t{{.Status}}' 2>/dev/null \ || docker compose -f "$COMPOSE_FILE" ps log "=== Deploy erfolgreich abgeschlossen (TAG=$TAG) ==="