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).

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.

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).

# 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 (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).
  • 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
S
Description
No description provided
Readme
180 KiB
Languages
TypeScript 94.2%
Shell 2.8%
Dockerfile 2.5%
HTML 0.5%