diff --git a/README.md b/README.md index 3275579..d5b2908 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,12 @@ SEICHE_ADMIN_USERNAME=admin SEICHE_ADMIN_PASSWORD='<≥12 caractères>' pnpm set Puis ouvrir http://localhost:5173 et se connecter. -## Démarrage en production (Docker) +## Production (LXC Proxmox + nginx-proxy-manager) + +Déploiement retenu (REPRISE.md §5 q12) : **LXC sur Proxmox** (Docker + compose dedans), TLS terminé par **nginx-proxy-manager** (Let's Encrypt). ```bash +# Dans le LXC : docker compose up -d --build # Premier compte (dans le conteneur, base vide) : @@ -38,7 +41,11 @@ docker compose exec seiche sh -c \ "SEICHE_ADMIN_USERNAME=admin SEICHE_ADMIN_PASSWORD='<≥12 caractères>' pnpm setup" ``` -Le conteneur écoute `8787` (API + front buildé, servi par Fastify). La base SQLite persiste dans le volume `seiche-data` (`/data`). Healthcheck : `GET /api/health`. +Puis dans NPM : **Add Site** → hostname (ex. `seiche.mondomaine.fr`), **Proxy to** `IP_DU_LXC:8787`, **Let's Encrypt** activé. + +Le compose active `SEICHE_COOKIE_SECURE=1` + `SEICHE_TRUST_PROXY=1` (TLS + `X-Forwarded-For` derrière NPM). **Sans proxy TLS** (test Docker en LAN) : passer ces deux variables à `0`, sinon les cookies `Secure` ne sont pas envoyés en HTTP. + +Le conteneur écoute `0.0.0.0:8787` (API + front buildé, servi par Fastify). La base SQLite persiste dans le volume `seiche-data` (`/data`). Healthcheck : `GET /api/health`. ## Variables d'environnement @@ -69,7 +76,7 @@ Snapshot cohérente **en ligne** (`VACUUM INTO` dans le conteneur, le serveur co - **Rate-limit** sur `/login` (anti brute-force, par IP client). - Session : cookie **httpOnly + SameSite=Lax**, durée limitée, révocation au logout ; **CSRF** (double-submit) sur toutes les mutations. - Secrets uniquement en variables d'environnement ; SQLite hors de l'image (volume + backup). -- **LAN sans TLS = mots de passe en clair sur le réseau.** À trancher (REPRISE.md §5 q4) : Caddy/Tailscale ou HTTP assumé. Derrière un proxy TLS : `SEICHE_COOKIE_SECURE=1` + `SEICHE_TRUST_PROXY=1`. +- **TLS** : en prod derrière nginx-proxy-manager (Let's Encrypt) → cookies `Secure` + `trustProxy` (déjà dans le compose). En dev LAN sans proxy : HTTP (mots de passe en clair, dév uniquement). - API minimale : `/api/health` public, tout le reste protégé. ## Structure diff --git a/REPRISE.md b/REPRISE.md index dc1d4c7..9200d99 100644 --- a/REPRISE.md +++ b/REPRISE.md @@ -1,6 +1,6 @@ # Projet « Seiche » — doc de reprise -> À jour : 2026-08-26 (A1–D4 + proxy + READMEs ✅, git) · Statut : **stack complète validée** : serveur 30 tests, front 3 tests + build, Docker build+up, admin créé, `master` poussé sur Gitea · Reste : TLS LAN + VPS (décision §5) +> À jour : 2026-08-26 (A1–D4 + proxy + READMEs + **déploiement LXC/NPM ✅**, git) · Statut : **stack complète validée** : serveur 30 tests, front 3 tests + build, Docker build+up, admin créé, `master` poussé sur Gitea · Reste : exécution du déploiement (LXC + règle NPM + domaine) = infra Nicolas (§5 q4/q12) > Mode d'emploi : doc auto-suffisant. En reprise avec un assistant IA : lui faire lire ce fichier en entier, traiter le §5, puis **planifier les tâches restantes une par une** (§9 + §11). > Mode de travail imposé par Nicolas : **petites tâches, planifier avant d'exécuter, ne jamais lancer de run autonome**. @@ -35,7 +35,7 @@ Appli web auto-hébergée, **réseau local uniquement, mono-utilisateur** (Nicol 5. Secrets **uniquement en variables d'env** (`.env` hors repo, `.env.example` versionné). 6. Logs **pino** structurés : jamais de mot de passe / token / cookie dans les logs. 7. SQLite **hors de l'image** : volume nommé `/data/app.db` + **script de backup** testé. -8. LAN sans TLS = mots de passe en clair sur le réseau → à trancher (§5 q4) : Caddy self-signed, Tailscale, ou HTTP assumé. +8. TLS : en prod derrière nginx-proxy-manager (Let's Encrypt, q4/q12 §5) ; dev LAN sans proxy = mots de passe en clair (acceptable en dev uniquement). 9. Dépendances : lockfile figé en prod, `npm audit` au rythme des maj. 10. Surface API minimale, pas de debug en prod. @@ -45,13 +45,13 @@ Appli web auto-hébergée, **réseau local uniquement, mono-utilisateur** (Nicol | 1 | **Quel service externe** ? URL, doc API, mode d'auth (token statique / OAuth / Basic ?) | Client isolé `server/src/external/` (adaptateurs), config env, mock en dev | | 2 | **Périmètre concret du MVP** ? (lister X, modifier Y, déclencher Z, logs…) | 1 CRUD exemple + health | | 3 | Un seul compte, ou plusieurs possibles ? | Schéma multi-comptes, 1 compte créé au 1er boot | -| 4 | **TLS en LAN** ? (Caddy / Tailscale / HTTP accepté) | HTTP + risque documenté dans le README | +| 4 | ✅ **TLS en LAN** (résolu 2026-08-26) : nginx-proxy-manager (Let's Encrypt) en prod ; dev LAN sans proxy = HTTP accepté (risque dév documenté). | — | | 5 | UI Tailwind + shadcn/ui, interface en français ? | Oui / FR | | 6 | Persistance : volume nommé + backup régulier OK ? | Oui | | 7 | Node 22 LTS dans les conteneurs ? | Oui | | 8 | ✅ **Docker opérationnel** (résolu 2026-08-23) : daemon 29.7.2 démarré (Arch Linux), compose 5.5.0 (plugin + standalone). | — | | 9 | ✅ **Gitea opérationnel** (2026-08-25) : remote SSH `gitea-lab:nicolas/seiche`, compte collaborateur `bot-claude` (clé `id_ed25519_gitea_bot_claude`), `master` poussé, tracking OK. | — | -| 12 | **VPS / mise en prod** : VPS non provisionné au 2026-08-25. Où ? Image ? Domaine/TLS ? | Développer en LAN ; prod = 1 tâche dédiée après décision | +| 12 | ✅ **VPS / mise en prod** (résolu 2026-08-26) : pas de VPS — **LXC Proxmox** (Docker + compose dans le LXC), TLS via **nginx-proxy-manager** (Let's Encrypt, déjà fonctionnel). | — | ## 6. Architecture - **1 conteneur = API + front prébuildé** servi par Fastify → zéro CORS, 1 port, déploiement simple. @@ -131,9 +131,13 @@ seiche/ | Git + Gitea | **✅** : `git init` `master`, commit `581f7e1` (69 fichiers) poussé sur `gitea-lab:nicolas/seiche` (compte `bot-claude`, clé `id_ed25519_gitea_bot_claude`). `.gitignore` corrigé (`.env` uniquement, pas `.env.example`). | | E4 : Proxy (2026-08-26) | **✅** : `SEICHE_TRUST_PROXY` (Fastify `trustProxy` → rate-limit par IP client derrière proxy) + `SEICHE_COOKIE_SECURE` (cookies Secure derrière TLS). 4 tests (`proxy.test.ts`) : 6 IPs distinctes limitées séparément, XFF ignoré par défaut, Secure on/off. Suite : 30/30. Session précédente interrompue sur cette tâche ; finalisée et commitée. | | E1 : READMEs (2026-08-26) | **✅** : README racine (démarrage dev/Docker, env, backup, sécurité) + `server/README.md` (scripts, migrations au boot, structure, API) + `web/README.md` (scripts, structure, contrat API). Corrections doc : port dev Vite = 5173 (pas 5199), health = `/api/health` (pas `/api/v1/health`). | +| F1 : Déploiement (2026-08-26) | **✅ décision + config** : prod = **LXC Proxmox** (Docker + compose dans le LXC), TLS via **nginx-proxy-manager** (Let's Encrypt, déjà fonctionnel). `docker-compose.yml` : `SEICHE_COOKIE_SECURE=1` + `SEICHE_TRUST_PROXY=1` (variables E4 enfin câblées). README : section « Production (LXC + NPM) ». Reste côté Nicolas : créer le LXC, règle NPM (hostname + `IP_LXC:8787`), domaine/certificat. | **Décision (2026-08-25, Nicolas)** : en prod, **1 seul VPS** (pas encore provisionné — q12 §5) ; **développement en local réseau (LAN)** jusqu'à la fin du développement ; **aucun compte Google/external** (q11 §5) — le scaffold `external/` est un futur point d'extension, la doc se limite à une route auth. -**Reste (E)** : READMEs racine/server/web (E1), TLS LAN + VPS (après décision §5). + +**Décision (2026-08-26, Nicolas, remplace « 1 VPS »)** : prod = **auto-hébergement LXC Proxmox** (Docker dans le LXC, compose), TLS par **nginx-proxy-manager** (Let's Encrypt, déjà en place). Exécution infra (LXC, règle NPM, domaine) = côté Nicolas. + +**Reste** : READMEs (E1) ✅, décision + config TLS/VPS (F1) ✅. Exécution du déploiement = infra Nicolas (LXC Proxmox, règle NPM, domaine). ## 11. État du repo — inventaire détaillé (2026-08-23) diff --git a/docker-compose.yml b/docker-compose.yml index 3815b89..3f7b8c8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,12 @@ services: volumes: - seiche-data:/data restart: unless-stopped + # Déploiement : LXC Proxmox derrière nginx-proxy-manager (TLS Let's Encrypt). + # NPM termine le TLS et ajoute X-Forwarded-For → cookies Secure + IP client réelle. + # Sans proxy TLS (test Docker en LAN) : passer ces deux valeurs à "0". + environment: + SEICHE_COOKIE_SECURE: "1" + SEICHE_TRUST_PROXY: "1" # Variables d'option (toutes ont des valeurs par défaut dans server/src/config.ts) : # SEICHE_EXTERNAL_ADAPTER, SEICHE_EXTERNAL_BASE_URL, SEICHE_EXTERNAL_API_KEY, # SEICHE_PORT, SEICHE_SESSION_TTL_MS, ...