rebreak-monorepo/scripts/deploy-from-artifact.sh
chahinebrini 87438ede8e feat(deploy): GitHub-Actions Build+Deploy-Pipeline für rebreak-staging
CX23 (4GB RAM) OOM'd am 2026-05-06 während webhook-getriggertem `pnpm build`
(Heap-Limit 1.5GB überschritten). Build raus aus Server, GitHub-Runner (7GB RAM)
übernimmt — Server deployed nur noch Artifact via scp + atomic-mv + pm2 restart.

- .github/workflows/deploy-staging.yml: 2-Job (build + deploy via SSH-Artifact-Push)
- scripts/deploy-from-artifact.sh: Server-Script mit Migration-Detection + atomic-mv
- ops/GITHUB_ACTIONS_PIPELINE.md: Architektur-Doku + Cheatsheet

Coexistence: alter rebreak-webhook bleibt als Failsafe, wird nach 5+ erfolgreichen
GA-Runs deaktiviert. Erster Run: Webhook temporär gestoppt für sauberen Test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:18:49 +02:00

144 lines
5.2 KiB
Bash
Executable File

#!/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"
# 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
"${PM2_BIN}" restart rebreak-idle-staging 2>/dev/null || true
"${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) ==="