From 8ae792fd902157c15f3945461ddf018b4e9e106d Mon Sep 17 00:00:00 2001 From: patrick Date: Wed, 8 Jul 2026 16:29:43 +0200 Subject: [PATCH] chore(deploy): entkoppelte Self-Update-Skripte pro Server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Jeder Server aktualisiert sich eigenstaendig aus dem Repo – kein Cross-Server-SSH, kein "both". 137 und 164 komplett getrennt. - server-update.sh: laeuft AUF dem Server (git pull --ff-only → alembic upgrade head → restart → health). Guard: bricht ab wenn venv fehlt. - push-frontend.sh: lokal, baut Frontend + rsync dist/ an EINEN Server (Node laeuft nicht auf den Servern, dist ist gitignored). Guard gegen Ausfuehrung auf einem Zielserver. - deploy.sh + update.sh stillgelegt (Abbruch-Stubs): deploy.sh rsyncte den Arbeitsbaum in den Server-git-Baum und verschmutzte ihn (git pull brach); update.sh koppelte beide Server ("both"). Co-Authored-By: Claude Opus 4.8 --- deploy.sh | 121 ++-------- push-frontend.sh | 49 +++++ server-update.sh | 68 ++++++ update.sh | 560 +---------------------------------------------- 4 files changed, 142 insertions(+), 656 deletions(-) create mode 100755 push-frontend.sh create mode 100755 server-update.sh diff --git a/deploy.sh b/deploy.sh index 054f6e5..498a09a 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,107 +1,14 @@ -#!/bin/bash -# ============================================================ -# deploy.sh – Lokal ausführen, deployt auf 192.168.1.137 -# Usage: -# ./deploy.sh → nur sync + restart -# ./deploy.sh --migrate → sync + alembic upgrade + restart -# ./deploy.sh --setup → erstes Setup auf dem Server -# ============================================================ -set -e - -SERVER="root@192.168.1.137" -REMOTE="/opt/timemaster" -LOCAL="$(cd "$(dirname "$0")" && pwd)" - -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' - -log() { echo -e "${GREEN}==>${NC} $1"; } -warn() { echo -e "${YELLOW}[!]${NC} $1"; } - -# ── Erstes Setup ────────────────────────────────────────────────────────────── -if [[ "$1" == "--setup" ]]; then - log "Erstes Server-Setup wird ausgeführt..." - - ssh "$SERVER" "apt-get update -q && apt-get install -y \ - python3.12 python3.12-venv python3.12-dev \ - postgresql postgresql-contrib \ - redis-server nginx \ - git build-essential libpq-dev curl" - - ssh "$SERVER" "mkdir -p $REMOTE/backend $REMOTE/frontend/dist" - - log "Dateien übertragen..." - rsync -avz --exclude='__pycache__' --exclude='*.pyc' --exclude='.env' \ - --exclude='venv/' --exclude='.git/' \ - "$LOCAL/" "$SERVER:$REMOTE/" - - ssh "$SERVER" bash << 'REMOTE_SETUP' - set -e - cd /opt/timemaster/backend - - # venv - python3.12 -m venv venv - source venv/bin/activate - pip install --upgrade pip -q - pip install -r requirements.txt -q - - # PostgreSQL - systemctl enable --now postgresql - sudo -u postgres psql -tc "SELECT 1 FROM pg_roles WHERE rolname='timemaster'" | grep -q 1 || \ - sudo -u postgres psql -c "CREATE ROLE timemaster LOGIN PASSWORD 'timemaster_secret_CHANGE_ME';" - sudo -u postgres psql -tc "SELECT 1 FROM pg_database WHERE datname='timemaster_db'" | grep -q 1 || \ - sudo -u postgres psql -c "CREATE DATABASE timemaster_db OWNER timemaster;" - - # Redis - systemctl enable --now redis-server - - # .env - if [ ! -f .env ]; then - cp .env.example .env - echo "" - echo "⚠️ WICHTIG: .env auf dem Server anpassen!" - echo " ssh root@192.168.1.137 'nano /opt/timemaster/backend/.env'" - fi - - # systemd service - cp /opt/timemaster/timemaster.service /etc/systemd/system/ - systemctl daemon-reload - - echo "✓ Setup abgeschlossen" - echo "Nächster Schritt: .env befüllen, dann: ./deploy.sh --migrate" -REMOTE_SETUP - exit 0 -fi - -# ── Normaler Deploy ─────────────────────────────────────────────────────────── -log "Sync → $SERVER:$REMOTE/backend" -rsync -avz --progress \ - --exclude='__pycache__' \ - --exclude='*.pyc' \ - --exclude='.env' \ - --exclude='venv/' \ - --exclude='.git/' \ - --exclude='tests/' \ - "$LOCAL/backend/" "$SERVER:$REMOTE/backend/" - -# Frontend build + sync (falls vorhanden) -if [ -d "$LOCAL/frontend/dist" ]; then - log "Frontend sync..." - rsync -avz "$LOCAL/frontend/dist/" "$SERVER:$REMOTE/frontend/dist/" -fi - -# ── Migration (optional) ────────────────────────────────────────────────────── -if [[ "$1" == "--migrate" ]]; then - log "Alembic upgrade head..." - ssh "$SERVER" "cd $REMOTE/backend && source venv/bin/activate && alembic upgrade head" -fi - -# ── Service neu starten ─────────────────────────────────────────────────────── -log "Service restart..." -ssh "$SERVER" "systemctl restart timemaster && sleep 2 && systemctl is-active timemaster" - -log "Logs (letzte 20 Zeilen):" -ssh "$SERVER" "journalctl -u timemaster -n 20 --no-pager" - -log "✓ Deploy abgeschlossen → http://192.168.1.137:8000/health" +#!/usr/bin/env bash +# ============================================================================= +# STILLGELEGT – nicht mehr verwenden. +# +# Dieses Skript rsyncte das lokale Arbeitsverzeichnis in den git-Baum des +# Servers und verschmutzte ihn dadurch (spätere 'git pull' schlugen fehl). +# +# Neuer, entkoppelter Ablauf (jeder Server macht sich selbst): +# 1. Lokal: git add -A && git commit && git push origin main +# 2. Server: ssh root@ 'cd /opt/timemaster && ./server-update.sh' +# 3. Frontend (falls geändert, lokal): ./push-frontend.sh <137|164> +# ============================================================================= +echo "deploy.sh ist stillgelegt. Nutze: git push → server-update.sh (auf dem Server) → push-frontend.sh (lokal)." >&2 +exit 1 diff --git a/push-frontend.sh b/push-frontend.sh new file mode 100755 index 0000000..effb8e8 --- /dev/null +++ b/push-frontend.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# ============================================================================= +# push-frontend.sh – LOKAL auf dem Dev-Rechner ausführen +# +# Baut das Frontend und liefert die dist/ an GENAU EINEN Server. +# Nötig, weil auf den Servern kein Node läuft und frontend/dist gitignored ist +# (server-update.sh kann das Frontend also nicht selbst bauen). +# +# Usage: +# ./push-frontend.sh 137 # → root@192.168.1.137 +# ./push-frontend.sh 164 # → root@192.168.1.164 +# ./push-frontend.sh 137 --no-build # bestehendes dist/ nur syncen +# ============================================================================= +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +FRONTEND_DIR="$SCRIPT_DIR/frontend" +REMOTE_PATH="/opt/timemaster" + +GREEN='\033[0;32m'; RED='\033[0;31m'; NC='\033[0m' +log() { echo -e "${GREEN}==>${NC} $*"; } +die() { echo -e "${RED}[FEHLER]${NC} $*" >&2; exit 1; } + +TARGET="${1:-}" +case "$TARGET" in + 137) SERVER="root@192.168.1.137" ;; + 164) SERVER="root@192.168.1.164" ;; + *) die "Server angeben: 137 oder 164 (z.B. ./push-frontend.sh 137)" ;; +esac + +# Schutz: nicht versehentlich auf dem Zielserver ausführen +for ip in 192.168.1.137 192.168.1.164; do + [[ " $(hostname -I 2>/dev/null) " == *" $ip "* ]] && die "Läuft auf einem Zielserver – nur lokal auf dem Dev-Rechner ausführen." +done + +DO_BUILD=true +[[ "${2:-}" == "--no-build" ]] && DO_BUILD=false + +if $DO_BUILD; then + log "npm run build" + ( cd "$FRONTEND_DIR" && npm run build ) || die "Frontend-Build fehlgeschlagen." +fi +[[ -f "$FRONTEND_DIR/dist/index.html" ]] || die "dist/index.html fehlt – erst bauen." + +log "rsync dist/ → $SERVER:$REMOTE_PATH/frontend/dist/" +rsync -az --delete "$FRONTEND_DIR/dist/" "$SERVER:$REMOTE_PATH/frontend/dist/" \ + || die "rsync fehlgeschlagen (SSH-Key/Host-Key prüfen)." + +log "✓ Frontend auf $TARGET aktualisiert" diff --git a/server-update.sh b/server-update.sh new file mode 100755 index 0000000..60be85a --- /dev/null +++ b/server-update.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# ============================================================================= +# server-update.sh – AUF dem jeweiligen Server ausführen (nicht lokal!) +# +# Aktualisiert GENAU DIESEN Server eigenständig aus dem Gitea-Repo: +# git pull → alembic upgrade head → service restart → health-check +# +# Jeder Server macht sich selbst – kein Cross-Server-SSH, kein "both". +# 137 und 164 sind komplett entkoppelt. +# +# Frontend: wird NICHT hier gebaut (kein Node auf den Servern, dist ist +# gitignored). Frontend liefert der Dev-Rechner separat per push-frontend.sh. +# +# Usage (auf dem Server): +# cd /opt/timemaster && ./server-update.sh +# ./server-update.sh --no-migrate # nur pull + restart +# ============================================================================= +set -euo pipefail + +REMOTE="/opt/timemaster" +SERVICE="timemaster" +HEALTH_URL="http://localhost:8000/health" + +GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m'; NC='\033[0m' +log() { echo -e "${GREEN}==>${NC} $*"; } +warn() { echo -e "${YELLOW}[!]${NC} $*"; } +die() { echo -e "${RED}[FEHLER]${NC} $*" >&2; exit 1; } + +DO_MIGRATE=true +[[ "${1:-}" == "--no-migrate" ]] && DO_MIGRATE=false + +# ── Sicherstellen, dass wir wirklich auf einem Server sind ──────────────────── +[[ -d "$REMOTE/backend/venv" ]] || die "venv fehlt ($REMOTE/backend/venv) – falscher Host? Dieses Skript läuft AUF dem Server." +cd "$REMOTE" +git config --global --add safe.directory "$REMOTE" 2>/dev/null || true + +# ── 1. Repo aktualisieren ───────────────────────────────────────────────────── +log "git pull --ff-only origin main" +if ! git pull --ff-only origin main; then + die "git pull fehlgeschlagen. Arbeitsbaum sauber? ('git status' prüfen, ggf. 'git stash')." +fi +log "Jetzt auf: $(git log --oneline -1)" + +# ── 2. Migration ────────────────────────────────────────────────────────────── +if $DO_MIGRATE; then + log "alembic upgrade head" + ( cd "$REMOTE/backend" && source venv/bin/activate && alembic upgrade head ) \ + || die "Migration fehlgeschlagen – Service wird NICHT neugestartet (Schema evtl. inkonsistent)." +else + warn "Migration übersprungen (--no-migrate)" +fi + +# ── 3. Service neustarten ───────────────────────────────────────────────────── +log "systemctl restart $SERVICE" +systemctl restart "$SERVICE" +sleep 2 +state="$(systemctl is-active "$SERVICE" || true)" +[[ "$state" == "active" ]] || { journalctl -u "$SERVICE" -n 30 --no-pager; die "Service nicht aktiv ($state)."; } +log "Service aktiv" + +# ── 4. Health-Check ─────────────────────────────────────────────────────────── +if curl -sf --max-time 10 "$HEALTH_URL" >/dev/null; then + log "Health OK ($HEALTH_URL)" +else + warn "Health-Check fehlgeschlagen – Logs prüfen: journalctl -u $SERVICE -n 50" +fi + +log "✓ Update abgeschlossen auf $(hostname)" diff --git a/update.sh b/update.sh index 8916b10..fff0a5b 100755 --- a/update.sh +++ b/update.sh @@ -1,554 +1,16 @@ #!/usr/bin/env bash # ============================================================================= -# TimeMaster – Deployment & Update Script -# Lokal ausführen – verbindet sich per SSH auf Produktivserver +# STILLGELEGT – nicht mehr verwenden. # -# Usage: -# ./update.sh [OPTIONS] +# Alte Variante: lokal starten, SSHt in BEIDE Server ("both") und deployt sie +# gekoppelt. Nicht gewünscht – 137 und 164 sollen entkoppelt sein, jeder Server +# aktualisiert sich selbst. # -# Options: -# --no-tests Tests überspringen -# --no-frontend Frontend-Build und -Sync überspringen -# --server 137 Nur Server 192.168.1.137 -# --server 164 Nur Server 192.168.1.164 -# --server both Beide Server (Standard) -# --dry-run Alle Befehle ausgeben, aber NICHT ausführen -# --help Diese Hilfe anzeigen -# -# Schritte (Reihenfolge ist kritisch): -# 1. Tests auf Server 137 ausführen (via SSH) -# 2. Frontend bauen (lokal, npm run build) -# 3. git pull origin main auf Server(n) -# 4. Alembic-Migration auf Server ausführen -# 5. Service neustarten -# 6. Health-Check (3 Versuche) -# 7. Frontend-Dist synchronisieren -# 8. Abschlussbericht +# Neuer, entkoppelter Ablauf: +# 1. Lokal: git add -A && git commit && git push origin main +# 2. Server: ssh root@ 'cd /opt/timemaster && ./server-update.sh' +# (jeder Server einzeln, unabhängig – kein Cross-Server-SSH) +# 3. Frontend (falls geändert, lokal): ./push-frontend.sh <137|164> # ============================================================================= - -set -euo pipefail - -# ============================================================================= -# KONFIGURATION -# ============================================================================= -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -BACKEND_DIR="$SCRIPT_DIR/backend" -FRONTEND_DIR="$SCRIPT_DIR/frontend" - -SERVER_137="root@192.168.1.137" -SERVER_164="root@192.168.1.164" -REMOTE_PATH="/opt/timemaster" -REMOTE_VENV="$REMOTE_PATH/backend/venv" - -HEALTH_RETRIES=3 -HEALTH_WAIT=3 - -# ============================================================================= -# FARBEN & AUSGABE -# ============================================================================= -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -BOLD='\033[1m' -RESET='\033[0m' - -step() { echo -e "\n${BLUE}${BOLD}▶ $*${RESET}"; } -ok() { echo -e " ${GREEN}✓ $*${RESET}"; } -fail() { echo -e " ${RED}✗ $*${RESET}"; } -warn() { echo -e " ${YELLOW}⚠ $*${RESET}"; } -info() { echo -e " ${RESET}$*${RESET}"; } -hr() { echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"; } - -die() { - echo "" - fail "$1" - echo "" - hr - echo -e "${RED}${BOLD} ABBRUCH: $1${RESET}" - hr - print_summary - exit 1 -} - -# ============================================================================= -# ARGUMENT-PARSING -# ============================================================================= -OPT_TESTS=true -OPT_FRONTEND=true -OPT_SERVER="both" -OPT_DRYRUN=false - -while [[ $# -gt 0 ]]; do - case "$1" in - --no-tests) - OPT_TESTS=false - shift ;; - --no-frontend) - OPT_FRONTEND=false - shift ;; - --server) - if [[ -z "${2:-}" ]]; then - die "--server benötigt einen Wert: 137, 164 oder both" - fi - OPT_SERVER="$2" - if [[ "$OPT_SERVER" != "137" && "$OPT_SERVER" != "164" && "$OPT_SERVER" != "both" ]]; then - die "--server muss 137, 164 oder both sein (nicht: $OPT_SERVER)" - fi - shift 2 ;; - --dry-run) - OPT_DRYRUN=true - shift ;; - --help|-h) - # Zeige nur den Header-Kommentarblock (bis zur ersten Leerzeile im Kommentar-Block) - awk '/^#!/{next} /^#/{sub(/^# ?/,""); print; next} /^[^#]/{exit}' "$0" - exit 0 ;; - *) - die "Unbekanntes Argument: $1 (--help für Hilfe)" - ;; - esac -done - -# Server-Liste aufbauen -SERVERS=() -case "$OPT_SERVER" in - 137) SERVERS=("$SERVER_137") ;; - 164) SERVERS=("$SERVER_164") ;; - both) SERVERS=("$SERVER_137" "$SERVER_164") ;; -esac - -# ============================================================================= -# ZUSAMMENFASSUNGS-TRACKING -# ============================================================================= -START_TIME=$(date +%s) -declare -A STEP_STATUS # name → "OK" | "FAIL" | "SKIP" | "WARN" -declare -a STEP_ORDER=() - -step_record() { - local name="$1" status="$2" - STEP_STATUS["$name"]="$status" - STEP_ORDER+=("$name") -} - -print_summary() { - local end_time elapsed - end_time=$(date +%s) - elapsed=$(( end_time - START_TIME )) - - echo "" - hr - echo -e "${BOLD} Abschlussbericht (Dauer: ${elapsed}s)${RESET}" - hr - for name in "${STEP_ORDER[@]}"; do - local status="${STEP_STATUS[$name]}" - case "$status" in - OK) echo -e " ${GREEN}✓ $name${RESET}" ;; - FAIL) echo -e " ${RED}✗ $name${RESET}" ;; - SKIP) echo -e " ${YELLOW}– $name (übersprungen)${RESET}" ;; - WARN) echo -e " ${YELLOW}⚠ $name${RESET}" ;; - esac - done - hr -} - -# ============================================================================= -# HILFSFUNKTIONEN -# ============================================================================= - -# Führt einen Befehl aus oder gibt ihn im Dry-Run nur aus -run_cmd() { - if $OPT_DRYRUN; then - echo -e " ${YELLOW}[DRY-RUN]${RESET} $*" - return 0 - fi - "$@" -} - -# SSH-Befehl mit Fehlerbehandlung -run_ssh() { - local server="$1" - shift - local cmd="$*" - if $OPT_DRYRUN; then - echo -e " ${YELLOW}[DRY-RUN]${RESET} ssh $server \"$cmd\"" - return 0 - fi - if ! ssh -o ConnectTimeout=10 -o BatchMode=yes "$server" "$cmd"; then - return 1 - fi -} - -# rsync mit Fehlerbehandlung -run_rsync() { - if $OPT_DRYRUN; then - echo -e " ${YELLOW}[DRY-RUN]${RESET} rsync $*" - return 0 - fi - rsync "$@" -} - -# SSH-Erreichbarkeit prüfen -check_ssh() { - local server="$1" - if $OPT_DRYRUN; then - info "Dry-Run: SSH-Check für $server übersprungen" - return 0 - fi - if ! ssh -o ConnectTimeout=10 -o BatchMode=yes "$server" "true" 2>/dev/null; then - return 1 - fi -} - -# ============================================================================= -# HEADER -# ============================================================================= -hr -echo -e "${BOLD} TimeMaster Deployment Script${RESET}" -echo -e " Datum: $(date '+%Y-%m-%d %H:%M:%S')" -echo -e " Server: ${SERVERS[*]}" -echo -e " Tests: $(if $OPT_TESTS; then echo "ja"; else echo "nein (--no-tests)"; fi)" -echo -e " Frontend: $(if $OPT_FRONTEND; then echo "ja"; else echo "nein (--no-frontend)"; fi)" -if $OPT_DRYRUN; then - echo -e " ${YELLOW}${BOLD}Modus: DRY-RUN (keine Änderungen werden durchgeführt)${RESET}" -fi -hr - -# ============================================================================= -# PRE-FLIGHT: Lokale Verzeichnisse prüfen -# ============================================================================= -step "Pre-Flight: Lokale Verzeichnisse prüfen" - -if [[ ! -d "$BACKEND_DIR" ]]; then - die "Backend-Verzeichnis nicht gefunden: $BACKEND_DIR" -fi -ok "Backend-Verzeichnis vorhanden: $BACKEND_DIR" - -if $OPT_FRONTEND; then - if [[ ! -d "$FRONTEND_DIR" ]]; then - die "Frontend-Verzeichnis nicht gefunden: $FRONTEND_DIR" - fi - if [[ ! -f "$FRONTEND_DIR/package.json" ]]; then - die "package.json nicht gefunden: $FRONTEND_DIR/package.json" - fi - ok "Frontend-Verzeichnis vorhanden: $FRONTEND_DIR" -fi - -# SSH-Erreichbarkeit aller Zielserver prüfen -for server in "${SERVERS[@]}"; do - step "Pre-Flight: SSH-Verbindung zu $server prüfen" - if check_ssh "$server"; then - ok "SSH-Verbindung zu $server erfolgreich" - else - die "SSH-Verbindung zu $server fehlgeschlagen.\n Prüfe: ssh $server\n SSH-Key konfiguriert?" - fi -done - -step_record "Pre-Flight" "OK" - -# ============================================================================= -# SCHRITT 1: TESTS (auf Server 137) -# ============================================================================= -if ! $OPT_TESTS; then - step "Schritt 1: Tests (übersprungen via --no-tests)" - step_record "1. Tests" "SKIP" -else - step "Schritt 1: Tests auf $SERVER_137 ausführen" - info "Befehl: cd /opt/timemaster/backend && source venv/bin/activate && python -m pytest -x -q" - - TEST_CMD="cd $REMOTE_PATH/backend && source $REMOTE_VENV/bin/activate && python -m pytest -x -q 2>&1" - - if $OPT_DRYRUN; then - echo -e " ${YELLOW}[DRY-RUN]${RESET} ssh $SERVER_137 \"$TEST_CMD\"" - step_record "1. Tests" "SKIP" - else - TEST_OUTPUT=$(ssh -o ConnectTimeout=30 -o BatchMode=yes "$SERVER_137" "$TEST_CMD" 2>&1) || true - TEST_RC=${PIPESTATUS[0]:-$?} - - # Ausgabe der Tests anzeigen - echo "$TEST_OUTPUT" | while IFS= read -r line; do - info "$line" - done - - # Prüfen ob Tests fehlgeschlagen (pytest gibt RC != 0 bei Fehler) - if echo "$TEST_OUTPUT" | grep -qE "^(FAILED|ERROR|[0-9]+ failed)"; then - fail "Tests FEHLGESCHLAGEN" - echo "" - echo -e " ${RED}Fehlerdetails:${RESET}" - echo "$TEST_OUTPUT" | grep -A 5 -E "^(FAILED|ERROR)" | while IFS= read -r line; do - echo -e " $line" - done - step_record "1. Tests" "FAIL" - die "Tests fehlgeschlagen – Deploy abgebrochen. Keine Dateien wurden auf den Server übertragen." - fi - - # Erfolgsmeldung aus pytest-Ausgabe extrahieren - PASSED_LINE=$(echo "$TEST_OUTPUT" | grep -E "passed" | tail -1 || echo "") - if [[ -n "$PASSED_LINE" ]]; then - ok "Tests bestanden: $PASSED_LINE" - else - ok "Tests abgeschlossen ohne Fehler" - fi - step_record "1. Tests" "OK" - fi -fi - -# ============================================================================= -# SCHRITT 2: FRONTEND BAUEN -# ============================================================================= -if ! $OPT_FRONTEND; then - step "Schritt 2: Frontend-Build (übersprungen via --no-frontend)" - step_record "2. Frontend-Build" "SKIP" -else - step "Schritt 2: Frontend bauen (npm run build)" - info "Verzeichnis: $FRONTEND_DIR" - - if $OPT_DRYRUN; then - echo -e " ${YELLOW}[DRY-RUN]${RESET} cd $FRONTEND_DIR && npm run build" - step_record "2. Frontend-Build" "SKIP" - else - BUILD_OUTPUT=$(cd "$FRONTEND_DIR" && npm run build 2>&1) || BUILD_RC=$? - BUILD_RC=${BUILD_RC:-0} - - if [[ $BUILD_RC -ne 0 ]]; then - fail "Frontend-Build FEHLGESCHLAGEN (Exit-Code: $BUILD_RC)" - echo "" - echo -e " ${RED}Build-Ausgabe:${RESET}" - echo "$BUILD_OUTPUT" | tail -30 | while IFS= read -r line; do - echo -e " $line" - done - step_record "2. Frontend-Build" "FAIL" - die "Frontend-Build fehlgeschlagen – Deploy abgebrochen." - fi - - if [[ ! -d "$FRONTEND_DIR/dist" ]]; then - step_record "2. Frontend-Build" "FAIL" - die "Frontend-Build scheinbar erfolgreich, aber dist/-Verzeichnis fehlt: $FRONTEND_DIR/dist" - fi - - DIST_SIZE=$(du -sh "$FRONTEND_DIR/dist" | cut -f1) - ok "Frontend gebaut: $FRONTEND_DIR/dist ($DIST_SIZE)" - step_record "2. Frontend-Build" "OK" - fi -fi - -# ============================================================================= -# SCHRITT 3, 4, 5, 6, 7: PRO SERVER AUSFÜHREN -# ============================================================================= -SERVER_RESULTS=() -ALL_SERVERS_OK=true - -for server in "${SERVERS[@]}"; do - SERVER_SHORT="${server##*@}" # "192.168.1.137" aus "root@192.168.1.137" - echo "" - hr - echo -e "${BOLD} Server: $server${RESET}" - hr - - SERVER_OK=true - - # ------------------------------------------------------------------------- - # SCHRITT 3: git pull vom Gitea - # ------------------------------------------------------------------------- - step "[$SERVER_SHORT] Schritt 3: git pull (origin/main)" - - GIT_PULL_CMD="git config --global --add safe.directory $REMOTE_PATH 2>/dev/null; cd $REMOTE_PATH && git pull --ff-only origin main 2>&1" - - if $OPT_DRYRUN; then - echo -e " ${YELLOW}[DRY-RUN]${RESET} ssh $server \"$GIT_PULL_CMD\"" - step_record "3. git pull [$SERVER_SHORT]" "SKIP" - else - GIT_OUTPUT=$(ssh -o ConnectTimeout=30 -o BatchMode=yes "$server" "$GIT_PULL_CMD" 2>&1) - GIT_RC=$? - echo "$GIT_OUTPUT" | while IFS= read -r line; do info "$line"; done - if [ $GIT_RC -eq 0 ]; then - ok "git pull erfolgreich" - step_record "3. git pull [$SERVER_SHORT]" "OK" - else - fail "git pull FEHLGESCHLAGEN (Exit-Code: $GIT_RC)" - step_record "3. git pull [$SERVER_SHORT]" "FAIL" - SERVER_OK=false - ALL_SERVERS_OK=false - warn "Server $server wird übersprungen (git pull fehlgeschlagen)" - continue - fi - fi - - # ------------------------------------------------------------------------- - # SCHRITT 4: Alembic-Migration - # ------------------------------------------------------------------------- - step "[$SERVER_SHORT] Schritt 4: Alembic-Migration" - MIGRATE_CMD="cd $REMOTE_PATH/backend && source $REMOTE_VENV/bin/activate && alembic upgrade head 2>&1" - info "Befehl: $MIGRATE_CMD" - - if $OPT_DRYRUN; then - echo -e " ${YELLOW}[DRY-RUN]${RESET} ssh $server \"$MIGRATE_CMD\"" - step_record "4. Migration [$SERVER_SHORT]" "SKIP" - else - MIGRATE_OUTPUT=$(ssh -o ConnectTimeout=30 -o BatchMode=yes "$server" "$MIGRATE_CMD" 2>&1) || MIGRATE_RC=$? - MIGRATE_RC=${MIGRATE_RC:-0} - - echo "$MIGRATE_OUTPUT" | while IFS= read -r line; do - info "$line" - done - - if [[ $MIGRATE_RC -ne 0 ]]; then - fail "Alembic-Migration FEHLGESCHLAGEN (Exit-Code: $MIGRATE_RC)" - echo "" - warn "Service wird NICHT neugestartet (DB-Schema könnte inkonsistent sein)" - echo "" - echo -e " ${RED}Letzte 20 Server-Log-Zeilen:${RESET}" - ssh -o ConnectTimeout=10 -o BatchMode=yes "$server" \ - "journalctl -u timemaster -n 20 --no-pager 2>/dev/null || echo '(keine Logs verfügbar)'" \ - | while IFS= read -r line; do echo -e " $line"; done - echo "" - echo -e " ${YELLOW}Diagnose-Befehle:${RESET}" - echo -e " ssh $server 'cd $REMOTE_PATH/backend && source $REMOTE_VENV/bin/activate && alembic current'" - echo -e " ssh $server 'cd $REMOTE_PATH/backend && source $REMOTE_VENV/bin/activate && alembic history --verbose'" - step_record "4. Migration [$SERVER_SHORT]" "FAIL" - SERVER_OK=false - ALL_SERVERS_OK=false - warn "Server $server wird übersprungen (Migration fehlgeschlagen, Service NICHT neugestartet)" - continue - fi - - # Prüfe ob Migration "up to date" ist (kein Fehler, aber auch keine Änderung) - if echo "$MIGRATE_OUTPUT" | grep -qi "already up to date\|no migrations"; then - ok "Migration: bereits aktuell (keine neuen Migrationen)" - else - ok "Migration erfolgreich" - fi - step_record "4. Migration [$SERVER_SHORT]" "OK" - fi - - # ------------------------------------------------------------------------- - # SCHRITT 5: Service neustarten - # ------------------------------------------------------------------------- - step "[$SERVER_SHORT] Schritt 5: Service neustarten" - - if run_ssh "$server" "systemctl restart timemaster"; then - ok "Service neugestartet" - step_record "5. Service-Restart [$SERVER_SHORT]" "OK" - else - RESTART_RC=$? - fail "systemctl restart timemaster FEHLGESCHLAGEN (Exit-Code: $RESTART_RC)" - echo "" - echo -e " ${RED}Letzte 30 Journal-Zeilen:${RESET}" - ssh -o ConnectTimeout=10 -o BatchMode=yes "$server" \ - "journalctl -u timemaster -n 30 --no-pager 2>/dev/null || echo '(keine Logs verfügbar)'" \ - | while IFS= read -r line; do echo -e " $line"; done - echo "" - echo -e " ${YELLOW}Diagnose-Befehle:${RESET}" - echo -e " ssh $server 'systemctl status timemaster'" - echo -e " ssh $server 'journalctl -u timemaster -n 100 --no-pager'" - step_record "5. Service-Restart [$SERVER_SHORT]" "FAIL" - SERVER_OK=false - ALL_SERVERS_OK=false - warn "Server $server: Service-Start fehlgeschlagen" - continue - fi - - # ------------------------------------------------------------------------- - # SCHRITT 6: Health-Check - # ------------------------------------------------------------------------- - step "[$SERVER_SHORT] Schritt 6: Health-Check (${HEALTH_RETRIES} Versuche, ${HEALTH_WAIT}s Pause)" - - if $OPT_DRYRUN; then - echo -e " ${YELLOW}[DRY-RUN]${RESET} ssh $server 'curl -sf http://localhost:8000/health'" - step_record "6. Health-Check [$SERVER_SHORT]" "SKIP" - else - HEALTH_OK=false - for attempt in $(seq 1 $HEALTH_RETRIES); do - info "Versuch $attempt/$HEALTH_RETRIES..." - HEALTH_OUTPUT=$(ssh -o ConnectTimeout=10 -o BatchMode=yes "$server" \ - "curl -sf --connect-timeout 5 --max-time 10 http://localhost:8000/health 2>&1") \ - && HEALTH_RC=0 || HEALTH_RC=$? - - if [[ $HEALTH_RC -eq 0 ]]; then - ok "Health-Check erfolgreich (Versuch $attempt): $HEALTH_OUTPUT" - HEALTH_OK=true - break - else - warn "Versuch $attempt fehlgeschlagen" - if [[ $attempt -lt $HEALTH_RETRIES ]]; then - info "Warte ${HEALTH_WAIT}s..." - sleep "$HEALTH_WAIT" - fi - fi - done - - if ! $HEALTH_OK; then - fail "Health-Check nach $HEALTH_RETRIES Versuchen FEHLGESCHLAGEN" - echo "" - warn "Service läuft möglicherweise nicht korrekt. Log-Befehle:" - echo -e " ${YELLOW}ssh $server 'journalctl -u timemaster -n 50 --no-pager'${RESET}" - echo -e " ${YELLOW}ssh $server 'systemctl status timemaster'${RESET}" - echo -e " ${YELLOW}ssh $server 'curl -v http://localhost:8000/health'${RESET}" - step_record "6. Health-Check [$SERVER_SHORT]" "WARN" - SERVER_OK=false - ALL_SERVERS_OK=false - # KEIN Abbruch hier – nur Warnung, Frontend noch synchronisieren - else - step_record "6. Health-Check [$SERVER_SHORT]" "OK" - fi - fi - - # ------------------------------------------------------------------------- - # SCHRITT 7: Frontend-Dist synchronisieren - # ------------------------------------------------------------------------- - if ! $OPT_FRONTEND; then - step "[$SERVER_SHORT] Schritt 7: Frontend-Sync (übersprungen via --no-frontend)" - step_record "7. Frontend-Sync [$SERVER_SHORT]" "SKIP" - else - step "[$SERVER_SHORT] Schritt 7: Frontend-Dist synchronisieren" - info "rsync: $FRONTEND_DIR/dist/ → $server:$REMOTE_PATH/frontend/dist/" - - if [[ ! -d "$FRONTEND_DIR/dist" ]] && ! $OPT_DRYRUN; then - warn "dist/-Verzeichnis nicht gefunden: $FRONTEND_DIR/dist" - warn "Frontend-Build wurde eventuell nicht ausgeführt (--no-frontend?)" - step_record "7. Frontend-Sync [$SERVER_SHORT]" "WARN" - else - if run_rsync -avz --delete \ - "$FRONTEND_DIR/dist/" \ - "$server:$REMOTE_PATH/frontend/dist/" ; then - ok "Frontend-Dist synchronisiert" - step_record "7. Frontend-Sync [$SERVER_SHORT]" "OK" - else - RSYNC_FE_RC=$? - fail "Frontend-rsync FEHLGESCHLAGEN (Exit-Code: $RSYNC_FE_RC)" - fail "Befehl war: rsync -avz --delete $FRONTEND_DIR/dist/ $server:$REMOTE_PATH/frontend/dist/" - step_record "7. Frontend-Sync [$SERVER_SHORT]" "FAIL" - SERVER_OK=false - ALL_SERVERS_OK=false - fi - fi - fi - - if $SERVER_OK; then - SERVER_RESULTS+=("${GREEN}✓ $server${RESET}") - else - SERVER_RESULTS+=("${YELLOW}⚠ $server (mit Warnungen/Fehlern)${RESET}") - fi - -done # Ende Server-Schleife - -# ============================================================================= -# ABSCHLUSSBERICHT -# ============================================================================= -echo "" -hr -echo -e "${BOLD} Server-Ergebnisse:${RESET}" -for result in "${SERVER_RESULTS[@]}"; do - echo -e " $result" -done - -print_summary - -if $ALL_SERVERS_OK; then - echo -e "${GREEN}${BOLD} DEPLOYMENT ERFOLGREICH${RESET}" -else - echo -e "${YELLOW}${BOLD} DEPLOYMENT MIT WARNUNGEN ABGESCHLOSSEN${RESET}" - echo -e "${YELLOW} Bitte die Fehlermeldungen oben prüfen.${RESET}" -fi -hr - -exit 0 +echo "update.sh ist stillgelegt. Nutze: git push → server-update.sh (auf dem Server) → push-frontend.sh (lokal)." >&2 +exit 1