Le SPA appelle /api/v1 en relatif et rien ne routait cet appel vers l'API une fois en conteneur. Le cookie de rafraîchissement prend le préfixe __Secure- dès que APP_ENV sort de local, donc sans HTTPS il n'était jamais posé et l'authentification ne survivait pas à un rechargement de page. Un service proxy, image officielle nginx dont la configuration est montée en volume, devient le seul composant publié : 80 redirige vers 443 et sert le défi ACME, 443 termine le TLS, sert le SPA sur / et l'API sur /api/ sous la même origine, pose HSTS et CSP que l'application refuse délibérément de poser, et ajoute une limitation de débit au frontal. Backend et frontend ne sont plus publiés, la base et l'interface Mailpit sont ramenées sur la boucle locale. nginx lit toujours les deux mêmes fichiers de certificat : seule leur fabrication varie, script openssl pour la démonstration, deploy-hook certbot le jour où un domaine public existera. Le chemin ACME est livré et documenté, pas exercé : sur une IP privée le défi HTTP-01 ne peut pas aboutir.
139 lines
5.5 KiB
Makefile
139 lines
5.5 KiB
Makefile
BACKEND := apps/backend
|
|
FRONTEND := apps/frontend
|
|
ML := ml
|
|
COMPOSE_PROD := docker compose -f docker-compose.yml -f docker-compose.prod.yml
|
|
|
|
# Piège : sans `export`, une valeur passée en ligne de commande n'atteindrait pas docker compose.
|
|
# Le `ifdef` évite d'exporter une valeur vide, qui masquerait alors celle du fichier `.env`.
|
|
ifdef PUBLIC_HOST
|
|
export PUBLIC_HOST
|
|
endif
|
|
ifdef ACME_EMAIL
|
|
export ACME_EMAIL
|
|
endif
|
|
|
|
.DEFAULT_GOAL := help
|
|
.PHONY: help install install-backend install-frontend install-ml dev dev-backend dev-frontend \
|
|
lint format typecheck test test-cov test-integration check \
|
|
openapi docker-build db-up db-down db-reset db-logs db-psql migrate bootstrap-admin \
|
|
ml-lint ml-typecheck ml-test ml-check ml-train ml-score \
|
|
tls-selfsigned tls-acme tls-renew stack-up stack-down stack-logs
|
|
|
|
help: ## Liste les cibles disponibles
|
|
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-16s\033[0m %s\n", $$1, $$2}'
|
|
|
|
install: install-backend install-frontend install-ml ## Installe les dépendances backend, frontend et ML
|
|
|
|
install-backend: ## Installe les dépendances du backend
|
|
cd $(BACKEND) && uv sync --all-groups
|
|
|
|
install-frontend: ## Installe les dépendances du frontend
|
|
cd $(FRONTEND) && npm ci
|
|
|
|
install-ml: ## Installe les dépendances du pipeline ML
|
|
cd $(ML) && uv sync --all-groups
|
|
|
|
dev: ## Lance toute la stack (backend + frontend) en rechargement à chaud
|
|
@trap 'kill 0' EXIT INT TERM; \
|
|
$(MAKE) --no-print-directory dev-backend & \
|
|
$(MAKE) --no-print-directory dev-frontend & \
|
|
wait
|
|
|
|
dev-backend: ## Lance l'API seule en rechargement à chaud
|
|
@echo "backend -> http://localhost:8000 (docs sur /docs)"
|
|
cd $(BACKEND) && uv run uvicorn app.main:create_app --factory --reload --host 0.0.0.0 --port 8000
|
|
|
|
dev-frontend: ## Lance le frontend seul en rechargement à chaud
|
|
@echo "frontend -> http://localhost:4200"
|
|
cd $(FRONTEND) && npm start
|
|
|
|
lint: ## Analyse statique du backend
|
|
cd $(BACKEND) && uv run ruff check .
|
|
|
|
format: ## Formate et corrige le backend
|
|
cd $(BACKEND) && uv run ruff format . && uv run ruff check --fix .
|
|
|
|
typecheck: ## Vérifie le typage du backend
|
|
cd $(BACKEND) && uv run mypy app
|
|
|
|
test: ## Exécute les tests backend ne demandant pas de base
|
|
cd $(BACKEND) && uv run pytest --cov-fail-under=85
|
|
|
|
test-cov: ## Rapports de couverture HTML et XML, plus les résultats au format JUnit
|
|
cd $(BACKEND) && uv run pytest --cov-fail-under=85 --cov-report=html \
|
|
--cov-report=xml --junitxml=test-results/junit.xml
|
|
|
|
test-integration: ## Exécute les tests exigeant une base joignable
|
|
cd $(BACKEND) && uv run pytest -m integration
|
|
|
|
check: lint typecheck test ## Chaîne de vérification complète
|
|
|
|
openapi: ## Régénère apps/backend/openapi.json depuis les routes déclarées
|
|
cd $(BACKEND) && uv run python -m app.cli export-openapi
|
|
|
|
ml-lint: ## Analyse statique du pipeline ML
|
|
cd $(ML) && uv run ruff check .
|
|
|
|
ml-typecheck: ## Vérifie le typage du pipeline ML
|
|
cd $(ML) && uv run mypy enervision_ml tests
|
|
|
|
ml-test: ## Exécute les tests du pipeline ML (donnees synthetiques, sans base ni serveur MLflow)
|
|
cd $(ML) && uv run pytest
|
|
|
|
ml-check: ml-lint ml-typecheck ml-test ## Chaîne de vérification complète du pipeline ML
|
|
|
|
ml-train: ## Entraine le modele LightGBM. CSV=chemin optionnel, sinon lit ML_DATABASE_URL
|
|
cd $(ML) && uv run python -m enervision_ml.train $(if $(CSV),--csv $(CSV),)
|
|
|
|
ml-score: ## Score le prochain pas horaire et l'ecrit dans `prediction`. CSV=chemin optionnel
|
|
cd $(ML) && uv run python -m enervision_ml.score $(if $(CSV),--csv $(CSV),)
|
|
|
|
docker-build: ## Construit l'image du backend
|
|
docker build -t enervision-backend:local $(BACKEND)
|
|
|
|
tls-selfsigned: ## Génère le certificat de démonstration. PUBLIC_HOST=..., FORCE=1 pour écraser
|
|
PUBLIC_HOST=$${PUBLIC_HOST:-enervision.local} ./scripts/tls-selfsigned.sh $(if $(FORCE),--force,)
|
|
|
|
stack-up: ## Démarre la stack complète derrière le reverse proxy (80/443). PUBLIC_HOST=... requis
|
|
@test -f infra/proxy/tls/fullchain.pem \
|
|
|| { echo "Aucun certificat dans infra/proxy/tls. Lancer d'abord make tls-selfsigned"; exit 1; }
|
|
$(COMPOSE_PROD) up -d --build
|
|
|
|
stack-down: ## Arrête la stack complète en conservant les données
|
|
$(COMPOSE_PROD) stop
|
|
|
|
stack-logs: ## Suit les journaux du reverse proxy
|
|
$(COMPOSE_PROD) logs -f proxy
|
|
|
|
tls-acme: ## Demande un certificat Let's Encrypt. PUBLIC_HOST et ACME_EMAIL requis
|
|
$(COMPOSE_PROD) --profile acme run --rm certbot certonly --webroot -w /var/www/certbot \
|
|
-d $${PUBLIC_HOST:?PUBLIC_HOST=... requis} \
|
|
--email $${ACME_EMAIL:?ACME_EMAIL=... requis} \
|
|
--agree-tos --no-eff-email --deploy-hook /deploy-hook.sh
|
|
$(COMPOSE_PROD) exec proxy nginx -s reload
|
|
|
|
tls-renew: ## Renouvelle les certificats Let's Encrypt et recharge le proxy
|
|
$(COMPOSE_PROD) --profile acme run --rm certbot renew --deploy-hook /deploy-hook.sh
|
|
$(COMPOSE_PROD) exec proxy nginx -s reload
|
|
|
|
db-up: ## Démarre la base PostgreSQL TimescaleDB
|
|
docker compose up -d db
|
|
|
|
db-down: ## Arrête la base en conservant ses données
|
|
docker compose stop db
|
|
|
|
db-reset: ## Détruit la base et rejoue db/init
|
|
docker compose down -v && docker compose up -d db
|
|
|
|
db-logs: ## Suit les journaux de la base
|
|
docker compose logs -f db
|
|
|
|
db-psql: ## Ouvre une session psql sur la base applicative
|
|
docker compose exec db psql -U $${POSTGRES_USER:-enervision} -d $${POSTGRES_DB:-enervision}
|
|
|
|
migrate: ## Applique les migrations Alembic
|
|
cd $(BACKEND) && uv run alembic upgrade head
|
|
|
|
bootstrap-admin: ## Crée le premier administrateur, mot de passe saisi au clavier
|
|
cd $(BACKEND) && uv run python -m app.cli create-admin --email $${EMAIL:?EMAIL=... requis}
|