chahinebrini 372aaa43dd fix(ci): pipeline race-condition + health-check retry + maestro secrets
Hauptproblem: Webhook-Deploy (deploy.sh) und GH-Actions-Deploy
(deploy-from-artifact.sh) liefen gleichzeitig → Race auf .output-staging
und doppelter pm2-restart.

Fixes:
- deploy-from-artifact.sh: setzt .deploy-ga.lock (noclobber, mit PID)
  während Deploy läuft; stale locks werden erkannt und überschrieben
- deploy.sh: prüft .deploy-ga.lock bei Start — wenn GH-Actions aktiv,
  sauberes exit 0 statt Kollision
- Health-Check: Retry-Loop (12× × 5s = max 60s) statt einmaligem sleep 5;
  Infisical-Login + Nitro-Start braucht auf gestresstem Server bis 30s
- maestro-cloud.yml: ungültiges `if: secrets.X != ''` entfernt (secrets
  in if-conditions sind in GH-Actions immer leer); stattdessen expliziter
  secrets-check als erster Step mit klarer Fehlermeldung
- pnpm --prefer-offline in deploy-from-artifact.sh: nutzt Store-Cache
- .gitignore: .deploy-ga.lock ergänzt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 09:32:25 +02:00

180 lines
7.1 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# deploy.sh Rebreak Deploy Script (backend/-Layout, post-cutover)
#
# Wird vom Webhook-Listener (scripts/deploy-webhook/server.mjs) aufgerufen.
# Repo-Root: /srv/rebreak
# Clone: git@github.com:RaynisDev/rebreak.git
# Backend: /srv/rebreak/backend (standalone Nitro, package: rebreak-backend)
#
# WICHTIG: GitHub Actions ist der primaere Deploy-Weg (deploy-from-artifact.sh).
# Dieses Script ist Fallback/Legacy-Pfad und wird NICHT ausgefuehrt wenn
# GH-Actions gerade deployed (.deploy-ga.lock).
#
# Ablauf:
# 1. Git pull (via Deploy-Key)
# 2. pnpm install --frozen-lockfile (mit hoisted node-linker via .npmrc)
# 3. cd backend && pnpm --filter rebreak-backend build (Prisma generate + Nitro build)
# 4. .output → .output-staging (atomisch via tmp)
# 5. pm2 restart rebreak-staging --update-env
# 6. pm2 restart rebreak-imap-staging / rebreak-idle-staging (best-effort, falls vorhanden)
# 7. pm2 restart dns-rebreak-staging / dns-rebreak (best-effort, falls vorhanden)
#
# Secrets: via Infisical (INFISICAL_CLIENT_ID/SECRET in /etc/environment) — NICHT hier,
# sondern in start-staging.sh / ecosystem.config.js zur Laufzeit.
set -euo pipefail
REPO_ROOT="/srv/rebreak"
APP_DIR="${REPO_ROOT}/backend"
NODE_BIN="/root/.nvm/versions/node/v24.11.1/bin/node"
PNPM_BIN="/root/.nvm/versions/node/v24.11.1/bin/pnpm"
PM2_BIN="/root/.nvm/versions/node/v24.11.1/bin/pm2"
GA_LOCK="${REPO_ROOT}/.deploy-ga.lock"
log() { echo "[deploy] $(date '+%H:%M:%S') $*"; }
log_err() { echo "[deploy:err] $(date '+%H:%M:%S') $*" >&2; }
log "=== Rebreak Deploy gestartet (backend/-Layout) ==="
# 0. Sicherstellen dass PATH stimmt
export PATH="/root/.nvm/versions/node/v24.11.1/bin:$PATH"
# 0a. GH-Actions-Lock pruefen: wenn deploy-from-artifact.sh laeuft, nicht doppeln.
if [[ -f "$GA_LOCK" ]]; then
LOCK_PID=$(cat "$GA_LOCK" 2>/dev/null || echo "")
if [[ -n "$LOCK_PID" ]] && kill -0 "$LOCK_PID" 2>/dev/null; then
log "GitHub-Actions-Deploy laeuft gerade (PID $LOCK_PID) -- Webhook-Deploy abgebrochen (kein Fehler)"
exit 0
else
log "Staler GA-Lock gefunden (PID $LOCK_PID) -- wird ignoriert"
rm -f "$GA_LOCK"
fi
fi
# 1. Git pull via Deploy-Key (SSH ist konfiguriert in /root/.ssh/config)
log "Step 1: git pull..."
cd "${REPO_ROOT}"
git fetch origin main 2>&1
git reset --hard origin/main 2>&1
git clean -fd 2>&1
log "Git updated to $(git rev-parse --short HEAD)"
# 2. pnpm install (workspace-root, .npmrc mit node-linker=hoisted ist im Repo)
log "Step 2: pnpm install --frozen-lockfile..."
cd "${REPO_ROOT}"
CI=true "${PNPM_BIN}" install --frozen-lockfile 2>&1 || {
log_err "frozen-lockfile fehlgeschlagen, fallback ohne frozen..."
CI=true "${PNPM_BIN}" install --no-frozen-lockfile 2>&1
}
log "pnpm install done"
# 2.5 Prisma-Migration auto-deploy wenn neue migration-files committed wurden
# Detect via git diff zwischen .last-deployed-sha und HEAD.
# Idempotent: prisma migrate deploy skipped already-applied migrations.
# Failure-Mode: bei Migration-Fehler pm2 NICHT restarten (alter Code/alte DB konsistent).
# First-deploy-Edge-Case: wenn .last-deployed-sha fehlt → Migration ausführen (sicher
# weil idempotent).
log "Step 2.5: Migration-Check..."
PREV_SHA=$(cat "${REPO_ROOT}/.last-deployed-sha" 2>/dev/null || echo "")
CUR_SHA=$(git -C "${REPO_ROOT}" rev-parse HEAD)
run_migration=false
if [[ -z "$PREV_SHA" ]]; then
log "Kein .last-deployed-sha gefunden — first-deploy: Migration sicherheitshalber ausführen"
run_migration=true
elif ! git -C "${REPO_ROOT}" diff --quiet "$PREV_SHA"..HEAD -- backend/prisma/migrations/ backend/prisma/schema.prisma; then
log "Migration-Changes detected zwischen ${PREV_SHA} und ${CUR_SHA}"
run_migration=true
else
log "Keine Migration-Changes seit ${PREV_SHA} — skip migrate deploy"
fi
if $run_migration; then
log "Running prisma migrate deploy..."
cd "${APP_DIR}"
# Infisical-Wrapper für DATABASE_URL (analog start-staging.sh)
source /etc/environment
if [[ -z "${INFISICAL_CLIENT_ID:-}" || -z "${INFISICAL_CLIENT_SECRET:-}" ]]; then
log_err "INFISICAL_CLIENT_ID / INFISICAL_CLIENT_SECRET fehlt — kann Migration nicht ausführen"
exit 1
fi
INFISICAL_TOKEN=$(infisical login \
--method=universal-auth \
--client-id="${INFISICAL_CLIENT_ID}" \
--client-secret="${INFISICAL_CLIENT_SECRET}" \
--silent --plain 2>/dev/null)
if [[ -z "$INFISICAL_TOKEN" ]]; then
log_err "Infisical login fehlgeschlagen — Migration abgebrochen"
exit 1
fi
# DATABASE_URL injecten via infisical run; Aliasing analog start-staging.sh
# (Infisical-Secret heißt evtl. NUXT_DATABASE_URL, prisma erwartet DATABASE_URL).
infisical run \
--projectId="${INFISICAL_PROJECT_ID:-14b11b35-ef59-4b8a-a16b-398f0cc3ad93}" \
--env=staging \
--token="$INFISICAL_TOKEN" \
-- bash -c '
set -e
export DATABASE_URL="${DATABASE_URL:-${NUXT_DATABASE_URL:-}}"
if [[ -z "$DATABASE_URL" ]]; then
echo "[deploy:err] DATABASE_URL nicht in Infisical-staging — Migration abgebrochen" >&2
exit 1
fi
"'"${PNPM_BIN}"'" prisma migrate deploy --schema prisma/schema.prisma
' 2>&1 || {
log_err "Migration-Deploy fehlgeschlagen — pm2-restart ABGEBROCHEN (Daten-Konsistenz-Schutz)"
exit 1
}
log "Migration done"
fi
# 3. Build backend (Nitro standalone) — Prisma generate ist Teil des build-scripts
log "Step 3: pnpm --filter rebreak-backend build..."
cd "${APP_DIR}"
# NODE_OPTIONS: max 1.5 GB für den Build-Prozess (4 GB RAM Server)
NODE_OPTIONS="--max-old-space-size=1536" CI=true "${PNPM_BIN}" --filter rebreak-backend build 2>&1
log "Build done"
# 4. Atomisches Deploy: .output → .output-staging (relativ zu backend/)
log "Step 4: Atomisches Deploy .output → .output-staging..."
cd "${APP_DIR}"
if [ -d ".output" ]; then
rm -rf .output-staging-new
cp -r .output .output-staging-new
rm -rf .output-staging
mv .output-staging-new .output-staging
log ".output-staging aktualisiert"
else
log_err "FEHLER: .output Verzeichnis nicht gefunden nach Build!"
exit 1
fi
# 5. pm2 restart rebreak-staging (--update-env zieht neue Infisical-secrets)
log "Step 5: pm2 restart rebreak-staging..."
"${PM2_BIN}" restart rebreak-staging --update-env 2>/dev/null || \
"${PM2_BIN}" start "${REPO_ROOT}/ecosystem.config.js" --only rebreak-staging
log "rebreak-staging restarted"
# 6. IMAP + DNS Services (optional kein Fehler wenn nicht vorhanden, Mo's Scope)
log "Step 6: Optional services restart..."
"${PM2_BIN}" restart rebreak-imap-staging 2>/dev/null || true
"${PM2_BIN}" restart rebreak-idle-staging 2>/dev/null || true
"${PM2_BIN}" restart dns-rebreak-staging 2>/dev/null || \
"${PM2_BIN}" start "${REPO_ROOT}/ecosystem.config.js" --only dns-rebreak-staging 2>/dev/null || true
"${PM2_BIN}" restart dns-rebreak 2>/dev/null || \
"${PM2_BIN}" start "${REPO_ROOT}/ecosystem.config.js" --only dns-rebreak 2>/dev/null || true
# 7. pm2 save
"${PM2_BIN}" save 2>/dev/null || true
# 8. Last-deployed-SHA persistieren (für Step 2.5 beim nächsten Deploy)
echo "${CUR_SHA}" > "${REPO_ROOT}/.last-deployed-sha"
log "Last-deployed-SHA gespeichert: ${CUR_SHA}"
log "=== Deploy erfolgreich: $(git -C ${REPO_ROOT} rev-parse --short HEAD) ==="