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
- Détail backend : server/README.md · Détail front : web/README.md
Prérequis
- Node ≥ 22, pnpm (workspace)
- Docker + compose (pour la prod)
Démarrage en développement
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) :
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)
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 (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
./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/healthpublic, 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