Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1f9dec5fe | ||
|
|
be6be42947 |
+2
-2
@@ -70,7 +70,7 @@ PUBLIC_ORIGIN=
|
||||
PROXY_HTTP_PORT=
|
||||
PROXY_HTTPS_PORT=
|
||||
# Réglages mémoire de la stack déployée. Sans eux, timescaledb-tune réserve 25 % de la RAM de la
|
||||
# machine à chaque base au premier démarrage, et le webserver Airflow lance 4 workers gunicorn.
|
||||
# machine à chaque base au premier démarrage. L'api-server Airflow 3 n'a rien à régler ici : son
|
||||
# nombre de workers vaut 1 par défaut, contre 4 pour le webserver d'Airflow 2.
|
||||
TS_TUNE_MEMORY=2GB
|
||||
TS_TUNE_NUM_CPUS=2
|
||||
AIRFLOW_WEBSERVER_WORKERS=2
|
||||
|
||||
@@ -52,6 +52,7 @@ jobs:
|
||||
done
|
||||
echo "L'API ne répond pas après 3 minutes" >&2
|
||||
cd "/srv/enervision/${ENVIRONNEMENT}"
|
||||
docker compose ps
|
||||
docker compose logs --tail=50 backend proxy
|
||||
compose="docker compose -f docker-compose.yml -f docker-compose.prod.yml"
|
||||
$compose ps
|
||||
$compose logs --tail=50 backend proxy
|
||||
exit 1
|
||||
|
||||
+1
-3
@@ -40,7 +40,6 @@ kubeconfig
|
||||
# Airflow
|
||||
etl/airflow/logs/
|
||||
airflow.db
|
||||
airflow-webserver.pid
|
||||
standalone_admin_password.txt
|
||||
|
||||
# Environnement et secrets
|
||||
@@ -64,8 +63,7 @@ ml/models/*
|
||||
!ml/models/.gitkeep
|
||||
ml/mlruns/
|
||||
ml/mlartifacts/
|
||||
ml/mlflow.db*
|
||||
ml/.env
|
||||
ml/mlflow.db
|
||||
|
||||
# Airflow : base sqlite locale generee par les tests d'integrite des DAGs (etl/airflow/tests)
|
||||
etl/airflow/tests/.airflow_home/
|
||||
|
||||
@@ -20,8 +20,6 @@ PG_USER := $(or $(strip $(call env-val,POSTGRES_USER)),enervision)
|
||||
PG_PASSWORD := $(or $(strip $(call env-val,POSTGRES_PASSWORD)),change_me)
|
||||
PG_DB := $(or $(strip $(call env-val,POSTGRES_DB)),enervision)
|
||||
PG_PORT := $(or $(strip $(call env-val,POSTGRES_PORT)),5433)
|
||||
ml-env-val = $(shell sed -n 's/^$(1)=//p' ml/.env 2>/dev/null | tail -1)
|
||||
ML_ENV_DB_PASSWORD := $(call ml-env-val,MLFLOW_DB_PASSWORD)
|
||||
AIRFLOW_PORT := $(or $(strip $(call env-val,AIRFLOW_PORT)),8080)
|
||||
MAILPIT_UI_PORT := $(or $(strip $(call env-val,MAILPIT_UI_PORT)),8025)
|
||||
ML_DATABASE_URL ?= postgresql+psycopg://$(PG_USER):$(PG_PASSWORD)@localhost:$(PG_PORT)/$(PG_DB)
|
||||
@@ -37,7 +35,7 @@ DEMO_NOW ?= 2024-12-31T00:00:00Z
|
||||
lint format typecheck test test-cov test-integration check \
|
||||
openapi docker-build db-up db-down db-reset db-logs db-psql db-wait db-ensure-airflow \
|
||||
migrate bootstrap-admin services-up demo-data demo-data-force \
|
||||
ml-lint ml-typecheck ml-test ml-check ml-train ml-score mlflow-up detect-alerts recommendations \
|
||||
ml-lint ml-typecheck ml-test ml-check ml-train ml-score detect-alerts recommendations \
|
||||
airflow-lint airflow-test airflow-check airflow-up airflow-down airflow-logs \
|
||||
tls-selfsigned tls-acme tls-renew stack-up stack-down stack-logs
|
||||
|
||||
@@ -69,7 +67,7 @@ services-up: ## Démarre les services conteneurisés dont `make dev` dépend (ba
|
||||
docker compose up -d db mailpit
|
||||
@$(MAKE) --no-print-directory db-wait
|
||||
@$(MAKE) --no-print-directory db-ensure-airflow
|
||||
docker compose up -d airflow-init airflow-webserver airflow-scheduler
|
||||
docker compose up -d airflow-init airflow-apiserver airflow-scheduler airflow-dag-processor
|
||||
|
||||
dev-backend: ## Lance l'API seule en rechargement à chaud
|
||||
@echo "backend -> http://localhost:8000 (docs sur /docs)"
|
||||
@@ -120,14 +118,6 @@ ml-train: ## Entraine le modele LightGBM. CSV=chemin optionnel, sinon lit ML_DAT
|
||||
ml-score: ## Score le prochain pas horaire et l'ecrit dans `prediction`. CSV= et NOW= optionnels
|
||||
cd $(ML) && uv run python -m enervision_ml.score $(if $(CSV),--csv $(CSV),) $(if $(NOW),--now $(NOW),)
|
||||
|
||||
mlflow-up: ## Démarre le serveur MLflow (tracking + registry) en conteneur. ml/.env requis
|
||||
@test -n "$(strip $(ML_ENV_DB_PASSWORD))" \
|
||||
|| { echo "MLFLOW_DB_PASSWORD absente de ml/.env (copier ml/.env.example)"; exit 1; }
|
||||
@echo "$(ML_ENV_DB_PASSWORD)" | grep -qE '^[A-Za-z0-9]+$$' \
|
||||
|| { echo "MLFLOW_DB_PASSWORD doit contenir uniquement lettres et chiffres (interpolee dans l'URI postgresql://)"; exit 1; }
|
||||
cd $(ML) && docker compose -f docker-compose.mlflow.yml up -d --build
|
||||
@echo "mlflow -> http://localhost:5000"
|
||||
|
||||
detect-alerts: ## Détecte les alertes internes depuis les lectures en base. SITE= et NOW= optionnels
|
||||
cd $(BACKEND) && uv run python -m app.detection.internal_alerts $(if $(SITE),--site-id $(SITE),) $(if $(NOW),--now $(NOW),)
|
||||
|
||||
|
||||
@@ -94,8 +94,9 @@ et frontend en rechargement a chaud sur le poste.
|
||||
| Mailpit | <http://localhost:8025> |
|
||||
|
||||
Le `.env` doit porter les cles Airflow avant le premier `make dev` : `AIRFLOW_FERNET_KEY`,
|
||||
`AIRFLOW_WEBSERVER_SECRET_KEY`, `AIRFLOW_APP_SECRET_KEY` et `AIRFLOW_ADMIN_PASSWORD`. Sans elles
|
||||
`airflow-init` refuse de demarrer, et `airflow-webserver` comme `airflow-scheduler` avec lui.
|
||||
`AIRFLOW_API_SECRET_KEY`, `AIRFLOW_JWT_SECRET`, `AIRFLOW_APP_SECRET_KEY` et
|
||||
`AIRFLOW_ADMIN_PASSWORD`. Sans elles `airflow-init` refuse de demarrer, et `airflow-apiserver`,
|
||||
`airflow-scheduler` et `airflow-dag-processor` avec lui.
|
||||
|
||||
Les cibles d'origine restent disponibles pour ne demarrer qu'une partie : `make db-up`,
|
||||
`make airflow-up`, `make dev-backend`, `make dev-frontend`.
|
||||
|
||||
@@ -25,8 +25,6 @@ services:
|
||||
airflow-apiserver:
|
||||
ports: !override
|
||||
- "127.0.0.1:${AIRFLOW_PORT:-8080}:8080"
|
||||
environment:
|
||||
AIRFLOW__WEBSERVER__WORKERS: ${AIRFLOW_WEBSERVER_WORKERS:-2}
|
||||
|
||||
backend:
|
||||
ports: !reset null
|
||||
|
||||
@@ -63,8 +63,9 @@ secret.
|
||||
|
||||
- Deux TimescaleDB sur une machine de 8 Go : sans réglage, chacune se réserverait 25 % de la
|
||||
RAM au premier démarrage. L'overlay fixe `TS_TUNE_MEMORY` à 2 Go et `TS_TUNE_NUM_CPUS` à 2 par
|
||||
base, et 2 workers gunicorn par webserver Airflow. La montée à 32 Go prévue par les
|
||||
consignes est à demander.
|
||||
base. Airflow 3 n'a rien à régler de ce côté : son api-server lance un seul worker par défaut,
|
||||
là où le webserver d'Airflow 2 en lançait quatre. La montée à 32 Go prévue par les consignes
|
||||
est à demander.
|
||||
- Un runner auto-hébergé sur un dépôt public exécute le code qu'on lui envoie. `deploy.yml` ne
|
||||
se déclenche jamais sur `pull_request`, le runner tourne sous un utilisateur dédié, et le
|
||||
dépôt doit exiger une approbation pour les workflows des PR externes.
|
||||
|
||||
@@ -9,7 +9,6 @@ quel contexte, quelles décisions sont arrêtées, et ce qui manque encore entre
|
||||
| Docker Compose plus reverse proxy | Déployer sur la machine on-premise | `Fait` |
|
||||
| Deux projets Compose sur la VM ENI, recette et production | Déploiement continu depuis GitHub | `En cours` |
|
||||
| k3s single-node | Cible à terme | `En cours` |
|
||||
| MLflow (`ml/`) | Tracker les expériences et le registre de modèles en local | `Fait`, non relié aux autres topologies |
|
||||
|
||||
## Poste de développement
|
||||
|
||||
@@ -148,34 +147,6 @@ est minimale et n'embarque pas la runtime OpenMP dont LightGBM a besoin, sans qu
|
||||
(`OSError: libgomp.so.1`) n'apparaît qu'à la première tâche réellement exécutée, pas à la
|
||||
construction de l'image.
|
||||
|
||||
### MLflow (`ml/`)
|
||||
|
||||
Statut : `Fait`, en local uniquement. Défini par `ml/docker-compose.mlflow.yml`, indépendant
|
||||
du `docker-compose.yml` principal (réseau, volumes et démarrage séparés).
|
||||
|
||||
| Service | Image | Points notables |
|
||||
|---|---|---|
|
||||
| `mlflow-db` | `postgres:17` | Stocke le tracking store MLflow. Mot de passe obligatoire via `MLFLOW_DB_PASSWORD` |
|
||||
| `mlflow` | Construite depuis `ml/` | Expose l'UI et l'API MLflow sur `127.0.0.1:5000`. Artefacts sur volume `mlflow-artifacts`, tracking store sur `mlflow-db` |
|
||||
|
||||
Portée actuelle : environnement de tracking et de registre de modèles pour le développement
|
||||
local uniquement. Ce compose n'est relié ni à `docker-compose.prod.yml`, ni aux deux
|
||||
environnements Compose de la VM ENI, ni à la cible k3s. Le magasin utilisé par Airflow pour
|
||||
`ml_train`/`ml_score` (SQLite, volume `airflow_ml_state`) en est distinct — les deux MLflow ne
|
||||
se voient pas tant que `MLFLOW_TRACKING_URI` n'est pas posé côté Airflow.
|
||||
|
||||
Limite connue : le DAG Airflow `ml_train` enregistre lui aussi une version a chaque execution
|
||||
via `registered_model_name` (magasin SQLite du volume `airflow_ml_state`, distinct de ce
|
||||
serveur). Versions et artefacts s'y accumulent sans politique de nettoyage -- fonctionne en
|
||||
l'etat, mais a surveiller si les entrainements deviennent frequents.
|
||||
|
||||
Pour relier les runs Airflow (`ml_train`, magasin SQLite local) a ce serveur MLflow, positionner
|
||||
`MLFLOW_TRACKING_URI=http://mlflow:5000` dans l'environnement du service `airflow-scheduler` (ou
|
||||
`http://host.docker.internal:5000` si le serveur MLflow tourne hors du reseau Compose principal),
|
||||
et s'assurer que le conteneur Airflow peut joindre le service `mlflow` -- ce qui suppose de les
|
||||
rapprocher sur le meme reseau Docker ou d'exposer MLflow autrement qu'en `127.0.0.1` uniquement
|
||||
(cf. point 1 sur l'exposition du port). Non fait a ce jour : aucun besoin de centraliser les runs
|
||||
d'entrainement Airflow et locaux n'a encore ete identifie.
|
||||
## Machine cible, exécution Docker
|
||||
|
||||
Statut : `Fait`. Défini par l'overlay `docker-compose.prod.yml`, appliqué par-dessus le
|
||||
|
||||
@@ -287,10 +287,6 @@ Chaque table remplit un rôle précis dans le traitement et l'exploitation des d
|
||||
| `alert` | Enregistrer les alertes, leur type, leur gravité et leur message | API Mock `/alerts` et détections EnerVision |
|
||||
| `recommendation` | Proposer des actions et expliquer la règle qui les motive | Règles métier d'EnerVision |
|
||||
|
||||
Le scoring (`ml_score`) charge le modèle depuis un fichier local (`models/lightgbm-consumption.txt`)
|
||||
et trace son empreinte SHA-256 dans `prediction.model_reference`. Il ne lit aucune version depuis
|
||||
le Model Registry MLflow (`ml/`) : ce registre sert aujourd'hui à la traçabilité des
|
||||
entraînements, pas au déploiement du modèle de scoring.
|
||||
Les anomalies historiques décrites dans les JSON sont conservées dans `dataset.metadata`.
|
||||
|
||||
Elles servent à l'analyse des données et ne sont pas considérées comme des alertes actuelles.
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
.venv
|
||||
data
|
||||
mlruns
|
||||
mlflow.db*
|
||||
models
|
||||
.env
|
||||
@@ -1 +0,0 @@
|
||||
MLFLOW_DB_PASSWORD=change-me
|
||||
@@ -1,7 +0,0 @@
|
||||
FROM python:3.14-slim
|
||||
RUN pip install --no-cache-dir --only-binary :all: mlflow==3.16.1 psycopg2-binary==2.9.13
|
||||
RUN useradd --create-home --uid 1000 mlflow \
|
||||
&& mkdir /mlartifacts \
|
||||
&& chown mlflow /mlartifacts
|
||||
USER mlflow
|
||||
EXPOSE 5000
|
||||
@@ -57,50 +57,6 @@ validation. La coupure est **chronologique**, jamais un tirage aleatoire de lign
|
||||
aleatoire laisserait des lignes de validation "voir" des lignes d'entrainement via leurs
|
||||
lags/moyennes glissantes, une fuite qui masquerait un surapprentissage.
|
||||
|
||||
## Serveur MLflow (conteneur)
|
||||
|
||||
Premiere utilisation : copier `.env.example` en `.env` et y choisir un mot de passe PostgreSQL
|
||||
(lettres et chiffres uniquement). Le fichier `.env` est ignore par git.
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Un serveur MLflow (PostgreSQL pour les metadonnees, volume pour les artefacts) se lance avec
|
||||
Docker. Prerequis : Docker Desktop demarre.
|
||||
|
||||
```bash
|
||||
make mlflow-up
|
||||
```
|
||||
|
||||
La cible vérifie que `MLFLOW_DB_PASSWORD` (définie dans `ml/.env`) ne contient que des lettres et
|
||||
des chiffres avant de démarrer le serveur : ce mot de passe est interpolé directement dans l'URI
|
||||
PostgreSQL (`postgresql://mlflow:${MLFLOW_DB_PASSWORD}@...`), un caractère spécial la rendrait
|
||||
invalide sans message d'erreur clair.
|
||||
|
||||
Interface : http://localhost:5000. Entrainer vers ce serveur :
|
||||
|
||||
```
|
||||
uv run python -m enervision_ml.train --csv data/all_sites_combined.csv --mlflow-tracking-uri http://localhost:5000
|
||||
```
|
||||
|
||||
Arreter : `docker compose -f docker-compose.mlflow.yml down` (ajouter `-v` pour effacer aussi les
|
||||
runs et les modeles).
|
||||
|
||||
Pour voir les runs dans l'interface (MLflow 3.x) :
|
||||
|
||||
- Passer le selecteur en haut a gauche sur **Model training**. Le mode **GenAI** affiche des
|
||||
traces LLM et reste vide pour un entrainement LightGBM.
|
||||
- **Runs** liste les entrainements, **Models** les artefacts de modele de chaque run (tous nommes
|
||||
`model`), et **Model registry** les versions numerotees de `consumption-forecast-lightgbm`.
|
||||
|
||||
Limites : l'identifiant PostgreSQL du compose est fixe a `mlflow`, le mot de passe vient de la
|
||||
variable obligatoire `MLFLOW_DB_PASSWORD` (aucune valeur par defaut, le compose refuse de
|
||||
demarrer sans elle) -- ce mot de passe est choisi lors de la copie de `.env.example`, il ne
|
||||
convient donc qu'au developpement local tel quel. Un deploiement partage demandera des secrets,
|
||||
de l'authentification et un stockage d'artefacts dedie (S3/MinIO). Le port 5000 doit etre libre : arreter `mlflow ui` avant,
|
||||
ou changer le mapping (`"5001:5000"`) dans le compose.
|
||||
|
||||
## Scoring
|
||||
|
||||
```bash
|
||||
@@ -127,13 +83,6 @@ section 2 :
|
||||
fichier : `train.py` reecrit toujours le meme chemin a chaque entrainement, donc le nom seul ne
|
||||
distinguerait pas deux versions du modele.
|
||||
|
||||
**Le scoring ne lit pas le Model Registry.** Le fichier charge par `--model` est local
|
||||
(`models/lightgbm-consumption.txt`), independant des versions enregistrees dans le
|
||||
**Model registry** MLflow (`consumption-forecast-lightgbm`). `train.py` enregistre bien une
|
||||
version a chaque entrainement (tracabilite), mais aucun alias (`champion` par exemple) n'est
|
||||
pose, et `enervision_ml.score` ne les lit pas. Le registre sert aujourd'hui a la tracabilite des
|
||||
entrainements, pas au deploiement du modele utilise en scoring.
|
||||
|
||||
En mode `--csv`, rien n'est ecrit en base : c'est un instantane historique fige (l'heure "future"
|
||||
calculee a partir de la fin du CSV n'existe dans aucune base reelle), utile pour valider le
|
||||
pipeline sans base joignable.
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
services:
|
||||
mlflow-db:
|
||||
image: postgres:17
|
||||
environment:
|
||||
POSTGRES_USER: mlflow
|
||||
POSTGRES_PASSWORD: ${MLFLOW_DB_PASSWORD:?definir MLFLOW_DB_PASSWORD dans ml/.env}
|
||||
POSTGRES_DB: mlflow
|
||||
volumes:
|
||||
- mlflow-db-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U mlflow"]
|
||||
interval: 5s
|
||||
retries: 10
|
||||
|
||||
mlflow:
|
||||
build: .
|
||||
depends_on:
|
||||
mlflow-db:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "127.0.0.1:5000:5000"
|
||||
volumes:
|
||||
- mlflow-artifacts:/mlartifacts
|
||||
environment:
|
||||
MLFLOW_DB_PASSWORD: ${MLFLOW_DB_PASSWORD}
|
||||
entrypoint: [ "/bin/sh", "-c" ]
|
||||
command:
|
||||
- exec mlflow server --host 0.0.0.0 --port 5000 --backend-store-uri "postgresql://mlflow:$$MLFLOW_DB_PASSWORD@mlflow-db:5432/mlflow" --artifacts-destination /mlartifacts --serve-artifacts
|
||||
|
||||
volumes:
|
||||
mlflow-db-data:
|
||||
mlflow-artifacts:
|
||||
@@ -181,11 +181,7 @@ def _log_to_mlflow(
|
||||
)
|
||||
mlflow.log_metrics({f"model_{cle}": valeur for cle, valeur in model_metrics.items()})
|
||||
mlflow.log_metrics({f"baseline_{cle}": valeur for cle, valeur in baseline_metrics.items()})
|
||||
mlflow.lightgbm.log_model(
|
||||
booster,
|
||||
name="model",
|
||||
registered_model_name="consumption-forecast-lightgbm",
|
||||
)
|
||||
mlflow.lightgbm.log_model(booster, name="model")
|
||||
mlflow.log_artifact(str(model_output))
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
from enervision_ml.features import TARGET_COLUMN, build_features, feature_columns
|
||||
from enervision_ml.train import chronological_split, prepare_dataset, train
|
||||
@@ -75,19 +74,3 @@ def test_train_runs_end_to_end_on_synthetic_data_and_beats_a_dummy_baseline(
|
||||
assert model_metrics["n_observations"] > 0
|
||||
assert model_metrics["mae"] >= 0
|
||||
assert baseline_metrics["n_observations"] == model_metrics["n_observations"]
|
||||
assert model_metrics["mae"] < baseline_metrics["mae"]
|
||||
|
||||
|
||||
def test_train_raises_when_the_validation_window_is_empty(tmp_path: Path) -> None:
|
||||
depart = datetime(2026, 1, 1, tzinfo=UTC)
|
||||
frame = make_frame("site-a", heures=50, depart=depart) # trop court pour un lag de 168h
|
||||
csv_path = tmp_path / "trop_court.csv"
|
||||
frame.to_csv(csv_path, index=False)
|
||||
|
||||
with pytest.raises(ValueError, match="Fenetre d'entrainement ou de validation vide"):
|
||||
train(
|
||||
csv_path=csv_path,
|
||||
model_output=tmp_path / "model.txt",
|
||||
test_fraction=0.2,
|
||||
tracking_uri=f"sqlite:///{tmp_path / 'mlflow.db'}",
|
||||
)
|
||||
|
||||
@@ -47,13 +47,15 @@ preparer() {
|
||||
fi
|
||||
|
||||
if [[ ! -f "$dossier/.env" ]]; then
|
||||
local brouillon="$dossier/.env.brouillon" oubliees
|
||||
sed -e "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$(secret)|" \
|
||||
-e "s|^POSTGRES_PORT=.*|POSTGRES_PORT=$port_pg|" \
|
||||
-e "s|^APP_SECRET_KEY=.*|APP_SECRET_KEY=$(secret)|" \
|
||||
-e "s|^MAILPIT_UI_PORT=.*|MAILPIT_UI_PORT=$port_mailpit|" \
|
||||
-e "s|^AIRFLOW_PORT=.*|AIRFLOW_PORT=$port_airflow|" \
|
||||
-e "s|^AIRFLOW_FERNET_KEY=.*|AIRFLOW_FERNET_KEY=$(fernet)|" \
|
||||
-e "s|^AIRFLOW_WEBSERVER_SECRET_KEY=.*|AIRFLOW_WEBSERVER_SECRET_KEY=$(secret)|" \
|
||||
-e "s|^AIRFLOW_API_SECRET_KEY=.*|AIRFLOW_API_SECRET_KEY=$(secret)|" \
|
||||
-e "s|^AIRFLOW_JWT_SECRET=.*|AIRFLOW_JWT_SECRET=$(secret)|" \
|
||||
-e "s|^AIRFLOW_ADMIN_PASSWORD=.*|AIRFLOW_ADMIN_PASSWORD=$(secret | cut -c1-20)|" \
|
||||
-e "s|^AIRFLOW_APP_SECRET_KEY=.*|AIRFLOW_APP_SECRET_KEY=$(secret)|" \
|
||||
-e "s|^PUBLIC_HOST=.*|PUBLIC_HOST=$hote|" \
|
||||
@@ -61,13 +63,21 @@ preparer() {
|
||||
-e "s|^COMPOSE_PROJECT_NAME=.*|COMPOSE_PROJECT_NAME=enervision-$env|" \
|
||||
-e "s|^PROXY_HTTP_PORT=.*|PROXY_HTTP_PORT=$port_http|" \
|
||||
-e "s|^PROXY_HTTPS_PORT=.*|PROXY_HTTPS_PORT=$port_https|" \
|
||||
"$dossier/.env.example" > "$dossier/.env"
|
||||
"$dossier/.env.example" > "$brouillon"
|
||||
# Branche antérieure à l'ADR 0009 : ces clés manquent alors dans .env.example.
|
||||
for cle in "COMPOSE_PROJECT_NAME=enervision-$env" "PUBLIC_ORIGIN=$origine" \
|
||||
"PROXY_HTTP_PORT=$port_http" "PROXY_HTTPS_PORT=$port_https"; do
|
||||
grep -q "^${cle%%=*}=" "$dossier/.env" || echo "$cle" >> "$dossier/.env"
|
||||
grep -q "^${cle%%=*}=" "$brouillon" || echo "$cle" >> "$brouillon"
|
||||
done
|
||||
chmod 600 "$dossier/.env"
|
||||
# Piège : une clé renommée en amont garde sa valeur d'exemple, que le `:?` du compose ne
|
||||
# voit pas puisqu'elle n'est pas vide. Cas vécu : AIRFLOW_WEBSERVER_SECRET_KEY, Airflow 3.
|
||||
oubliees="$(grep '=change_me$' "$brouillon" | grep -v '^APP_MOCK_API_' | cut -d= -f1 | tr '\n' ' ' || true)"
|
||||
if [[ -n "$oubliees" ]]; then
|
||||
rm -f "$brouillon"
|
||||
erreur "$env : secrets non générés, .env non écrit : $oubliees"
|
||||
fi
|
||||
chmod 600 "$brouillon"
|
||||
mv "$brouillon" "$dossier/.env"
|
||||
echo "$env : .env généré. Reste à renseigner APP_MOCK_API_USERNAME et APP_MOCK_API_PASSWORD."
|
||||
fi
|
||||
|
||||
|
||||
Reference in New Issue
Block a user