#!/usr/bin/env bash # deploy-vps.sh — deploy Cerbero MCP V2 sul VPS senza passare per registry. # # Workflow: # 1. git fetch + reset al ramo target # 2. docker compose build (rebuild immagine se SHA è cambiata) # 3. docker compose down (graceful, max 15s) # 4. docker compose up -d # 5. attesa healthcheck su /health # 6. rollback automatico al SHA precedente se health fallisce # # Eseguito ON THE VPS, dentro la directory del repo (es. /opt/cerbero-mcp). # # Uso (sul VPS): # cd /opt/cerbero-mcp # bash scripts/deploy-vps.sh # # Uso (da macchina dev, via SSH): # ssh user@vps 'cd /opt/cerbero-mcp && bash scripts/deploy-vps.sh' # # Variabili env (opzionali): # BRANCH ramo git da deployare (default: V2.0.0) # SERVICE nome servizio docker compose (default: cerbero-mcp) # PORT porta /health da pingare (default: dal .env, fallback 9000) # HEALTH_TIMEOUT_SECONDS attesa max health (default: 30) # HEALTH_INTERVAL secondi tra retry health (default: 2) # FORCE se "1", rebuild + restart anche se SHA invariata # SKIP_ROLLBACK se "1", non fare rollback su health fail (per debug) set -euo pipefail # ─── Config ────────────────────────────────────────────────────────────── BRANCH="${BRANCH:-V2.0.0}" SERVICE="${SERVICE:-cerbero-mcp}" HEALTH_TIMEOUT_SECONDS="${HEALTH_TIMEOUT_SECONDS:-30}" HEALTH_INTERVAL="${HEALTH_INTERVAL:-2}" # Risolvi PORT da .env se non passata if [[ -z "${PORT:-}" ]]; then if [[ -f .env ]] && grep -q '^PORT=' .env; then PORT="$(grep '^PORT=' .env | head -1 | cut -d= -f2 | tr -d '[:space:]"')" fi fi PORT="${PORT:-9000}" HEALTH_URL="http://localhost:${PORT}/health" # ─── Pre-check ─────────────────────────────────────────────────────────── command -v git >/dev/null || { echo "FATAL: git non installato"; exit 1; } command -v docker >/dev/null || { echo "FATAL: docker non installato"; exit 1; } command -v curl >/dev/null || { echo "FATAL: curl non installato"; exit 1; } docker compose version >/dev/null 2>&1 || { echo "FATAL: docker compose non disponibile"; exit 1; } if [[ ! -f .env ]]; then echo "FATAL: .env non trovato in $(pwd)." echo " Copia .env.example → .env e compila i valori prima del primo deploy." exit 1 fi if [[ ! -f docker-compose.yml ]]; then echo "FATAL: docker-compose.yml non trovato in $(pwd)." exit 1 fi # Verifica working tree pulito if [[ -n "$(git status --porcelain)" ]]; then echo "FATAL: working tree non pulito. Modifiche locali non gestite:" git status --short echo " Risolvi prima di deployare (es. git stash o git reset)." exit 1 fi # ─── Stato corrente ────────────────────────────────────────────────────── CURRENT_SHA="$(git rev-parse --short HEAD)" echo "==> SHA attuale (rollback target): $CURRENT_SHA" echo "==> branch: $BRANCH" echo "==> port: $PORT" # ─── Fetch + reset ─────────────────────────────────────────────────────── echo "==> git fetch + reset --hard origin/${BRANCH}" git fetch --prune origin git reset --hard "origin/${BRANCH}" NEW_SHA="$(git rev-parse --short HEAD)" echo "==> SHA nuovo: $NEW_SHA" if [[ "$CURRENT_SHA" == "$NEW_SHA" ]] && [[ "${FORCE:-0}" != "1" ]]; then echo "==> Già aggiornato a $NEW_SHA. Nessun deploy necessario." echo " (esporta FORCE=1 per riavviare comunque)" exit 0 fi if [[ "$CURRENT_SHA" == "$NEW_SHA" ]]; then echo "==> FORCE=1 → rebuild e restart anche se SHA invariata" fi # ─── Funzione di rollback ──────────────────────────────────────────────── rollback() { if [[ "${SKIP_ROLLBACK:-0}" == "1" ]]; then echo "==> SKIP_ROLLBACK=1 → niente rollback automatico" return fi if [[ "$CURRENT_SHA" == "$NEW_SHA" ]]; then echo "==> SHA invariata, niente da rollbackare" return fi echo "==> ROLLBACK a $CURRENT_SHA" git reset --hard "$CURRENT_SHA" docker compose build "$SERVICE" docker compose up -d --force-recreate "$SERVICE" echo "==> rollback eseguito. Verifica manualmente lo stato." } # ─── Build ─────────────────────────────────────────────────────────────── echo "==> docker compose build $SERVICE" docker compose build "$SERVICE" # ─── Down + up ─────────────────────────────────────────────────────────── echo "==> docker compose down --timeout 15" docker compose down --timeout 15 echo "==> docker compose up -d" docker compose up -d # ─── Health check ──────────────────────────────────────────────────────── echo "==> attendo /health (timeout ${HEALTH_TIMEOUT_SECONDS}s, retry ogni ${HEALTH_INTERVAL}s)" deadline=$(( $(date +%s) + HEALTH_TIMEOUT_SECONDS )) while [[ $(date +%s) -lt $deadline ]]; do if curl -fsS "$HEALTH_URL" >/dev/null 2>&1; then echo echo "==> health OK" curl -s "$HEALTH_URL" echo echo echo "==> deploy DONE (SHA $CURRENT_SHA → $NEW_SHA, branch $BRANCH)" exit 0 fi printf "." sleep "$HEALTH_INTERVAL" done echo echo "==> FAIL: /health non risponde dopo ${HEALTH_TIMEOUT_SECONDS}s" echo "==> log container (ultime 40 righe):" docker compose logs --tail 40 "$SERVICE" || true rollback exit 1