d824d16eed
Compose de prod docker-compose.prod.yml (GitOps, sans env_file, réseau proxy réel, certresolver letsencrypt) + runbook docs/deploy.md (Phase 1, users chlova restreints Portainer/n8n). Surface Telegram rendue optionnelle pour un déploiement UI-only ; garde assertHasSurface fail-closed. Typecheck + 78 tests verts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016w5jRe87MGdd6AMvXQcHNi
149 lines
6.6 KiB
Markdown
149 lines
6.6 KiB
Markdown
# 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 <URL_DEPOT_PRIVE>
|
|
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 -- <admin_user> '<mot_de_passe_fort>'
|
|
```
|
|
|
|
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://<portainer>.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).
|