diff --git a/CHANGELOG.md b/CHANGELOG.md index c785025..ac34f71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ incompatibles. Chaque ligne renvoie à un commit dédié (un artefact = un commi ## [Unreleased] +## [0.4.0] — 2026-06-23 — fin Phase 0 (socle) +### Added +- `infra/docker-compose.yml` : stack CHLOVA — Ollama (proxy cloud, interne + + egress, aucun port publié), socket-proxy (lecture seule), MCP n8n + MCP + Portainer (read-only), backend (seule surface, aucun port publié en P1). + Images à tags épinglés (digests à confirmer avant déploiement réel), réseaux + `chlova-internal` (internal) / `chlova-egress`. Config validée (`compose config`). + ## [0.3.0] — 2026-06-23 ### Added - Socle sécurité : `docs/security.md` (modèle de menace prompt-injection + diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml new file mode 100644 index 0000000..1e8da8c --- /dev/null +++ b/infra/docker-compose.yml @@ -0,0 +1,117 @@ +# CHLOVA — stack d'orchestration. +# Règles : une seule surface exposée (backend), tout le reste interne, images +# épinglées (jamais :latest), secrets par référence (voir ../.env.example). +# +# ⚠️ Les tags ci-dessous sont des points d'épinglage à CONFIRMER avant tout +# déploiement réel : `docker pull :` puis remplacer par +# `:@sha256:` pour une immuabilité complète (docs/versioning.md). +# Aucun déploiement réel n'est lancé depuis ce dépôt sans validation explicite. + +name: chlova + +services: + # ── Ollama : proxy authentifié vers les modèles cloud (ollama.com) ────── + ollama: + image: ollama/ollama:0.6.8 # TODO épingler le digest + restart: unless-stopped + environment: + # Clé du proxy cloud — injectée depuis .env, jamais en dur. + OLLAMA_API_KEY: ${OLLAMA_API_KEY:?OLLAMA_API_KEY requis} + OLLAMA_HOST: 0.0.0.0:11434 + volumes: + - ollama-data:/root/.ollama + networks: + - chlova-internal # joignable par le backend + - chlova-egress # seul service autorisé à sortir + # AUCUN port publié : Ollama n'a pas d'auth native, jamais exposé. + + # ── socket-proxy : accès Docker filtré (voir socket-proxy/README.md) ──── + socket-proxy: + image: tecnativa/docker-socket-proxy:0.3.0 # TODO épingler le digest + restart: unless-stopped + environment: + # Phase 1 — LECTURE SEULE : lecture autorisée, mutation refusée. + CONTAINERS: 1 + IMAGES: 1 + NETWORKS: 1 + VOLUMES: 1 + SERVICES: 1 + TASKS: 1 + NODES: 1 + INFO: 1 + VERSION: 1 + POST: 0 # refuse toute mutation + EXEC: 0 # refuse exec dans un conteneur + AUTH: 0 + SECRETS: 0 + CONFIGS: 0 + BUILD: 0 + COMMIT: 0 + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro # seul à monter le socket + networks: + - chlova-internal + # AUCUN port publié. + + # ── MCP n8n (czlonkowski/n8n-mcp) — read-only en Phase 1 ──────────────── + mcp-n8n: + image: ghcr.io/czlonkowski/n8n-mcp:2.18.3 # TODO confirmer tag + épingler digest + restart: unless-stopped + environment: + MCP_MODE: http # transport HTTP (backend distant via réseau) + MCP_AUTH_TOKEN: ${MCP_N8N_AUTH_TOKEN:?requis} + N8N_API_URL: ${N8N_API_URL} # instance n8n existante (interne) + N8N_API_KEY: ${N8N_API_KEY:?requis} # token n8n à portée RESTREINTE (lecture P1) + networks: + - chlova-internal + # AUCUN port publié. + + # ── MCP Portainer (portainer/portainer-mcp) — read-only en Phase 1 ────── + mcp-portainer: + image: portainer/portainer-mcp:0.6.0 # TODO confirmer tag/transport + épingler digest + restart: unless-stopped + environment: + PORTAINER_URL: ${PORTAINER_URL} + PORTAINER_MCP_AUTH_TOKEN: ${PORTAINER_MCP_AUTH_TOKEN:?requis} + PORTAINER_READ_ONLY: ${PORTAINER_READ_ONLY:-true} # P1 : NE PAS passer à false + # Docker via socket-proxy uniquement, jamais le socket brut. + DOCKER_HOST: tcp://socket-proxy:2375 + depends_on: + - socket-proxy + networks: + - chlova-internal + # AUCUN port publié. + + # ── Backend CHLOVA : SEULE surface, cerveau (boucle agent) ────────────── + backend: + build: + context: ../orchestrator # Dockerfile ajouté en Phase 1 + image: chlova/backend:0.1.0 # tag versionné local + restart: unless-stopped + env_file: ../.env + environment: + CHLOVA_ENV: ${CHLOVA_ENV:-production} + OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://ollama:11434} + MCP_N8N_URL: ${MCP_N8N_URL:-http://mcp-n8n:3000} + MCP_PORTAINER_URL: ${MCP_PORTAINER_URL:-http://mcp-portainer:3000} + volumes: + - chlova-data:/app/data # SQLite (table assets, P2+) + depends_on: + - ollama + - mcp-n8n + - mcp-portainer + networks: + - chlova-internal + # Phase 1 : surface Telegram en long-polling → AUCUN port publié. + # Phases ultérieures (API/UI) : exposer UNIQUEMENT ce service derrière + # Traefik + TLS et/ou VPN mesh. Voir infra/networks.md. + +networks: + chlova-internal: + internal: true # aucune route vers l'extérieur + chlova-egress: + driver: bridge # sortie contrôlée (Ollama → ollama.com), egress filtré côté hôte + +volumes: + ollama-data: + chlova-data: