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/<branch> - 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) <noreply@anthropic.com>
This commit is contained in:
@@ -219,18 +219,54 @@ labels:
|
|||||||
|
|
||||||
## Build & deploy pipeline
|
## 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
|
```bash
|
||||||
export GITEA_PAT='<PAT con scope write:package>'
|
# Sul VPS:
|
||||||
./scripts/build-push.sh
|
sudo mkdir -p /opt/cerbero-mcp
|
||||||
|
sudo chown -R "$USER":"$USER" /opt/cerbero-mcp
|
||||||
|
cd /opt/cerbero-mcp
|
||||||
|
git clone <repo-url> .
|
||||||
|
cp .env.example .env
|
||||||
|
# editare .env con i token e le credenziali reali
|
||||||
```
|
```
|
||||||
|
|
||||||
Lo script tagga `:2.0.0`, `:latest` e `:sha-<short>` per rollback puntuali
|
### Deploy ricorrente
|
||||||
e pubblica al registry Gitea. Sul VPS Watchtower polla `:latest` e
|
|
||||||
aggiorna il container automaticamente.
|
|
||||||
|
|
||||||
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
|
```bash
|
||||||
PORT=9000 TESTNET_TOKEN="$TESTNET_TOKEN" bash tests/smoke/run.sh
|
PORT=9000 TESTNET_TOKEN="$TESTNET_TOKEN" bash tests/smoke/run.sh
|
||||||
|
|||||||
@@ -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)."
|
|
||||||
Executable
+148
@@ -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
|
||||||
Reference in New Issue
Block a user