Files
system_update/docs/superpowers/specs/2026-06-04-jalon1-tranche-verticale-apt-design.md
T
gilles 1e1be7f627 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>
2026-06-05 04:41:30 +02:00

9.1 KiB

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.tplapt-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 machinePOST /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. RapportGET …/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).