Files
seiche/README.md
T

91 lines
4.0 KiB
Markdown

# 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.
## 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) :
docker compose exec seiche sh -c \
"SEICHE_ADMIN_USERNAME=admin SEICHE_ADMIN_PASSWORD='<≥12 caractères>' pnpm setup"
```
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
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).
- **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
```
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
```