# EnerVision · procédure de déploiement (22/09/2026) Terraform provisionne la machine, GitHub Actions déploie (ADR 0010). Deux environnements Compose sur la VM ENI `10.101.200.37` : `rec` sur la branche `dev`, `prod` sur `main` (ADR 0009). | | recette | production | |---|---|---| | Branche, environnement GitHub | `dev`, `rec` | `main`, `prod` | | Dossier, projet Compose | `/srv/enervision/rec`, `enervision-rec` | `/srv/enervision/prod`, `enervision-prod` | | URL | `https://rec.enervision.local:8443` | `https://enervision.local` | | Proxy HTTP / HTTPS | `127.0.0.1:8081` / `8443` | `80` / `443` | | Postgres / Mailpit / Airflow (locaux) | `5434` / `8026` / `8082` | `5433` / `8025` / `8080` | ## 0. Avant toute commande 1. **Clé SSH déposée** sur la VM : `ssh-copy-id -i ~/.ssh/id_ed25519.pub root@10.101.200.37`. Terraform ne gère **pas** l'authentification par mot de passe (elle finirait dans le state). 2. **L'utilisateur propriétaire existe déjà** sur la VM (ex. `enervision`) : il possède `/srv/enervision` et fait tourner le runner. Terraform échoue tôt s'il manque, il ne le crée pas. 3. **Jeton d'enregistrement du runner** : Settings > Actions > Runners > New self-hosted runner. Valable 1 h, une seule inscription, créé par un administrateur du dépôt (ineszang). 4. **`main` est en retard de 64 commits** et ne porte ni `deploy.yml`, ni `provision-host.sh`, ni le Terraform, ni l'overlay paramétré (ports et `PUBLIC_ORIGIN` en dur). Tant que `dev` n'est pas remonté dans `main`, seule la recette est déployable : le clone `prod` sera préparé mais son `make stack-up` publierait 80/443 sans les variables, et aucun push sur `main` ne déclencherait de déploiement (le workflow n'y existe pas). **Remonter `dev` → `main` avant de toucher à prod.** ## 1. Provisionner la machine (depuis le poste) ```bash cd infra/terraform/environments/vm-eni cp terraform.tfvars.example terraform.tfvars terraform init terraform apply ``` `terraform.tfvars`, ignoré par git, trois valeurs à renseigner : ```hcl proprietaire = "enervision" # doit exister sur la VM runner_version = "2.330.0" # épingler depuis github.com/actions/runner/releases runner_token = "..." # jeton d'1 h, à retirer du fichier après l'apply ``` Défauts utiles : `ssh_host = "10.101.200.37"`, `ssh_user = "root"`, `ssh_private_key_path = "~/.ssh/id_ed25519"`, `racine = "/srv/enervision"`, `runner_labels = "eni-g3"` (ciblé par `deploy.yml`), `runner_dossier = "/opt/actions-runner"`. L'apply fait trois choses, dans cet ordre : Docker + plugin Compose et `usermod -aG docker`, puis `scripts/provision-host.sh`, puis l'installation et l'enregistrement du runner en service. Il ne construit aucune image et ne démarre aucun conteneur : un apply n'interrompt pas la stack. Rejouable : un clone existant est réaligné, un `.env` présent n'est **jamais** réécrit, un certificat présent n'est jamais régénéré. Un nouvel apply de la ressource runner redemande un jeton frais (il expire en 1 h). ## 2. Variables d'environnement Un `.env` par dossier, en `600`, généré sur la machine depuis `.env.example`. **Aucun secret ne passe par git ni par GitHub** : le runner n'en reçoit aucun (seul `SONAR_TOKEN` existe côté CI). **Générés automatiquement** : `POSTGRES_PASSWORD`, `APP_SECRET_KEY`, `AIRFLOW_FERNET_KEY`, `AIRFLOW_API_SECRET_KEY`, `AIRFLOW_JWT_SECRET`, `AIRFLOW_ADMIN_PASSWORD`, `AIRFLOW_APP_SECRET_KEY`. **Fixés par environnement** : `COMPOSE_PROJECT_NAME`, `PUBLIC_HOST`, `PUBLIC_ORIGIN`, `PROXY_HTTP_PORT`, `PROXY_HTTPS_PORT`, `POSTGRES_PORT`, `MAILPIT_UI_PORT`, `AIRFLOW_PORT`. **À renseigner à la main**, dans chaque `.env`, avant le premier démarrage : ``` APP_MOCK_API_USERNAME=... APP_MOCK_API_PASSWORD=... ``` Garde-fou : le script refuse d'écrire un `.env` s'il reste un `change_me` hors `APP_MOCK_API_*` (cas vécu d'une clé renommée en amont, `AIRFLOW_WEBSERVER_SECRET_KEY` sous Airflow 3). `APP_ENV=prod` et `APP_DEBUG=false` sont en dur dans l'overlay, pas dans le `.env` : la valeur `local` du poste reprendrait le dessus et rouvrirait `/docs` sans cookie `__Secure-`. `TS_TUNE_MEMORY=2GB` et `TS_TUNE_NUM_CPUS=2` sont obligatoires : deux TimescaleDB sur 8 Go se réserveraient 25 % de la RAM chacune. La montée à 32 Go est à demander. Certificats auto-signés générés par le script (`infra/proxy/tls/`), couvrant le nom d'hôte, `localhost` et l'IP. Let's Encrypt (`make tls-acme`, `ACME_EMAIL`) reste hors d'atteinte sans domaine public résolvable. ## 3. Premier démarrage (manuel, une seule fois, sur la VM) ```bash cd /srv/enervision/rec && make stack-up # build + up + alembic upgrade head cd /srv/enervision/prod && make stack-up # seulement après la remontée dev → main ``` `stack-up` refuse de démarrer si le certificat manque ou ne couvre pas `PUBLIC_HOST`, et applique les migrations : sans elles la stack démarrerait verte sur une base sans schéma. Premier administrateur, stack démarrée, dans chaque dossier : ```bash docker compose -f docker-compose.yml -f docker-compose.prod.yml exec backend \ python -m app.cli create-admin --email ``` Données historiques : `data/raw` n'est pas dans git. Déposer les fichiers dans chaque dossier avant de déclencher le DAG `historical_import`. ## 4. Réglages GitHub (administrateur du dépôt) - Environnement `prod` : branche `main` seule autorisée, **approbation d'un relecteur** requise. - Environnement `rec` : branche `dev` seule autorisée, sans approbation. - Settings > Actions : **« Require approval for all outside collaborators »**. Un runner auto-hébergé sur un dépôt public exécute ce qu'on lui envoie ; `deploy.yml` ne se déclenche jamais sur `pull_request`, et le runner ne tourne jamais en root. ## 5. Déploiement continu, ensuite Un push sur `dev` déploie la recette, un push sur `main` la production après approbation. Le job (runner `eni-g3`) aligne le clone (`fetch`, `checkout`, `reset --hard`), lance `make stack-up`, puis sonde `/api/v1/health/ready` derrière le proxy pendant 3 minutes ; en cas d'échec il publie `ps` et les 50 dernières lignes de `backend` et `proxy`. Pas de `checkout` dans l'espace du runner : `.env`, certificats et volumes doivent survivre d'un déploiement à l'autre. Concurrence par branche, sans annulation. Déclenchement manuel possible : `workflow_dispatch`. ## 6. Vérifier ```bash curl -k https://localhost:8443/api/v1/health/ready # recette, sur la VM curl -k https://localhost/api/v1/health/ready # production, sur la VM ``` Depuis un poste, ajouter à `/etc/hosts` : ``` 10.101.200.37 enervision.local rec.enervision.local ``` Les deux noms sont obligatoires : le cookie `__Secure-ev_refresh` est posé par hôte et non par port ; un seul nom déconnecterait la production à chaque connexion en recette. ## Pièges à connaître - Compose **2.24.4 minimum** : l'overlay emploie `!override` et `!reset`, sans quoi l'API resterait joignable en clair à côté du proxy. Le script le vérifie. - Le runner doit tourner sous le propriétaire de `/srv/enervision` : sinon git refuse les clones (propriété douteuse) et le `.env` en `600` lui échappe. Correctif : `PROPRIETAIRE= bash scripts/provision-host.sh`. - Chaque environnement reconstruit ses images à partir du même commit : la production n'exécute pas l'artefact validé en recette, mais un second build. Le passage à GHCR lèvera cette limite. - Un `.env` perdu se régénère, mais invalide les sessions et les connexions chiffrées par Airflow : ils ne sont sauvegardés nulle part ailleurs. - Retirer le runner se fait à la main, depuis les paramètres du dépôt : `terraform destroy` ne le désinscrit pas. ## Références dans le dépôt `docs/adr/0009-deux-environnements-compose-sur-la-vm-eni.md`, `docs/adr/0010-terraform-provisionne-github-actions-deploie.md`, `docs/architecture/50-cicd.md`, `docs/architecture/10-infra.md`, `infra/README.md`, `scripts/provision-host.sh`, `.github/workflows/deploy.yml`, `docker-compose.prod.yml`.