Ajoute specs/environment.md : liste et explique chaque variable d'env de l'appli (API, front, service NLP, worker LLM, Postgres, Compose) — obligatoire ou non, defaut, fichier ou la poser, role, et la matrice des secrets partages qui doivent correspondre entre deux bouts. Resume les schemas de validation (apps/api/src/config/env.ts, services/tech-step-llm-worker/src/config.ts, tech-step-intent-service/config.py) qui restent la source de verite, plus les 7 fichiers *.env.example et docker-compose.yml. README : pointeur vers ce document depuis la section Installation, et mention explicite des .env des deux services. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
14 KiB
Variables d'environnement
Référence unique de toutes les variables d'environnement de l'appli : lesquelles poser, dans quel fichier, obligatoire ou non, et à quoi elles servent.
La source de vérité de chaque variable reste son schéma de validation
(apps/api/src/config/env.ts, services/tech-step-llm-worker/src/config.ts,
services/tech-step-intent-service/intent_service/config.py) : au démarrage,
un secret requis manquant fait échouer immédiatement le process plutôt que
de laisser une erreur obscure survenir plus tard. Ce document résume ces
schémas et les relie entre eux.
Principes
-
Aucun secret n'est commité. Les fichiers
*.env.examplene contiennent que deschangeme; les vrais.envsont git-ignorés et à créer par copie. -
Générer un secret (32 caractères minimum) :
node -e "console.log(require('crypto').randomBytes(48).toString('hex'))" -
Deux workflows, deux jeux de fichiers :
Workflow Fichiers lus Dev natif ( pnpm dev:api/pnpm dev:web+ services lancés à la main)apps/api/.env,apps/web/.env,services/*/.envpnpm --filter api testapps/api/.env.test(chargé à la place de.envviaNODE_ENV=test)Docker Compose ( docker compose …, prod/review).envà la racine uniquement — Compose injecte le reste enenvironment: -
Secrets partagés : plusieurs variables portent le même secret des deux côtés d'un lien (API ↔ service). Elles doivent avoir la même valeur — voir Secrets à faire correspondre.
Fichiers .env
| Fichier | Créé par | Utilisé pour | Modèle |
|---|---|---|---|
.env (racine) |
cp .env.example .env |
Docker Compose uniquement (provisionne Postgres + injecte les vars des services) | .env.example |
apps/api/.env |
cp apps/api/.env.example apps/api/.env |
API en dev natif (pnpm dev:api) |
apps/api/.env.example |
apps/api/.env.test |
cp apps/api/.env.test.example apps/api/.env.test |
pnpm --filter api test (base séparée de la dev) |
apps/api/.env.test.example |
apps/web/.env |
cp apps/web/.env.example apps/web/.env |
Front en dev natif — lu au build par Vite | apps/web/.env.example |
services/tech-step-intent-service/.env |
cp …/.env.example …/.env |
Service NLP en dev natif | idem |
services/tech-step-llm-worker/.env |
cp …/.env.example …/.env |
Worker LLM en dev natif (optionnel) | idem |
services/tech-step-llm-worker/.env.test |
cp …/.env.test.example …/.env.test |
Tests Mocha du worker | idem |
Postgres — .env racine (Docker Compose)
Provisionnent le conteneur postgres de docker-compose.yml. En dev natif,
ces valeurs ne sont lues que par Compose pour lancer la base ; l'API, elle,
lit DATABASE_URL dans apps/api/.env (qui doit refléter ces identifiants).
| Variable | Obligatoire | Défaut | Rôle |
|---|---|---|---|
POSTGRES_USER |
oui (Compose refuse de démarrer sans) | — | Utilisateur du conteneur Postgres |
POSTGRES_PASSWORD |
oui | — | Mot de passe |
POSTGRES_DB |
oui | — | Nom de la base |
POSTGRES_PORT |
non | 5432 |
Port hôte exposé. Passer à 5433 si un Postgres natif occupe déjà 5432 (adapter aussi DATABASE_URL) |
API — apps/api/.env (dev natif) / injecté par Compose
Schéma : apps/api/src/config/env.ts.
| Variable | Obligatoire | Défaut | Rôle |
|---|---|---|---|
NODE_ENV |
non | development |
development | test | production. test déclenche des comportements dédiés (coût argon2 réduit, chargement de .env.test). |
PORT |
non | 3000 |
Port d'écoute HTTP de l'API. |
DATABASE_URL |
de fait oui (Prisma en a besoin) | — | Chaîne de connexion Postgres. Doit refléter les identifiants du .env racine. En Compose elle est construite automatiquement et cible l'hôte postgres:5432. |
JWT_SECRET |
oui, sans défaut | — | Signe/vérifie le JWT de session utilisateur. ≥ 32 caractères. |
JWT_EXPIRES_IN |
non | 7d |
Durée de validité du JWT (format jsonwebtoken). |
AUTH_COOKIE_NAME |
non | session |
Nom du cookie httpOnly de session utilisateur. |
CORS_ORIGIN |
non | http://localhost:5173 |
Origine autorisée par CORS — doit correspondre à l'URL de apps/web. En Compose, le front est servi par le même conteneur : pas de CORS cross-origin. |
COOKIE_SECURE |
non | (non posé → NODE_ENV === "production") |
Force l'attribut Secure du cookie de session. Mettre false uniquement si le déploiement est en HTTP nu (sans TLS devant) : sinon le cookie n'est jamais renvoyé et toute requête authentifiée renvoie 401 après un login pourtant réussi. |
FRONTEND_DIST_DIR |
non | — | Chemin absolu vers apps/web/dist à servir avec l'API. Posé uniquement dans l'image Docker de prod ; laissé vide en dev natif (c'est le serveur Vite de pnpm dev:web qui sert le front). |
Lien API ↔ service NLP (tech-step-intent-service)
| Variable | Obligatoire | Défaut | Rôle |
|---|---|---|---|
INTENT_SERVICE_BASE_URL |
non | http://localhost:8000 |
URL du service NLP. Compose la remplace par le nom de service réseau (http://tech-step-intent-service:8000). |
INTENT_SERVICE_SECRET |
oui, sans défaut | — | Secret partagé (header X-Intent-Service-Secret). Dépendance cœur : sans le service NLP joignable, aucune technique de cuisine n'est détectée à l'enregistrement/l'aperçu d'une recette. Doit être identique à celui du service (voir plus bas). |
Surface d'administration /admin/* (optionnelle)
L'UI admin est servie par apps/web sous /admin/* (même origine que le
reste du front — pas de CORS dédié), mais avec une auth totalement
séparée de celle des utilisateurs.
| Variable | Obligatoire | Défaut | Rôle |
|---|---|---|---|
ADMIN_JWT_SECRET |
non (mais /admin/* renvoie 401 tant qu'absent) |
— | Signe/vérifie le JWT de session admin. Doit être ≠ JWT_SECRET pour qu'un token utilisateur ne puisse jamais être rejoué contre /admin/*. ≥ 32 caractères. |
ADMIN_COOKIE_NAME |
non | admin_session |
Nom du cookie httpOnly de session admin — doit différer de AUTH_COOKIE_NAME pour que les deux sessions coexistent dans un même navigateur. |
ADMIN_INITIAL_EMAIL |
non | — | Valeurs lues uniquement par apps/api/src/scripts/create-admin.ts quand ses flags --email / --password / --name sont omis (bootstrap du premier admin). Jamais lues par le serveur. |
ADMIN_INITIAL_PASSWORD |
non | — | idem |
ADMIN_INITIAL_NAME |
non | — | idem |
Lien API ↔ worker LLM (tech-step-llm-worker, optionnel)
| Variable | Obligatoire | Défaut | Rôle |
|---|---|---|---|
INTERNAL_WORKER_SECRET |
non (mais /internal/tech-steps/* renvoie 401 tant qu'absent) |
— | Secret partagé (header X-Internal-Worker-Secret) pour les appels du worker LLM vers l'API. ≥ 32 caractères. Job de fond optionnel : un déploiement qui ne lance pas le worker n'en a pas besoin. Doit être identique à celui du worker. |
Front — apps/web/.env
Vite n'expose au code client que les variables préfixées VITE_, et les
inline au build (pas de lecture à l'exécution).
| Variable | Obligatoire | Défaut | Rôle |
|---|---|---|---|
VITE_API_URL |
non | "" (même origine) |
Base URL de l'API pour fetch (apiClient et adminApiClient). En dev natif : http://localhost:3000. Vide = même origine, correct derrière un reverse-proxy partagé ou dans l'image Docker mono-conteneur. |
(La version affichée dans l'UI (__APP_VERSION__) vient de package.json
via vite.config.ts, ce n'est pas une variable d'environnement.)
Service NLP — services/tech-step-intent-service/.env
Schéma : intent_service/config.py (pydantic-settings). En Docker, Compose
injecte les variables ; en dev natif, elles viennent de ce .env.
| Variable | Obligatoire | Défaut | Rôle |
|---|---|---|---|
INTENT_SERVICE_SECRET |
oui, sans défaut (le service refuse de démarrer sans) | — | Secret attendu sur X-Intent-Service-Secret de chaque requête (sauf GET /health). Doit être identique à INTENT_SERVICE_SECRET côté API. |
LOG_LEVEL |
non | INFO |
Niveau du logging JSON structuré. À INFO, chaque appel /v1/process journalise son input/output (locale, texte, entités, intent, score). |
Le port n'est pas une variable d'env : il est passé à uvicorn en
argument (--port 8000).
Worker LLM — services/tech-step-llm-worker/.env (optionnel)
Schéma : src/config.ts. Nécessaire uniquement hors Docker Compose
(Compose pose lui-même API_BASE_URL / INTERNAL_WORKER_SECRET).
| Variable | Obligatoire | Défaut | Rôle |
|---|---|---|---|
INTERNAL_WORKER_SECRET |
oui, sans défaut | — | Doit être identique à INTERNAL_WORKER_SECRET côté API. ≥ 32 caractères. |
API_BASE_URL |
non | http://app:3000 |
Base URL de l'API. Le défaut est le nom d'hôte Compose du service app ; à surcharger (http://localhost:3000) pour un pnpm dev:api local. |
TECH_STEP_WORKER_CRON |
non | 0 3 * * 0 (dimanche 03:00) |
Expression cron (syntaxe node-cron) de réveil du worker. Valeur provisoire à calibrer une fois déployé. |
TECH_STEP_LLM_MODEL_URI |
non | hf:Qwen/Qwen2.5-1.5B-Instruct-GGUF:Q4_K_M |
URI hf:<repo>:<quant> du modèle GGUF à télécharger. |
TECH_STEP_LLM_MODEL_PATH |
non | — | Chemin local explicite vers un GGUF, court-circuite le téléchargement HF ci-dessus (offline / éviter un download en plein déploiement). |
TECH_STEP_WORKER_LOCALE |
non | fr |
Locale échantillonnée par le job audit-low-confidence. |
TECH_STEP_WORKER_BATCH_LIMIT |
non | 50 |
Borne (?limit=) sur le nombre d'items traités par run — plafonne le coût d'inférence LLM d'une exécution planifiée. |
RUN_ONCE |
non | (non posé → false) |
true = lance les deux jobs une fois puis quitte, au lieu de démarrer la boucle cron (run manuel / CI). Comparaison de chaîne stricte : "false" vaut bien false. |
services/tech-step-llm-worker/.env.test ne contient qu'un
INTERNAL_WORKER_SECRET bidon (les tests du worker mockent tous les appels
HTTP vers l'API) — juste là pour satisfaire le schéma à l'import.
Compose — .env racine, variables spécifiques
| Variable | Obligatoire | Défaut | Rôle |
|---|---|---|---|
APP_PORT |
non | 3000 |
Port hôte du conteneur app (API + front construit) dans docker-compose.yml. |
COOKIE_SECURE |
non | (non posé) | Voir la table API — ${COOKIE_SECURE:-} dans Compose, à mettre à false seulement pour un déploiement HTTP nu. |
TECH_STEP_WORKER_CRON |
non | 0 3 * * 0 |
Passé au service tech-step-llm-worker de Compose. |
Le .env racine porte aussi JWT_SECRET, INTENT_SERVICE_SECRET,
INTERNAL_WORKER_SECRET, ADMIN_JWT_SECRET, ADMIN_INITIAL_* : en Compose,
c'est là qu'ils sont posés (et non dans apps/api/.env, jamais copié dans
l'image).
Secrets à faire correspondre
Un même secret doit porter la même valeur aux deux bouts :
| Secret | Bout A | Bout B | Si absent / divergent |
|---|---|---|---|
INTENT_SERVICE_SECRET |
apps/api/.env |
services/tech-step-intent-service/.env |
Le service NLP refuse chaque requête → plus aucune détection de technique (dépendance cœur). |
INTERNAL_WORKER_SECRET |
apps/api/.env |
services/tech-step-llm-worker/.env |
/internal/tech-steps/* renvoie 401 → le worker LLM ne peut rien faire (job optionnel). |
| Identifiants Postgres | .env racine (POSTGRES_*) |
apps/api/.env (DATABASE_URL) |
P1000 Authentication failed. |
Contraintes supplémentaires :
ADMIN_JWT_SECRET≠JWT_SECRET(isolation des sessions admin/utilisateur).ADMIN_COOKIE_NAME≠AUTH_COOKIE_NAME.apps/api/.env.test→DATABASE_URLdoit pointer une base différente deapps/api/.env(la suiteTRUNCATEtout avant chaque test ; un garde-fou refuse de tourner si l'URL ne contient nitestnici).
Mise en route rapide
Dev natif
cp .env.example .env # Postgres (Compose)
cp apps/api/.env.example apps/api/.env # API
cp apps/web/.env.example apps/web/.env # front
cp services/tech-step-intent-service/.env.example \
services/tech-step-intent-service/.env # service NLP
Puis éditer :
.env— vraisPOSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB.apps/api/.env—DATABASE_URLavec ces mêmes identifiants ; unJWT_SECRETgénéré ; le mêmeINTENT_SERVICE_SECRETque…services/tech-step-intent-service/.env— …ce fichier.apps/web/.env—VITE_API_URL=http://localhost:3000(déjà le défaut du modèle).
Optionnel — pour l'admin : ajouter ADMIN_JWT_SECRET (≥ 32 c, ≠ JWT_SECRET)
dans apps/api/.env, puis créer un admin :
pnpm --filter api exec tsx src/scripts/create-admin.ts \
--email=ops@example.com --password='…' --name='Ops'
Optionnel — pour le worker LLM : ajouter le même INTERNAL_WORKER_SECRET
dans apps/api/.env et services/tech-step-llm-worker/.env.
Tests apps/api
cp apps/api/.env.test.example apps/api/.env.test
Éditer DATABASE_URL → base dédiée (ex. batchcooking_test), même
JWT_SECRET / INTENT_SERVICE_SECRET que d'habitude. Le service NLP doit
tourner. Détails : apps/api/.env.test.example.
Docker Compose
Un seul fichier : .env à la racine. Y renseigner POSTGRES_*, JWT_SECRET,
INTENT_SERVICE_SECRET, et — si besoin — ADMIN_JWT_SECRET,
INTERNAL_WORKER_SECRET, COOKIE_SECURE=false (HTTP nu), APP_PORT.
docker compose up -d postgres # dev : Postgres seul
docker compose up -d --build # stack complète (review/prod)