docs: fondation projet (CLAUDE.md, design system, spec + plan jalon 1)
Ignore les dépôts de référence imbriqués (linux-update-dashboard, nas-ops). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,179 @@
|
||||
# Jalon 1 — Tranche verticale APT — Design
|
||||
|
||||
> Spec du premier jalon de la webapp de mise à jour distante Linux.
|
||||
> Statut : **validé** (2026-06-04). Langue de travail : français.
|
||||
> Voir aussi : `CLAUDE.md`, `deep-research-report(7).md` (étude d'architecture), `ajout.md` (volet Hermes / MCP).
|
||||
|
||||
## Objectif
|
||||
|
||||
Construire la première tranche verticale qui valide toute l'ossature de la plateforme
|
||||
(UI ↔ API ↔ worker ↔ SSH ↔ JSON canonique ↔ rapport) sur le cas le plus simple :
|
||||
|
||||
> ajouter une machine Debian/Ubuntu → tester la connexion → rafraîchir les mises à jour APT
|
||||
> en tâche de fond → afficher les paquets dans une tuile → déclencher `full-upgrade` (ou `reboot`)
|
||||
> manuellement avec terminal live → archiver un rapport Markdown + log brut.
|
||||
|
||||
## Décisions verrouillées
|
||||
|
||||
| Sujet | Décision |
|
||||
|-------|----------|
|
||||
| Périmètre | Tranche verticale APT, Debian/Ubuntu, 1 machine à la fois |
|
||||
| Stockage | SQLite via Drizzle ORM |
|
||||
| File de jobs | In-process (croner / worker interne), pas de pg-boss/BullMQ |
|
||||
| Auth SSH | Mot de passe (login + password + sudo password), chiffré au repos |
|
||||
| Déploiement | Docker, clé maître de chiffrement via variable d'environnement |
|
||||
| Auth webapp | Aucune au MVP (réseau de confiance derrière reverse proxy / VPN) |
|
||||
| Exécution | Templates shell versionnés `.sh.tpl` (Mustache), esprit `nas-ops` |
|
||||
| Architecture | API headless agnostique du frontend (réutilisable par TUI / MCP / Hermes) |
|
||||
| Frontend | React 19 + Vite + design system Gruvbox seventies (`design_system/`) |
|
||||
| Backend | Hono + TypeScript |
|
||||
| Terminal live | WebSocket + xterm.js |
|
||||
| Packaging code | Mono-package pnpm (client + server, un seul `package.json`) |
|
||||
| Volet Hermes | Stub visuel uniquement (« à venir ») |
|
||||
| Actions | `apt_full_upgrade` **et** `reboot` |
|
||||
|
||||
### Hors périmètre de ce jalon (jalons suivants)
|
||||
|
||||
Docker Compose ; profils Proxmox / Raspberry Pi OS ; serveur MCP ; intelligence Hermes ;
|
||||
authentification de la webapp ; clés SSH ; déduplication multi-machines ; apt-cacher-ng persistant.
|
||||
|
||||
## Contrainte d'architecture transverse
|
||||
|
||||
Le **cœur métier (API) est headless et agnostique du frontend**. Le client web n'est qu'un
|
||||
consommateur parmi d'autres ; un futur TUI, un bot de messagerie, le serveur MCP et Hermes
|
||||
taperont sur la **même API interne**. Conséquence : **aucune logique métier dans le client**.
|
||||
Les secrets et l'exécution restent côté backend ; les clients (web, TUI, bot) proposent et
|
||||
déclenchent uniquement des actions autorisées.
|
||||
|
||||
## Structure du projet (mono-package pnpm, racine du dépôt)
|
||||
|
||||
```
|
||||
system_update/
|
||||
├─ shared/ # types JSON canoniques (snapshot, execution result), partagés client+server
|
||||
├─ server/ # API Hono headless — cœur métier
|
||||
│ ├─ index.ts
|
||||
│ ├─ routes/ # machines, refresh, actions, ws
|
||||
│ ├─ services/ # orchestration update, génération snapshot, rapports
|
||||
│ ├─ ssh/ # wrapper ssh2 (password, sudo -S, exécution détachée)
|
||||
│ ├─ templates/ # rendu Mustache + parsing JSON
|
||||
│ ├─ crypto/ # chiffrement AES-256-GCM des credentials
|
||||
│ ├─ jobs/ # worker in-process (croner)
|
||||
│ └─ db/ # schéma + migrations Drizzle (SQLite)
|
||||
├─ client/ # React 19 + Vite + design system
|
||||
│ └─ src/{components,panels,features,lib}
|
||||
├─ templates/apt/ # check.sh.tpl, full-upgrade.sh.tpl, reboot-check.sh.tpl
|
||||
├─ reports/ # rapports .md + logs bruts (volume Docker)
|
||||
├─ docker/ # Dockerfile + docker-compose.yml
|
||||
└─ docs/superpowers/specs/
|
||||
```
|
||||
|
||||
Le design system existant (`design_system/tokens/*`, `design_system/components/ui-kit.jsx`)
|
||||
est consommé/porté dans `client/src/components` : `tokens.css` chargé globalement,
|
||||
`data-theme` posé sur `<html>`.
|
||||
|
||||
## Modèle de données (SQLite / Drizzle)
|
||||
|
||||
- **machines** : `id, name, hostname, port, os_family, username, enc_password, enc_sudo_password,
|
||||
apt_proxy_mode, apt_proxy_url, status, last_checked_at, created_at`
|
||||
- **snapshots** : `id, machine_id, checked_at, status, payload_json`
|
||||
(le *update availability snapshot* canonique du rapport)
|
||||
- **executions** : `id, machine_id, action, mode, started_at, finished_at, status, result_json,
|
||||
report_path, raw_log_path`
|
||||
|
||||
Les jobs de fond sont suivis en mémoire + reflétés via `machine.status` ; pas de table jobs
|
||||
dédiée au MVP.
|
||||
|
||||
### Secrets
|
||||
|
||||
Chiffrement **AES-256-GCM**. Clé maître lue depuis `SU_MASTER_KEY` (env). Les credentials sont
|
||||
déchiffrés uniquement en mémoire au moment de l'exécution SSH. **Jamais loggués, jamais
|
||||
sérialisés vers l'API, l'UI ou (futur) le MCP.**
|
||||
|
||||
## Couche SSH + templates (le moteur)
|
||||
|
||||
- `ssh2` en authentification mot de passe.
|
||||
- Commandes enveloppées `sh -c`, avec `LC_ALL=C` et un `PATH` minimal pour stabiliser les sorties
|
||||
(pattern repris de `linux-update-dashboard`).
|
||||
- sudo via `sudo -S` (mot de passe sudo poussé sur stdin).
|
||||
- **Opérations longues détachées** : `nohup` + fichier de log + fichier d'exit-code, pour survivre
|
||||
à une coupure SSH.
|
||||
- Les templates `.sh.tpl` (Mustache) **produisent eux-mêmes le JSON** (esprit `nas-ops`) :
|
||||
- `check.sh.tpl` → `apt-get update -qq` puis simulation `full-upgrade`, émet
|
||||
`{ count, packages[], reboot_required }`.
|
||||
- `full-upgrade.sh.tpl` → applique `apt-get full-upgrade` (options dpkg défensives), émet le
|
||||
*execution result*.
|
||||
- `reboot-check.sh.tpl` → état `reboot_required` / reboot.
|
||||
- Le backend rend le template avec les variables par machine (proxy APT, etc.), pousse en SSH,
|
||||
parse la **dernière ligne JSON**, et **streame** la sortie lisible vers le terminal via WebSocket.
|
||||
|
||||
## API headless (contrat réutilisable par TUI / MCP / Hermes)
|
||||
|
||||
```
|
||||
GET /api/machines
|
||||
POST /api/machines # crée + test-connection
|
||||
POST /api/machines/:id/test-connection
|
||||
GET /api/machines/:id/snapshot
|
||||
POST /api/machines/:id/refresh # déclenche un job de fond
|
||||
POST /api/machines/:id/actions # { action: "apt_full_upgrade" | "reboot" }
|
||||
GET /api/machines/:id/executions
|
||||
GET /api/machines/:id/executions/:execId
|
||||
GET /api/machines/:id/executions/:execId/report
|
||||
WS /api/ws/machines/:id/output # flux live + buffer rejouable
|
||||
```
|
||||
|
||||
## Flux fonctionnels
|
||||
|
||||
1. **Ajout machine** — `POST /api/machines` : connexion SSH de test + détection OS
|
||||
(`lsb_release` / `/etc/os-release`), chiffrement et stockage des credentials, création de la
|
||||
tuile avec état initial.
|
||||
2. **Refresh (tâche de fond)** — le worker rend `check.sh.tpl`, exécute en SSH, parse le snapshot
|
||||
JSON, le stocke ; la tuile UI se met à jour (polling ou WS).
|
||||
3. **Upgrade (manuel)** — `POST /api/machines/:id/actions { action: "apt_full_upgrade" }` : job
|
||||
détaché, sortie streamée sur `WS /api/ws/machines/:id/output` ; à la fin, parsing du résultat,
|
||||
écriture du rapport `.md` + log brut, persistance de l'exécution.
|
||||
4. **Reboot (manuel)** — même mécanique, action `reboot`.
|
||||
5. **Rapport** — `GET …/report` renvoie le Markdown archivé.
|
||||
|
||||
## Frontend (design system Gruvbox, layout 3 volets)
|
||||
|
||||
Layout `Resizable` :
|
||||
- **Gauche** : panneau Hermes (stub « à venir »).
|
||||
- **Centre** : dashboard de tuiles machines + bouton `+` (Popup d'ajout).
|
||||
- **Droite** : terminal `xterm.js` branché sur le WebSocket.
|
||||
|
||||
Composants issus du design system (Button, IconButton, StatusLed, Popup, tuiles `glass`,
|
||||
BatteryGauge pour les compteurs d'updates). Respect strict des règles : pas de hover (pression
|
||||
3D), tooltips sur icônes seules, polices Inter / JetBrains Mono / Share Tech Mono, vérification
|
||||
des deux thèmes (`data-theme`).
|
||||
|
||||
## Gestion d'erreurs & réduction
|
||||
|
||||
- Échec de connexion → `machine.status = error` + message **sans secret**.
|
||||
- Template avec exit ≠ 0 → `execution.status = error`, capture des lignes stderr importantes.
|
||||
- Reconnexion WS → rejoue le buffer circulaire côté serveur.
|
||||
- **Réducteur déterministe APT** construit dès ce jalon : filtre les lignes utiles
|
||||
(`Inst`, `Conf`, `Remv`, `Err`, `E:`, `W:`, `dpkg:`, `reboot-required`) ; le log brut complet
|
||||
reste sur disque. Le JSON canonique reste propre — prépare l'intégration Hermes sans la câbler.
|
||||
|
||||
## Tests (vitest)
|
||||
|
||||
Unitaires :
|
||||
- rendu de template Mustache,
|
||||
- **parser de sortie APT → snapshot JSON** (sur fixtures de sortie capturées),
|
||||
- round-trip chiffrement / déchiffrement des credentials,
|
||||
- réducteur de lignes APT.
|
||||
|
||||
La couche SSH réelle est validée manuellement contre une vraie machine pour ce jalon (pas de
|
||||
mock SSH lourd).
|
||||
|
||||
## Critères d'acceptation
|
||||
|
||||
- [ ] Ajout d'une machine Debian/Ubuntu via le bouton `+`, avec test de connexion réussi.
|
||||
- [ ] Credentials stockés chiffrés ; aucun secret visible en base, API, logs ou UI.
|
||||
- [ ] Refresh de fond produit un snapshot JSON canonique avec compteur + liste de paquets.
|
||||
- [ ] La tuile affiche nom, IP, OS, compteur d'updates, paquets concernés.
|
||||
- [ ] `full-upgrade` déclenché manuellement, sortie visible en direct dans le terminal xterm.js.
|
||||
- [ ] `reboot` déclenchable manuellement.
|
||||
- [ ] Un rapport `.md` + un log brut sont archivés après exécution.
|
||||
- [ ] L'app tourne en Docker avec `SU_MASTER_KEY` en env et volumes pour SQLite + rapports.
|
||||
- [ ] Tests vitest verts (parser, crypto, réducteur, rendu template).
|
||||
Reference in New Issue
Block a user