# Seiche Appli web auto-hébergée, **réseau local uniquement, mono-utilisateur** : gère un service externe via son API REST, protégée par mot de passe. **Stack** : TypeScript strict · Node 22 + Fastify 5 · SQLite (libsql + Drizzle) · Vite + React 19 + Tailwind 4 · 1 conteneur (API + front statique). - Doc de projet / décisions / plan : [REPRISE.md](REPRISE.md) - Détail backend : [server/README.md](server/README.md) · Détail front : [web/README.md](web/README.md) ## Prérequis - Node ≥ 22, pnpm (workspace) - Docker + compose (pour la prod) ## Démarrage en développement ```bash pnpm install pnpm dev:server # terminal 1 — API sur http://localhost:8787 pnpm dev:web # terminal 2 — Vite sur http://localhost:5173 (proxy /api → 8787) ``` Premier compte (nécessaire une seule fois, idempotent) : ```bash SEICHE_ADMIN_USERNAME=admin SEICHE_ADMIN_PASSWORD='<≥12 caractères>' pnpm setup ``` Puis ouvrir http://localhost:5173 et se connecter. ## Démarrage en production (Docker) ```bash docker compose up -d --build # Premier compte (dans le conteneur, base vide) : 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`. ## Variables d'environnement Référent complet : [.env.example](.env.example) (copier dans `.env` pour la dev — **jamais committer le `.env`**). | Variable | Défaut | Rôle | |---|---|---| | `SEICHE_PORT` / `SEICHE_HOST` | `8787` / `127.0.0.1` | Écoute du serveur (Docker force `0.0.0.0`) | | `SEICHE_DATA_DIR` | `./data` | Dossier SQLite (volume `/data` en Docker) | | `SEICHE_SESSION_TTL_MS` | 8 h | Durée de vie d'une session | | `SEICHE_SESSION_ABSOLUTE_TTL_MS` | 7 j | Durée absolue max | | `SEICHE_MAX_LOGIN_ATTEMPTS_PER_MINUTE` | `5` | Rate-limit `/login` par IP | | `SEICHE_COOKIE_SECURE` | `0` | `1` derrière un proxy TLS (cookies `Secure`) | | `SEICHE_TRUST_PROXY` | `0` | `1` derrière un proxy (lit `X-Forwarded-For`) | | `SEICHE_EXTERNAL_*` | `example` | Service REST externe (adaptateur, URL, clé, cache…) | ## Sauvegarde ```bash ./scripts/backup.sh [dir_sortie] # défaut : ./backups/ ``` Snapshot cohérente **en ligne** (`VACUUM INTO` dans le conteneur, le serveur continue de servir) → copie **hors** du volume → vérification de l'en-tête SQLite. Prérequis : conteneur en marche. Penser à cronner et à stocker les `.db` ailleurs que sur la machine (le dossier `backups/` est gitignoré). ## Sécurité — non négociables - Mot de passe **Argon2id** (sel par compte), jamais en clair ni dans les logs. - **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`. - API minimale : `/api/health` public, tout le reste protégé. ## Structure ``` seiche/ ├── server/ # Fastify : routes /api/v1/*, auth, DB, client externe ├── web/ # Vite + React : login, dashboard, client API ├── scripts/ # backup.sh └── docker-compose.yml · Dockerfile · .env.example ```