chore(V2): build-push.sh costruisce 1 sola immagine V2.0.0; rimosso deploy-noclone.sh
Lo script ora pubblica un solo tag cerbero-mcp:2.0.0 + :latest + :sha-<short>. deploy-noclone.sh era specifico del workflow V1 multi-image. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+16
-56
@@ -1,35 +1,26 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Cerbero_mcp — build & push image al registry Gitea da macchina locale.
|
# Cerbero MCP — build & push immagine unica V2.0.0 al registry Gitea.
|
||||||
#
|
|
||||||
# Sostituisce il job CI `build-and-push` di .gitea/workflows/ci.yml.
|
|
||||||
# Usalo dopo `git push` (o senza, se vuoi pushare un build "dirty").
|
|
||||||
# Watchtower sul VPS pulla automaticamente entro WATCHTOWER_POLL_INTERVAL.
|
|
||||||
#
|
#
|
||||||
# Pre-requisiti:
|
# Pre-requisiti:
|
||||||
# - docker + buildx
|
# - docker
|
||||||
# - PAT Gitea con scope `write:package` in env $GITEA_PAT
|
# - PAT Gitea con scope `write:package` in env $GITEA_PAT
|
||||||
# - $GITEA_USER (default: adriano)
|
# - $GITEA_USER (default: adriano)
|
||||||
#
|
#
|
||||||
# Uso:
|
# Uso:
|
||||||
# ./scripts/build-push.sh # tutte le image
|
# ./scripts/build-push.sh
|
||||||
# ./scripts/build-push.sh base gateway # solo specifiche
|
# VERSION=2.0.1 ./scripts/build-push.sh
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
REGISTRY="${REGISTRY:-git.tielogic.xyz}"
|
REGISTRY="${REGISTRY:-git.tielogic.xyz}"
|
||||||
IMAGE_PREFIX="${IMAGE_PREFIX:-$REGISTRY/adriano/cerbero-mcp}"
|
IMAGE_PREFIX="${IMAGE_PREFIX:-$REGISTRY/adriano/cerbero-mcp}"
|
||||||
GITEA_USER="${GITEA_USER:-adriano}"
|
GITEA_USER="${GITEA_USER:-adriano}"
|
||||||
|
VERSION="${VERSION:-2.0.0}"
|
||||||
SHA="$(git rev-parse --short HEAD)"
|
SHA="$(git rev-parse --short HEAD)"
|
||||||
|
|
||||||
# Ordine di build: base prima (parent delle mcp-*), poi le altre.
|
|
||||||
ALL_TARGETS=(base gateway mcp-deribit mcp-bybit mcp-hyperliquid mcp-alpaca mcp-macro mcp-sentiment)
|
|
||||||
TARGETS=("${@:-${ALL_TARGETS[@]}}")
|
|
||||||
|
|
||||||
command -v docker >/dev/null || { echo "FATAL: docker non installato"; exit 1; }
|
command -v docker >/dev/null || { echo "FATAL: docker non installato"; exit 1; }
|
||||||
docker buildx version >/dev/null || { echo "FATAL: docker buildx non disponibile"; exit 1; }
|
|
||||||
|
|
||||||
# Login solo se non già autenticato sul registry. Per primo login fai:
|
# Login solo se non già autenticato sul registry.
|
||||||
# echo "<PAT>" | docker login $REGISTRY -u $GITEA_USER --password-stdin
|
|
||||||
if grep -q "\"$REGISTRY\"" ~/.docker/config.json 2>/dev/null; then
|
if grep -q "\"$REGISTRY\"" ~/.docker/config.json 2>/dev/null; then
|
||||||
echo "=== docker già loggato su $REGISTRY (skip login) ==="
|
echo "=== docker già loggato su $REGISTRY (skip login) ==="
|
||||||
elif [ -n "${GITEA_PAT:-}" ]; then
|
elif [ -n "${GITEA_PAT:-}" ]; then
|
||||||
@@ -41,50 +32,19 @@ else
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
build_one() {
|
TAG_VERSION="$IMAGE_PREFIX:$VERSION"
|
||||||
local name="$1"
|
TAG_LATEST="$IMAGE_PREFIX:latest"
|
||||||
local context file
|
TAG_SHA="$IMAGE_PREFIX:sha-$SHA"
|
||||||
case "$name" in
|
|
||||||
base)
|
|
||||||
context="."; file="docker/base.Dockerfile" ;;
|
|
||||||
gateway)
|
|
||||||
context="./gateway"; file="gateway/Dockerfile" ;;
|
|
||||||
mcp-*)
|
|
||||||
context="."; file="docker/${name}.Dockerfile" ;;
|
|
||||||
*)
|
|
||||||
echo "FATAL: target sconosciuto '$name'"; exit 1 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ ! -f "$file" ]; then
|
echo "=== build cerbero-mcp:$VERSION ==="
|
||||||
echo "FATAL: Dockerfile non trovato: $file"; exit 1
|
docker build -t "$TAG_VERSION" -t "$TAG_LATEST" -t "$TAG_SHA" .
|
||||||
fi
|
|
||||||
|
|
||||||
local tag_latest="$IMAGE_PREFIX/$name:latest"
|
echo "=== push ==="
|
||||||
local tag_sha="$IMAGE_PREFIX/$name:sha-$SHA"
|
for tag in "$TAG_VERSION" "$TAG_LATEST" "$TAG_SHA"; do
|
||||||
|
docker push "$tag"
|
||||||
echo "=== [$name] build & push ==="
|
echo " pushed: $tag"
|
||||||
local args=(buildx build --push
|
|
||||||
-f "$file"
|
|
||||||
-t "$tag_latest"
|
|
||||||
-t "$tag_sha"
|
|
||||||
)
|
|
||||||
if [[ "$name" == mcp-* ]]; then
|
|
||||||
args+=(--build-arg "BASE_IMAGE=$IMAGE_PREFIX/base"
|
|
||||||
--build-arg "BASE_TAG=latest")
|
|
||||||
fi
|
|
||||||
args+=("$context")
|
|
||||||
|
|
||||||
docker "${args[@]}"
|
|
||||||
echo " pushed: $tag_latest"
|
|
||||||
echo " pushed: $tag_sha"
|
|
||||||
}
|
|
||||||
|
|
||||||
for t in "${TARGETS[@]}"; do
|
|
||||||
build_one "$t"
|
|
||||||
done
|
done
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "=== Tutto pushato (commit $SHA) ==="
|
echo "=== Done (commit $SHA, version $VERSION) ==="
|
||||||
echo "VPS Watchtower farà pull entro WATCHTOWER_POLL_INTERVAL (default 5min)."
|
echo "VPS Watchtower farà pull entro WATCHTOWER_POLL_INTERVAL (default 5min)."
|
||||||
echo "Per forzare subito:"
|
|
||||||
echo " ssh <vps> 'cd /docker/cerbero_mcp && docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d'"
|
|
||||||
|
|||||||
@@ -1,202 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Cerbero_mcp — deploy script per VPS produzione.
|
|
||||||
#
|
|
||||||
# Sul VPS NON viene clonato il repo: lo script scarica solo i file
|
|
||||||
# strettamente necessari al runtime (compose, Caddyfile, public assets)
|
|
||||||
# via raw HTTP da Gitea. Le image vengono pullate pre-built dal registry
|
|
||||||
# Gitea (buildate dal laptop dev con scripts/build-push.sh).
|
|
||||||
#
|
|
||||||
# Pre-requisiti sul VPS (NON gestiti da questo script):
|
|
||||||
# 1. Docker Engine ≥ 24 + plugin docker compose installati.
|
|
||||||
# 2. DNS A record `cerbero-mcp.tielogic.xyz` → IP del VPS (warn-only).
|
|
||||||
# 3. Porte 80 e 443 aperte sul firewall (per ACME + traffico HTTPS).
|
|
||||||
# 4. PAT Gitea con scope `read:package`, salvato in env `$GITEA_PAT`.
|
|
||||||
# 5. Username Gitea in env `$GITEA_USER` (default: adriano).
|
|
||||||
# 6. Secret JSON exchange + token bearer disponibili in $SECRETS_SRC
|
|
||||||
# (default: $DEPLOY_DIR/secrets/), che lo script copierà in
|
|
||||||
# $DEPLOY_DIR/secrets/ con permessi 600 (ignorato se SECRETS_SRC == DEPLOY_DIR/secrets).
|
|
||||||
#
|
|
||||||
# Idempotente: rieseguibile per aggiornamenti (riscarica i file di config
|
|
||||||
# dal branch corrente, NON tocca .env esistente).
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
DEPLOY_DIR="${DEPLOY_DIR:-/docker/cerbero_mcp}"
|
|
||||||
SECRETS_SRC="${SECRETS_SRC:-$DEPLOY_DIR/secrets}"
|
|
||||||
GITEA_USER="${GITEA_USER:-adriano}"
|
|
||||||
GITEA_RAW_BASE="${GITEA_RAW_BASE:-https://git.tielogic.xyz/Adriano/Cerbero-mcp/raw/branch/main}"
|
|
||||||
REGISTRY="${REGISTRY:-git.tielogic.xyz}"
|
|
||||||
DOMAIN="${DOMAIN:-cerbero-mcp.tielogic.xyz}"
|
|
||||||
AUDIT_LOG_DIR="${AUDIT_LOG_DIR:-/var/log/cerbero-mcp}"
|
|
||||||
|
|
||||||
echo "=== Cerbero_mcp deploy (no-clone) → $DEPLOY_DIR (domain $DOMAIN) ==="
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────────────────────────
|
|
||||||
# 1. Verifica pre-requisiti
|
|
||||||
# ──────────────────────────────────────────────────────────────
|
|
||||||
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 || { echo "FATAL: docker compose plugin assente"; exit 1; }
|
|
||||||
|
|
||||||
if [ -z "${GITEA_PAT:-}" ]; then
|
|
||||||
echo "FATAL: env GITEA_PAT non settata. Export del PAT con scope read:package prima."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check DNS resolution (warning only, non blocca)
|
|
||||||
ip_resolved=$(getent hosts "$DOMAIN" | awk '{print $1}' | head -1 || true)
|
|
||||||
if [ -z "$ip_resolved" ]; then
|
|
||||||
echo "WARN: $DOMAIN non risolve via DNS — TLS Let's Encrypt fallirà finché DNS non propaga."
|
|
||||||
else
|
|
||||||
echo "DNS $DOMAIN → $ip_resolved"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────────────────────────
|
|
||||||
# 2. Login al container registry
|
|
||||||
# ──────────────────────────────────────────────────────────────
|
|
||||||
echo "=== docker login $REGISTRY ==="
|
|
||||||
echo "$GITEA_PAT" | docker login "$REGISTRY" -u "$GITEA_USER" --password-stdin
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────────────────────────
|
|
||||||
# 3. Setup dir + scarica i file di config dal repo (no clone)
|
|
||||||
# ──────────────────────────────────────────────────────────────
|
|
||||||
sudo mkdir -p "$DEPLOY_DIR"
|
|
||||||
sudo chown "$USER:$USER" "$DEPLOY_DIR"
|
|
||||||
mkdir -p "$DEPLOY_DIR/secrets" "$DEPLOY_DIR/gateway/public"
|
|
||||||
|
|
||||||
# File di config necessari al runtime. Scaricati come raw da Gitea.
|
|
||||||
# Idempotente: ricarica sempre la versione di main.
|
|
||||||
download() {
|
|
||||||
local rel="$1"
|
|
||||||
local dst="$DEPLOY_DIR/$rel"
|
|
||||||
echo " fetch: $rel"
|
|
||||||
curl -fsSL -o "$dst" "$GITEA_RAW_BASE/$rel" \
|
|
||||||
|| { echo "FATAL: download $rel fallito"; exit 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "=== Download config da $GITEA_RAW_BASE ==="
|
|
||||||
download docker-compose.prod.yml
|
|
||||||
download docker-compose.traefik.yml
|
|
||||||
download gateway/Caddyfile
|
|
||||||
download gateway/public/index.html
|
|
||||||
download gateway/public/status.js
|
|
||||||
download gateway/public/style.css
|
|
||||||
|
|
||||||
cd "$DEPLOY_DIR"
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────────────────────────
|
|
||||||
# 4. Copia secrets con permessi 600
|
|
||||||
# ──────────────────────────────────────────────────────────────
|
|
||||||
if [ "$(realpath "$SECRETS_SRC")" != "$(realpath "$DEPLOY_DIR/secrets")" ]; then
|
|
||||||
if [ ! -d "$SECRETS_SRC" ]; then
|
|
||||||
echo "FATAL: secrets src dir $SECRETS_SRC non esiste."
|
|
||||||
echo " Atteso contenere: deribit.json bybit.json hyperliquid.json alpaca.json"
|
|
||||||
echo " macro.json sentiment.json core.token observer.token"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "=== Copia secrets da $SECRETS_SRC ==="
|
|
||||||
for f in deribit.json bybit.json hyperliquid.json alpaca.json macro.json sentiment.json core.token observer.token; do
|
|
||||||
if [ -f "$SECRETS_SRC/$f" ]; then
|
|
||||||
cp "$SECRETS_SRC/$f" "secrets/$f"
|
|
||||||
chmod 600 "secrets/$f"
|
|
||||||
echo " ok: secrets/$f"
|
|
||||||
else
|
|
||||||
echo " WARN: $SECRETS_SRC/$f assente — il servizio relativo fallirà al boot."
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
else
|
|
||||||
echo "=== Secrets già in $DEPLOY_DIR/secrets — solo chmod 600 ==="
|
|
||||||
for f in deribit.json bybit.json hyperliquid.json alpaca.json macro.json sentiment.json core.token observer.token; do
|
|
||||||
[ -f "secrets/$f" ] && chmod 600 "secrets/$f" && echo " ok: secrets/$f" \
|
|
||||||
|| echo " WARN: secrets/$f assente — il servizio relativo fallirà al boot."
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────────────────────────
|
|
||||||
# 5. Crea/aggiorna .env (preserva esistente)
|
|
||||||
# ──────────────────────────────────────────────────────────────
|
|
||||||
if [ ! -f .env ]; then
|
|
||||||
echo "=== Creazione .env iniziale (testnet di default) ==="
|
|
||||||
cat > .env <<EOF
|
|
||||||
# Cerbero_mcp deploy config — modifica per passare a mainnet
|
|
||||||
ACME_EMAIL=adrianodalpastro@tielogic.com
|
|
||||||
GATEWAY_HTTP_PORT=80
|
|
||||||
GATEWAY_HTTPS_PORT=443
|
|
||||||
WRITE_ALLOWLIST="127.0.0.1/32 ::1/128 172.16.0.0/12"
|
|
||||||
|
|
||||||
IMAGE_TAG=latest
|
|
||||||
IMAGE_PREFIX=git.tielogic.xyz/adriano/cerbero-mcp
|
|
||||||
|
|
||||||
# Environment exchange (true=testnet, false=mainnet).
|
|
||||||
# IMPORTANTE: per mainnet aggiungi anche "environment":"mainnet" al secret JSON
|
|
||||||
# corrispondente, altrimenti il boot abortisce per safety (vedi consistency_check).
|
|
||||||
DERIBIT_TESTNET=true
|
|
||||||
BYBIT_TESTNET=true
|
|
||||||
HYPERLIQUID_TESTNET=true
|
|
||||||
ALPACA_PAPER=true
|
|
||||||
|
|
||||||
# Permette mainnet senza creds["environment"]="mainnet" esplicito (sconsigliato).
|
|
||||||
STRICT_MAINNET=true
|
|
||||||
|
|
||||||
# Audit log persistente per write endpoint (place_order, cancel, ecc.).
|
|
||||||
AUDIT_LOG_DIR=$AUDIT_LOG_DIR
|
|
||||||
|
|
||||||
# Watchtower polling auto-update (sec).
|
|
||||||
WATCHTOWER_POLL_INTERVAL=300
|
|
||||||
EOF
|
|
||||||
echo " $DEPLOY_DIR/.env creato. Rivedi prima del primo up."
|
|
||||||
else
|
|
||||||
echo "=== .env preesistente — non sovrascritto ==="
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────────────────────────
|
|
||||||
# 6. Audit log dir host (volume bind)
|
|
||||||
# ──────────────────────────────────────────────────────────────
|
|
||||||
sudo mkdir -p "$AUDIT_LOG_DIR"
|
|
||||||
sudo chown 1000:1000 "$AUDIT_LOG_DIR"
|
|
||||||
echo "Audit log dir: $AUDIT_LOG_DIR (chown 1000:1000)"
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────────────────────────
|
|
||||||
# 7. Pull image + up
|
|
||||||
# ──────────────────────────────────────────────────────────────
|
|
||||||
COMPOSE_FILES=("-f" "docker-compose.prod.yml")
|
|
||||||
if [ "${BEHIND_TRAEFIK:-false}" = "true" ]; then
|
|
||||||
echo "=== Modalità behind-traefik attiva (network ${TRAEFIK_NETWORK:-gitea_traefik-public}) ==="
|
|
||||||
COMPOSE_FILES+=("-f" "docker-compose.traefik.yml")
|
|
||||||
fi
|
|
||||||
# Override locale specifico macchina (es. fix DOCKER_API_VERSION watchtower).
|
|
||||||
# Non versionato (in .gitignore), creato a mano sul VPS se serve.
|
|
||||||
if [ -f "docker-compose.local.yml" ]; then
|
|
||||||
echo "=== Override locale rilevato: docker-compose.local.yml ==="
|
|
||||||
COMPOSE_FILES+=("-f" "docker-compose.local.yml")
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "=== docker compose pull + up ==="
|
|
||||||
docker compose "${COMPOSE_FILES[@]}" --env-file .env pull
|
|
||||||
docker compose "${COMPOSE_FILES[@]}" --env-file .env up -d
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────────────────────────
|
|
||||||
# 8. Verifica stato
|
|
||||||
# ──────────────────────────────────────────────────────────────
|
|
||||||
sleep 5
|
|
||||||
echo "=== Stato container ==="
|
|
||||||
docker compose "${COMPOSE_FILES[@]}" --env-file .env ps
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "=== Smoke test (health check via gateway pubblico) ==="
|
|
||||||
sleep 10
|
|
||||||
if curl -sf -o /dev/null -m 10 "https://$DOMAIN/mcp-macro/health"; then
|
|
||||||
echo " OK: https://$DOMAIN/mcp-macro/health → 200"
|
|
||||||
else
|
|
||||||
echo " WARN: https://$DOMAIN/mcp-macro/health non risponde (DNS o cert non ancora pronti?)"
|
|
||||||
echo " Riprova fra 30s o controlla: docker compose -f docker-compose.prod.yml logs gateway"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "=== Deploy completato ==="
|
|
||||||
echo "Comandi utili (compose files: ${COMPOSE_FILES[*]}):"
|
|
||||||
echo " Logs: docker compose ${COMPOSE_FILES[*]} --env-file .env logs -f <service>"
|
|
||||||
echo " Audit: tail -f $AUDIT_LOG_DIR/*.audit.jsonl"
|
|
||||||
echo " Restart: docker compose ${COMPOSE_FILES[*]} --env-file .env restart <service>"
|
|
||||||
echo " Stop: docker compose ${COMPOSE_FILES[*]} --env-file .env down"
|
|
||||||
echo " Update: ri-esegui questo script (riscarica config + pull image)"
|
|
||||||
Reference in New Issue
Block a user