# Déploiement CHLOVA (Phase 1 — lecture seule, GitOps Portainer) Procédure de mise en production réelle sur le homelab. Cible : environnement Portainer **`vps-pogoo-002`** (endpoint 11), aux côtés de `proxy` (Traefik), `n8n`, `jenkins`. > **Modèle de risque.** Phase 1 = lecture seule : `CHLOVA_PHASE=1`, > `PORTAINER_READ_ONLY=true`, MCP read-only filtré. Aucune écriture branchée. > Les secrets ne transitent **jamais** par l'agent : ils sont saisis par > l'opérateur dans les variables de stack Portainer (UI) — voir §4. ## Vue d'ensemble | Brique | Réseau | Exposé ? | |---|---|---| | `backend` (API + SPA) | `chlova-internal` + `proxy` | **Oui** — `chlova.pogoo.app` via Traefik/TLS | | `ollama` (proxy cloud) | `chlova-internal` + `chlova-egress` | Non | | `mcp-portainer` (sidecar) | `chlova-internal` | Non | | `socket-proxy` (Docker RO) | `chlova-internal` | Non | | n8n (MCP natif) | `proxy` (existant) | déjà exposé en `n8n.pogoo.app` | Compose de prod : [`infra/docker-compose.prod.yml`](../infra/docker-compose.prod.yml). Build de l'image fait **par Portainer sur le VPS** (GitOps : clone du dépôt + `docker compose build`). Contexte de build = racine du dépôt. --- ## 1. Prérequis — dépôt git accessible au VPS GitOps exige un remote que Portainer peut cloner. Choisir un hébergeur privé (GitHub privé, Gitea du homelab…), puis : ```bash git remote add origin git push -u origin main ``` Noter l'URL de clone (HTTPS) + des identifiants en lecture (PAT/deploy key) pour Portainer. ## 2. Prérequis — secrets & login fort (générés, jamais commités) Générer le hash de mot de passe + secrets TOTP/JWT pour l'admin de l'UI : ```bash cd orchestrator npm run provision-auth -- '' ``` La commande imprime `CHLOVA_ADMIN_PASSWORD_HASH`, `CHLOVA_TOTP_SECRET`, `CHLOVA_JWT_SECRET` et un `otpauth://…` à scanner dans une app TOTP. **Conserver ces valeurs pour l'étape 4 (UI Portainer).** Ne pas les committer. ## 3. Prérequis — users restreints (`chlova`) > Le MCP Portainer n'expose pas la gestion d'utilisateurs/tokens : ces étapes se > font dans les **UI** Portainer et n8n. Principe : CHLOVA n'accède qu'à ses > ressources, avec des tokens à portée minimale. ### 3a. Portainer — user `chlova` + token 1. **Users → Add user** : `chlova`, mot de passe fort. **Non-administrateur.** 2. **Environments → vps-pogoo-002 → Access** : donner à `chlova` le rôle le plus bas suffisant. En CE, la granularité est **par environnement** (pas par stack) — l'accès se limite donc à ce seul environnement. *(La restriction par-ressource fine nécessite l'édition Business ; à défaut, le verrou réel reste `PORTAINER_READ_ONLY=true` côté sidecar.)* 3. Se connecter en tant que `chlova` → **My account → Access tokens → Add token**. Copier le token → ce sera `PORTAINER_MCP_AUTH_TOKEN`. ### 3b. n8n — projet + membre `chlova` 1. **Admin → Users** : inviter/créer un utilisateur `chlova`. 2. **Projects → New project** « CHLOVA » ; ajouter `chlova` comme membre. Y déplacer les workflows que CHLOVA doit voir (les autres restent invisibles). 3. **Settings → n8n API / MCP** : activer le serveur MCP natif, générer le **MCP Access Token** scopé → ce sera `MCP_N8N_AUTH_TOKEN`. Endpoint interne : `http://n8n:5678/mcp-server/http` (backend sur réseau `proxy`). ## 4. Déploiement du stack (Portainer) `infra/docker-compose.prod.yml` n'utilise **aucun** `env_file` : toutes les variables ci-dessous sont fournies comme **variables d'environnement du stack** Portainer (les secrets y sont saisis par l'opérateur, jamais par l'agent). ### Variables de stack à renseigner | Variable | Exemple / valeur | Secret ? | |---|---|---| | `CHLOVA_DOMAIN` | `chlova.pogoo.app` | non | | `CHLOVA_PHASE` | `1` | non | | `PORTAINER_READ_ONLY` | `true` | non | | `OLLAMA_API_KEY` | clé Ollama cloud | **oui** | | `OLLAMA_MODEL` | `qwen3:cloud` | non | | `MCP_N8N_AUTH_TOKEN` | token MCP n8n (§3b) | **oui** | | `PORTAINER_URL` | URL interne de l'API Portainer (cf. §4 note) | non | | `PORTAINER_MCP_AUTH_TOKEN` | token user chlova (§3a) | **oui** | | `CHLOVA_ADMIN_USER` | ex. `kantin` | non | | `CHLOVA_ADMIN_PASSWORD_HASH` | (§2) | **oui** | | `CHLOVA_TOTP_SECRET` | (§2) | **oui** | | `CHLOVA_JWT_SECRET` | (§2) | **oui** | > **`PORTAINER_URL`** = URL **publique** du serveur Portainer. Topologie réelle : > le serveur Portainer (`portainer-ce`) tourne sur l'hôte `local`, tandis que > CHLOVA et son sidecar tournent sur `vps-pogoo-002` (un **autre hôte**). Le > sidecar ne peut donc pas joindre Portainer en interne : utiliser l'URL publique > (la même que celle configurée pour le MCP `portainer-pogoo`), p. ex. > `https://.pogoo.app`. Pas de `:9443` interne ici. ### Procédure UI (recommandée — l'opérateur saisit les secrets) **Stacks → Add stack → Git repository** : - Repository URL = remote de l'étape 1 ; Reference = `refs/heads/main` ; Compose path = `infra/docker-compose.prod.yml`. - Authentication = identifiants de lecture (§1). - Environment variables = tableau ci-dessus. - **Deploy**. Portainer clone, build l'image et lance le stack. ### Procédure assistée (MCP) CHLOVA peut créer le stack via `StackCreateDockerStandaloneRepository` une fois le remote prêt — **mais** les valeurs secrètes ne doivent pas transiter par l'agent. Schéma retenu : l'agent crée le stack avec les variables **non secrètes** et l'opérateur ajoute/édite les secrètes dans l'UI avant le 1er déploiement, **ou** l'opérateur fait l'étape UI ci-dessus de bout en bout. ## 5. Vérification (post-déploiement) 1. Portainer → le stack `chlova` est *running* ; conteneurs `backend`, `ollama`, `mcp-portainer`, `socket-proxy` *up*. 2. Logs `backend` : `API/UI activée (auth configurée)` + `healthcheck interne prêt`. Pas d'erreur de config fail-closed. 3. `https://chlova.pogoo.app` répond (certificat `letsencrypt` émis) → page de login (mot de passe + TOTP). 4. Connexion → l'onglet Chat répond ; les outils MCP read-only (n8n/Portainer) sont listés dans l'état (header UI). ## 6. Rollback GitOps : revert du commit compose + redeploy, ou **Stacks → chlova → Stop/Remove**. Le volume `chlova-data` (SQLite) persiste ; le supprimer pour repartir de zéro. ## 7. Passage en Phase 2 (plus tard, hors de cette procédure) Écriture sous gatekeeper + need-review : `CHLOVA_PHASE=2` et `PORTAINER_READ_ONLY=false` (le sidecar autorise alors les mutations, toujours filtrées par le gatekeeper + paliers de risque). À ne faire qu'après validation du cerveau en lecture seule. Voir [`docs/need-review.md`](./need-review.md).