#!/bin/bash # deploy-from-artifact.sh -- Server-side Deploy nach GitHub-Actions-Artifact-Upload. # # Wird via SSH von .github/workflows/deploy-staging.yml aufgerufen. # Erwartet: /srv/rebreak/backend/.output-incoming.tar.gz (vom GA-Runner via scp gepusht). # # Diff zu scripts/deploy.sh: # - KEIN pnpm build hier (das macht der GA-Runner mit 7 GB RAM) # - KEIN pnpm install --frozen-lockfile fuer Build-Deps -- nur Runtime-Deps via prod-flag # - Migration-Detection bleibt (Pattern aus deploy.sh) # - Atomic .output-staging-Replacement bleibt # # Failure-Mode: Bei Migration-Fehler kein pm2-restart (Daten-Konsistenz-Schutz). set -euo pipefail REPO_ROOT="/srv/rebreak" APP_DIR="${REPO_ROOT}/backend" ARTIFACT="${APP_DIR}/.output-incoming.tar.gz" PM2_BIN="/root/.nvm/versions/node/v24.11.1/bin/pm2" PNPM_BIN="/root/.nvm/versions/node/v24.11.1/bin/pnpm" log() { echo "[deploy-artifact] $(date '+%H:%M:%S') $*"; } log_err() { echo "[deploy-artifact:err] $(date '+%H:%M:%S') $*" >&2; } log "=== Rebreak Deploy-from-Artifact gestartet ===" export PATH="/root/.nvm/versions/node/v24.11.1/bin:$PATH" # 0. Sanity-Check Artifact [[ -f "$ARTIFACT" ]] || { log_err "Artifact $ARTIFACT fehlt -- abort"; exit 1; } # 1. Git pull (fuer scripts/-Updates + prisma/migrations + .last-deployed-sha) log "Step 1: git pull..." cd "${REPO_ROOT}" git fetch origin main git reset --hard origin/main log "Git updated to $(git rev-parse --short HEAD)" # 2. Migration-Detection (1:1 aus scripts/deploy.sh uebernommen) log "Step 2: 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 ausfuehren" 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}" source /etc/environment if [[ -z "${INFISICAL_CLIENT_ID:-}" || -z "${INFISICAL_CLIENT_SECRET:-}" ]]; then log_err "INFISICAL_CLIENT_ID / INFISICAL_CLIENT_SECRET fehlt -- Migration abgebrochen" 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) [[ -z "$INFISICAL_TOKEN" ]] && { log_err "Infisical login fehlgeschlagen"; exit 1; } 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-artifact:err] DATABASE_URL nicht in Infisical-staging" >&2 exit 1 fi "'"${PNPM_BIN}"'" prisma migrate deploy --schema prisma/schema.prisma ' 2>&1 || { log_err "Migration-Deploy fehlgeschlagen -- pm2-restart ABGEBROCHEN" exit 1 } log "Migration done" fi # 3. Runtime-Deps installieren (nur falls package.json/lockfile changed) # Prisma-Client ist schon im Artifact baked-in via `prisma generate` auf dem Runner, # aber Runtime-Module (z.B. @prisma/client native binaries) muessen lokal sein. log "Step 3: pnpm install (runtime-deps)..." 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" # 3b. imap-idle Runtime-Deps installieren (imapflow + pg, standalone package.json) IDLE_DIR="${APP_DIR}/imap-idle" if [[ -d "$IDLE_DIR" && -f "$IDLE_DIR/package.json" ]]; then log "Step 3b: npm install (imap-idle runtime-deps)..." cd "$IDLE_DIR" npm install --production --prefer-offline 2>&1 # scp preserviert Permissions nicht immer -- explizit setzen [[ -f "$IDLE_DIR/start-idle-staging.sh" ]] && chmod +x "$IDLE_DIR/start-idle-staging.sh" log "imap-idle npm install done" else log "Step 3b: imap-idle-Verzeichnis nicht gefunden -- skip (wird via GH-Actions deployed)" fi # 4. Artifact extrahieren -- atomisches mv (gleicher Pattern wie deploy.sh) log "Step 4: Artifact extrahieren..." cd "${APP_DIR}" rm -rf .output-staging-new mkdir -p .output-staging-new tar xzf "$ARTIFACT" -C .output-staging-new # Sanity-Check: server/index.mjs muss drin sein [[ -f .output-staging-new/server/index.mjs ]] || { log_err "Ungueltiges Artifact -- .output-staging-new/server/index.mjs fehlt" rm -rf .output-staging-new exit 1 } rm -rf .output-staging mv .output-staging-new .output-staging rm -f "$ARTIFACT" log ".output-staging aktualisiert" # 5. pm2 restart (--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. Optional services (best-effort, Mo's Scope) log "Step 6: Optional services restart..." "${PM2_BIN}" restart rebreak-imap-staging 2>/dev/null || true # rebreak-idle-staging: startOrReload (erster Deploy hat keinen laufenden Prozess) "${PM2_BIN}" restart rebreak-idle-staging --update-env 2>/dev/null || \ "${PM2_BIN}" start "${REPO_ROOT}/ecosystem.config.js" --only rebreak-idle-staging "${PM2_BIN}" restart dns-rebreak-staging 2>/dev/null || true "${PM2_BIN}" restart dns-rebreak 2>/dev/null || true # 7. pm2 save "${PM2_BIN}" save 2>/dev/null || true # 8. Last-deployed-SHA persistieren 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) ==="