#2 Env switch safety: - mcp_common/environment.py: nuova consistency_check() che previene switch accidentali a mainnet. Solleva EnvironmentMismatchError se resolved=mainnet senza creds["environment"]="mainnet" esplicito, o se declared/resolved mismatch. Override via STRICT_MAINNET=false. - Wirato in app_factory.run_exchange_main al boot. - 6 nuovi test consistency. #3 Audit log persistence: - mcp_common/audit.py: TimedRotatingFileHandler aggiuntivo se env AUDIT_LOG_FILE settato. Rotation midnight UTC, retention 30gg default (AUDIT_LOG_BACKUP_DAYS). Format JSONL con SecretsFilter. - docker-compose.prod.yml: bind mount /var/log/cerbero-mcp + env AUDIT_LOG_FILE per i 4 servizi exchange (write endpoints). - 2 nuovi test file sink. #1 Deploy script: - scripts/deploy.sh: idempotente, fa docker login + clone/pull repo + copia secrets chmod 600 + crea .env + setup audit dir + pull image + up + smoke test pubblico HTTPS. - DEPLOYMENT.md aggiornato: sezioni 2 (script), 3 (safety mainnet), 4 (audit log query), renumber sezioni successive. Test: 488/488 verdi. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
13 KiB
Deployment Cerbero_mcp
Guida operativa per il deploy della suite MCP su un VPS pubblico. L'architettura è: Gitea ospita codice + container registry; il VPS produzione non builda nulla, ma fa pull dei container già pronti dalla registry e usa Watchtower per il rollover automatico delle versioni.
┌─────────────────────────┐ ┌──────────────────────────────────┐
│ Gitea git.tielogic.xyz │ │ VPS produzione │
│ │ │ cerbero-mcp.tielogic.xyz │
│ ┌──────────────────┐ │ push │ │
│ │ Cerbero-mcp repo │───┼─CI/CD──▶│ ┌────────────────────────────┐ │
│ └──────────────────┘ │ image │ │ docker compose │ │
│ ┌──────────────────┐ │ │ │ (docker-compose.prod.yml) │ │
│ │ Container reg. │◀──┼─ pull ──┤ │ gateway, mcp-* │ │
│ └──────────────────┘ │ │ │ watchtower (poll 5min) │ │
│ ┌──────────────────┐ │ │ └────────────────────────────┘ │
│ │ Actions runner │ │ │ │
│ └──────────────────┘ │ │ │
└─────────────────────────┘ └──────────────────────────────────┘
1. Pipeline CI/CD (Gitea Actions)
.gitea/workflows/ci.yml ad ogni push su main esegue 5 job:
- lint (
ruff check services/) — gating, 0 violations. - typecheck (
mypy services/common/src/mcp_common) — gating sul modulo comune, warn-only sui servizi. - test (
pytest services/) — gating, 478 test verdi. - validate-config —
docker compose config -qsudocker-compose.ymledocker-compose.prod.yml+caddy validate --config Caddyfile. - build-and-push — solo su push a
main(skip su PR):docker login git.tielogic.xyzcon${{ secrets.REGISTRY_TOKEN }}(PAT scopewrite:package, configurato user-level su Gitea).- Builda e pusha 8 image al registry con tag
:latest+:sha-X:git.tielogic.xyz/adriano/cerbero-mcp/basegit.tielogic.xyz/adriano/cerbero-mcp/gatewaygit.tielogic.xyz/adriano/cerbero-mcp/mcp-{deribit,bybit,hyperliquid,alpaca,macro,sentiment}
- Cache Docker buildx via registry stesso (
buildcache:<name>per ognuna) → run successivi 5-10× più veloci.
Le PR fanno girare solo lint+typecheck+test+validate-config,
niente build/push (gating per merge).
Setup runner Gitea
Il runner act_runner deve girare:
- Sulla stessa Docker network di Gitea (
gitea_gitea-internalo equivalente) altrimentiactions/checkout@v4non risolvegitea:3000per il clone. - Con bind di
/var/run/docker.sockper buildare image dai workflow. - Image dei job mappata a
docker.gitea.com/runner-images:ubuntu-22.04(default Gitea, contiene docker CLI + buildx + git + curl + node). - Label: almeno
ubuntu-latest(usato dal workflow). Altre label (tielogic-ci,ubuntu-22.04, ecc.) opzionali.
Setup secret REGISTRY_TOKEN
Crea un Personal Access Token Gitea con scope write:package (User
Settings → Applications → Generate Token). Aggiungilo come secret a
livello user (User Settings → Secrets → New Secret) con nome
REGISTRY_TOKEN. Tutti i tuoi repo ereditano il secret automaticamente.
2. Deploy automatizzato (script)
Il modo più rapido è scripts/deploy.sh, idempotente. Esegui sul VPS:
# Prerequisiti
export GITEA_PAT="<PAT con scope read:package>"
export GITEA_USER=adriano
mkdir -p ~/cerbero-secrets
# Copia (via scp dal posto sicuro) i secret in ~/cerbero-secrets/:
# deribit.json bybit.json hyperliquid.json alpaca.json
# macro.json sentiment.json core.token observer.token
# Clone temporaneo solo per lo script
curl -sL -o /tmp/deploy.sh \
https://git.tielogic.xyz/Adriano/Cerbero-mcp/raw/branch/main/scripts/deploy.sh
chmod +x /tmp/deploy.sh
# Run (richiede sudo per /opt/cerbero-mcp e /var/log/cerbero-mcp)
/tmp/deploy.sh
Lo script esegue: docker login registry → clone/pull repo in
/opt/cerbero-mcp → copia secrets con chmod 600 → genera .env
iniziale (testnet) → crea /var/log/cerbero-mcp con permessi 1000:1000
→ pull image dal registry → docker compose up -d → smoke test pubblico.
Per aggiornare in seguito: ri-esegui lo stesso script (preserva .env).
3. Safety: switch testnet → mainnet
mcp_common.environment.consistency_check (richiamato dal boot
run_exchange_main) PREVIENE switch accidentali:
- Se l'ambiente risolto è mainnet ma il secret JSON corrispondente
non contiene
"environment": "mainnet"esplicito → boot abort conEnvironmentMismatchError. - Se il secret dichiara un environment diverso da quello risolto (es.
creds["environment"]="mainnet"ma env var setta testnet) → boot abort.
Per passare a mainnet su un exchange specifico (es. bybit):
- Edita
secrets/bybit.json: aggiungi"environment": "mainnet". - Modifica
.env:BYBIT_TESTNET=false. docker compose -f docker-compose.prod.yml --env-file .env restart mcp-bybit.
Senza il flag esplicito nel secret, il container mcp-bybit fallirà al boot e Watchtower NON aggiornerà su versioni con cred mainnet rotti.
Override STRICT_MAINNET=false in .env permette mainnet senza la
conferma esplicita (downgrade safety, sconsigliato in produzione).
4. Audit log persistente
Tutti i write endpoint (place_order, place_combo_order, cancel_*,
set_*, close_*, transfer_*, amend_*, switch_*) emettono un
record JSON strutturato sul logger mcp.audit.
Sink:
- stdout/stderr container (sempre, visibile via
docker logs). - File JSONL persistente su volume host:
${AUDIT_LOG_DIR:-/var/log/cerbero-mcp}/<service>.audit.jsonl. Rotation a mezzanotte UTC con retentionAUDIT_LOG_BACKUP_DAYS(default 30 giorni).
Esempio record:
{
"audit_event": "write_op",
"action": "place_order",
"exchange": "bybit",
"principal": "core",
"target": "BTCUSDT",
"payload": {"side": "Buy", "qty": 0.01, "price": 60000, "leverage": 3},
"result": {"order_id": "abc123", "status": "submitted"}
}
Query operative:
# Tutto l'audit log oggi
tail -f /var/log/cerbero-mcp/*.audit.jsonl
# Solo place_order su bybit
jq -c 'select(.action=="place_order" and .exchange=="bybit")' \
/var/log/cerbero-mcp/bybit.audit.jsonl
# Errori
jq -c 'select(.error)' /var/log/cerbero-mcp/*.audit.jsonl
# Operazioni di un principal
jq -c 'select(.principal=="core")' /var/log/cerbero-mcp/*.audit.jsonl
I secret (api_key, password) sono filtrati automaticamente da
SecretsFilter prima di arrivare al sink.
5. Setup iniziale del VPS (manuale, alternativa allo script)
Pre-requisiti: Docker Engine ≥ 24, docker compose plugin, accesso SSH
sudo, dominio DNS A record cerbero-mcp.tielogic.xyz → IP del VPS, porte 80
e 443 aperte sul firewall (per ACME challenge + traffico HTTPS).
a) Login al registry Gitea
Crea un Personal Access Token su Gitea (Settings → Applications → Generate new token) con scope read:package. Quindi sul VPS:
echo "$GITEA_PAT" | docker login git.tielogic.xyz -u <gitea-username> --password-stdin
Le credenziali vengono salvate in ~/.docker/config.json. Watchtower lo
bind-monta in sola lettura per fare i pull autenticati.
b) Clona repository (solo per i file di compose, secret e Caddyfile)
sudo mkdir -p /opt/cerbero-mcp && sudo chown $USER /opt/cerbero-mcp
cd /opt/cerbero-mcp
git clone ssh://git@git.tielogic.xyz:222/Adriano/Cerbero-mcp.git .
Il VPS NON ha bisogno di buildare; usa docker-compose.prod.yml che fa solo
pull dal registry.
c) Prepara secrets
mkdir -p secrets
# Copia (via scp) i file JSON con cred reali:
# secrets/deribit.json, bybit.json, alpaca.json, hyperliquid.json,
# secrets/macro.json, sentiment.json
# secrets/core.token, observer.token
chmod 600 secrets/*
d) .env con configurazione runtime
Crea /opt/cerbero-mcp/.env:
# Gateway
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` per auto-update Watchtower, oppure pin a sha-XXXXXXX
IMAGE_TAG=latest
IMAGE_PREFIX=git.tielogic.xyz/adriano/cerbero-mcp
# Environment exchange (true=testnet, false=mainnet)
DERIBIT_TESTNET=true
BYBIT_TESTNET=true
HYPERLIQUID_TESTNET=true
ALPACA_PAPER=true
# Watchtower polling interval (sec). 300=5min default.
WATCHTOWER_POLL_INTERVAL=300
e) Avvio
docker compose -f docker-compose.prod.yml --env-file .env pull
docker compose -f docker-compose.prod.yml --env-file .env up -d
docker compose -f docker-compose.prod.yml logs -f gateway
Caddy chiede automaticamente il certificato Let's Encrypt al primo
contatto su https://cerbero-mcp.tielogic.xyz.
6. Auto-update via Watchtower
Watchtower (servizio watchtower nel compose) polla il registry ogni
WATCHTOWER_POLL_INTERVAL secondi. Se trova un nuovo digest dietro al tag
:latest di un container etichettato com.centurylinklabs.watchtower.enable=true,
fa:
docker pulldella nuova imagedocker stopgraceful del container vecchiodocker rm+ start del nuovo container con stessa config + secret + volumi- Cleanup image vecchia (
WATCHTOWER_CLEANUP=true)
I container con label sono: gateway, mcp-deribit, mcp-bybit,
mcp-hyperliquid, mcp-alpaca, mcp-macro, mcp-sentiment. Il container
watchtower stesso non si auto-aggiorna (per evitare loop).
Disabilitare auto-update temporaneamente
Pin a uno SHA specifico nel .env:
IMAGE_TAG=sha-6b7b3f7
docker compose -f docker-compose.prod.yml --env-file .env up -d
In questo modo :latest non viene più seguito; per riattivare il rollover
automatico ripristina IMAGE_TAG=latest.
Disabilitare auto-update per un singolo servizio
Rimuovi la label com.centurylinklabs.watchtower.enable=true per quel
servizio nel compose (oppure imposta =false). Watchtower lo ignora ma
continua a tenere aggiornati gli altri.
7. Rollback
# Trova lo SHA della versione precedente
docker images "git.tielogic.xyz/adriano/cerbero-mcp/*" --format "{{.Tag}}"
# Pin nel .env
IMAGE_TAG=sha-XXXXXXX
docker compose -f docker-compose.prod.yml --env-file .env up -d
Watchtower NON downgraderà perché il digest del tag pin corrisponde a quello locale.
8. Smoke test post-deploy
# Da fuori VPS (laptop)
curl -s https://cerbero-mcp.tielogic.xyz/mcp-macro/health
# {"status":"ok",...}
# Test write endpoint allowlist (deve rispondere 403 da IP esterno):
curl -X POST https://cerbero-mcp.tielogic.xyz/mcp-deribit/tools/place_order \
-H "Authorization: Bearer $(cat secrets/core.token)" \
-d '{"instrument_name":"BTC-PERPETUAL","side":"buy","amount":1}'
# 403 forbidden: source ip not in allowlist ← OK
# Sul VPS:
GATEWAY=http://localhost bash tests/smoke/run.sh
9. Sicurezza VPS
- Firewall
ufw:allow 22, 80, 443. Tutto il resto deny in. fail2bansu SSH e (opz) sul log Caddy 401.- Secret rotation manuale: aggiorna i file
secrets/*.token→docker compose restart(i token vengono ricaricati al boot di ogni servizio MCP). - Audit log in
docker compose logs <service> | grep audit_event— per produzione meglio redirezionare a syslog o a un servizio dedicato.
10. Note Traefik / reverse proxy davanti a Gitea
Gitea è esposto via Traefik (ROOT_URL https://git.tielogic.xyz). Per il push
di image Docker il reverse proxy deve consentire upload di body grossi (un
singolo layer può superare i 100MB).
Traefik default va bene, ma se vedi 413 Request Entity Too Large durante
docker push aumenta il limite nel middleware:
# traefik dynamic config
http:
middlewares:
gitea-upload:
buffering:
maxRequestBodyBytes: 524288000 # 500MB
Applicalo come middleware al router Gitea.
11. Aggiornamento del compose stesso (file YAML)
Watchtower aggiorna le image, non il docker-compose.prod.yml. Se cambi
struttura (nuovi servizi, nuove env var) devi:
cd /opt/cerbero-mcp
git pull
docker compose -f docker-compose.prod.yml --env-file .env up -d
Per automatizzare anche questo serve un cron job o uno step CD push-based (vedi backlog).