batchCooking/specs/environment.md
Nicolas 9a9401ca8d
Some checks failed
CI / lint (push) Successful in 2m17s
CI / build (push) Successful in 2m48s
CI / e2e (push) Failing after 15m19s
CI / test (push) Failing after 22m57s
CI / intent-service-test (push) Failing after 24m1s
docs: reference unique de toutes les variables d'environnement
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>
2026-08-29 21:21:37 +02:00

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.example ne contiennent que des changeme ; les vrais .env sont 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/*/.env
    pnpm --filter api test apps/api/.env.test (chargé à la place de .env via NODE_ENV=test)
    Docker Compose (docker compose …, prod/review) .env à la racine uniquement — Compose injecte le reste en environment:
  • 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 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.testDATABASE_URL doit pointer une base différente de apps/api/.env (la suite TRUNCATE tout avant chaque test ; un garde-fou refuse de tourner si l'URL ne contient ni test ni ci).

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 :

  1. .env — vrais POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB.
  2. apps/api/.envDATABASE_URL avec ces mêmes identifiants ; un JWT_SECRET généré ; le même INTENT_SERVICE_SECRET que…
  3. services/tech-step-intent-service/.env — …ce fichier.
  4. apps/web/.envVITE_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)