#!/usr/bin/env bash # Cerbero_mcp — deploy script (no-clone) per VPS produzione. # # Variante di deploy.sh che NON clona il repo; scarica solo i file # strettamente necessari al runtime (compose, Caddyfile, public assets) # via raw HTTP da Gitea. Image pre-built dal registry come deploy.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 <" echo " Audit: tail -f $AUDIT_LOG_DIR/*.audit.jsonl" echo " Restart: docker compose ${COMPOSE_FILES[*]} --env-file .env restart " echo " Stop: docker compose ${COMPOSE_FILES[*]} --env-file .env down" echo " Update: ri-esegui questo script (riscarica config + pull image)"