Files
Cerbero-mcp/DEPLOYMENT.md
T
AdrianoDev a1110c8ecb
ci / ruff lint (push) Failing after 12s
ci / mypy mcp_common (push) Successful in 25s
ci / pytest (push) Successful in 35s
ci / validate compose + Caddyfile (push) Successful in 2m3s
ci / build & push to registry (push) Has been skipped
feat(safety+audit+deploy): consistency_check + audit log file sink + deploy script
#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>
2026-04-29 09:29:04 +02:00

349 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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:
1. **lint** (`ruff check services/`) — gating, 0 violations.
2. **typecheck** (`mypy services/common/src/mcp_common`) — gating sul
modulo comune, warn-only sui servizi.
3. **test** (`pytest services/`) — gating, 478 test verdi.
4. **validate-config**`docker compose config -q` su `docker-compose.yml`
e `docker-compose.prod.yml` + `caddy validate --config Caddyfile`.
5. **build-and-push** — solo su push a `main` (skip su PR):
- `docker login git.tielogic.xyz` con `${{ secrets.REGISTRY_TOKEN }}`
(PAT scope `write:package`, configurato user-level su Gitea).
- Builda e pusha **8 image** al registry con tag `:latest` + `:sha-X`:
- `git.tielogic.xyz/adriano/cerbero-mcp/base`
- `git.tielogic.xyz/adriano/cerbero-mcp/gateway`
- `git.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-internal` o equivalente)
altrimenti `actions/checkout@v4` non risolve `gitea:3000` per il clone.
- Con bind di `/var/run/docker.sock` per 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:
```bash
# 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 con
`EnvironmentMismatchError`.
- 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):
1. Edita `secrets/bybit.json`: aggiungi `"environment": "mainnet"`.
2. Modifica `.env`: `BYBIT_TESTNET=false`.
3. `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 retention `AUDIT_LOG_BACKUP_DAYS`
(default 30 giorni).
**Esempio record**:
```json
{
"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**:
```bash
# 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:
```bash
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)
```bash
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
```bash
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`:
```bash
# 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
```bash
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:
1. `docker pull` della nuova image
2. `docker stop` graceful del container vecchio
3. `docker rm` + start del nuovo container con stessa config + secret + volumi
4. 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`:
```bash
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
```bash
# 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
```bash
# 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.
- `fail2ban` su 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:
```yaml
# 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:
```bash
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).