From 51081f4e18108e5484925451265a194bad25c372 Mon Sep 17 00:00:00 2001 From: AdrianoDev Date: Fri, 1 May 2026 09:05:26 +0200 Subject: [PATCH] feat(V2): deploy-vps.sh per deploy via clone (no registry) Il deploy ora avviene clonando il repo direttamente sul VPS, costruendo l'immagine in loco e riavviando il container. Sostituisce il workflow build & push verso registry + Watchtower. Lo script automatizza: - git fetch + reset --hard origin/ - docker compose build - restart graceful (down 15s + up -d) - attesa healthcheck con timeout configurabile - rollback automatico al SHA precedente se /health fallisce Variabili: BRANCH, PORT, HEALTH_TIMEOUT_SECONDS, FORCE, SKIP_ROLLBACK. Rimosso scripts/build-push.sh (workflow registry abbandonato). README aggiornato con la nuova procedura. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 50 ++++++++++++-- scripts/build-push.sh | 50 -------------- scripts/deploy-vps.sh | 148 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 191 insertions(+), 57 deletions(-) delete mode 100755 scripts/build-push.sh create mode 100755 scripts/deploy-vps.sh diff --git a/README.md b/README.md index 7052087..5aaec66 100644 --- a/README.md +++ b/README.md @@ -219,18 +219,54 @@ labels: ## Build & deploy pipeline -Build dell'immagine eseguita sulla macchina di sviluppo: +Il deploy su VPS avviene **per clone diretto del repo**, senza passare per +un container registry. Lo script `scripts/deploy-vps.sh` automatizza +l'intero flusso: pull del ramo target, rebuild dell'immagine sulla +macchina VPS, restart del servizio, healthcheck e rollback automatico in +caso di fallimento. + +### Setup iniziale sul VPS (una sola volta) ```bash -export GITEA_PAT='' -./scripts/build-push.sh +# Sul VPS: +sudo mkdir -p /opt/cerbero-mcp +sudo chown -R "$USER":"$USER" /opt/cerbero-mcp +cd /opt/cerbero-mcp +git clone . +cp .env.example .env +# editare .env con i token e le credenziali reali ``` -Lo script tagga `:2.0.0`, `:latest` e `:sha-` per rollback puntuali -e pubblica al registry Gitea. Sul VPS Watchtower polla `:latest` e -aggiorna il container automaticamente. +### Deploy ricorrente -Smoke test post-deploy: +Da qualunque macchina con accesso SSH al VPS: + +```bash +ssh user@vps 'cd /opt/cerbero-mcp && bash scripts/deploy-vps.sh' +``` + +Oppure direttamente dal VPS: + +```bash +cd /opt/cerbero-mcp +bash scripts/deploy-vps.sh +``` + +Lo script: +1. verifica che il working tree sia pulito e che `.env` sia presente; +2. esegue `git fetch + reset --hard origin/main`; +3. se la SHA non è cambiata, esce senza fare nulla (override con + `FORCE=1`); +4. ricostruisce l'immagine Docker (`docker compose build`); +5. restart graceful del container (`docker compose down --timeout 15` + seguito da `docker compose up -d`); +6. attende `/health` (timeout 30 s di default); +7. se l'health fallisce, esegue rollback automatico al SHA precedente. + +Variabili d'ambiente accettate: `BRANCH` (default `main`), `PORT` (default +letto da `.env`), `HEALTH_TIMEOUT_SECONDS`, `FORCE`, `SKIP_ROLLBACK`. + +### Smoke test post-deploy ```bash PORT=9000 TESTNET_TOKEN="$TESTNET_TOKEN" bash tests/smoke/run.sh diff --git a/scripts/build-push.sh b/scripts/build-push.sh deleted file mode 100755 index 2d44470..0000000 --- a/scripts/build-push.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env bash -# Cerbero MCP — build & push immagine unica V2.0.0 al registry Gitea. -# -# Pre-requisiti: -# - docker -# - PAT Gitea con scope `write:package` in env $GITEA_PAT -# - $GITEA_USER (default: adriano) -# -# Uso: -# ./scripts/build-push.sh -# VERSION=2.0.1 ./scripts/build-push.sh - -set -euo pipefail - -REGISTRY="${REGISTRY:-git.tielogic.xyz}" -IMAGE_PREFIX="${IMAGE_PREFIX:-$REGISTRY/adriano/cerbero-mcp}" -GITEA_USER="${GITEA_USER:-adriano}" -VERSION="${VERSION:-2.0.0}" -SHA="$(git rev-parse --short HEAD)" - -command -v docker >/dev/null || { echo "FATAL: docker non installato"; exit 1; } - -# Login solo se non già autenticato sul registry. -if grep -q "\"$REGISTRY\"" ~/.docker/config.json 2>/dev/null; then - echo "=== docker già loggato su $REGISTRY (skip login) ===" -elif [ -n "${GITEA_PAT:-}" ]; then - echo "=== docker login $REGISTRY ===" - echo "$GITEA_PAT" | docker login "$REGISTRY" -u "$GITEA_USER" --password-stdin -else - echo "FATAL: non autenticato su $REGISTRY e GITEA_PAT non settata." - echo " Esegui una volta: docker login $REGISTRY -u $GITEA_USER" - exit 1 -fi - -TAG_VERSION="$IMAGE_PREFIX:$VERSION" -TAG_LATEST="$IMAGE_PREFIX:latest" -TAG_SHA="$IMAGE_PREFIX:sha-$SHA" - -echo "=== build cerbero-mcp:$VERSION ===" -docker build -t "$TAG_VERSION" -t "$TAG_LATEST" -t "$TAG_SHA" . - -echo "=== push ===" -for tag in "$TAG_VERSION" "$TAG_LATEST" "$TAG_SHA"; do - docker push "$tag" - echo " pushed: $tag" -done - -echo -echo "=== Done (commit $SHA, version $VERSION) ===" -echo "VPS Watchtower farà pull entro WATCHTOWER_POLL_INTERVAL (default 5min)." diff --git a/scripts/deploy-vps.sh b/scripts/deploy-vps.sh new file mode 100755 index 0000000..5c35104 --- /dev/null +++ b/scripts/deploy-vps.sh @@ -0,0 +1,148 @@ +#!/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: main) +# 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:-main}" +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