Files
system_update/server/routes/postInstall.ts
T
gilles e6f4ae470b feat(post-install): moteur de profils + bootstrap + identité/réseau (tâche 2 SJ-8)
- templates custom/bootstrap-root + identity-network (sortie structurée parsable,
  sauvegardes, échec contrôlé, jamais de coupure réseau sans reconnexion)
- postInstall: registre de manifestes (champs typés + defaults/defaultFrom),
  validateProfileValues + maskSecretValues + buildPostInstallResult (TDD),
  renderProfile/previewProfile (masquage secrets), runPostInstall (SSH)
- execute: RunActionOpts.profileId/values + branche post_install (bloc postInstall)
- action_requests: post_install accepté, payload profileId/values transmis à approve
- routes: GET /profiles, POST .../preview (script masqué + validation),
  POST .../run (action_request si requiresConfirmation, sinon direct)

Champs = formulaire (pas de question SSH interactive) ; secrets jamais sérialisés ;
identity_network exige confirmation. tsc 0 · 101 tests · build OK · boot OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 08:02:32 +02:00

47 lines
2.2 KiB
TypeScript

// server/routes/postInstall.ts
import { Hono } from "hono";
import { PROFILES, previewProfile, validateProfileValues } from "../services/postInstall.js";
import { createActionRequest } from "../services/actionRequests.js";
import { runAction } from "../services/execute.js";
export const postInstallRoutes = new Hono();
// Catalogue des profils (manifestes, sans secret).
postInstallRoutes.get("/profiles", (c) => c.json(Object.values(PROFILES)));
// Preview du script rendu (secrets masqués) + validation des champs.
postInstallRoutes.post("/machines/:id/profiles/:profileId/preview", async (c) => {
const profileId = c.req.param("profileId");
const manifest = PROFILES[profileId];
if (!manifest) return c.json({ error: "Profil inconnu" }, 404);
const { values } = (await c.req.json().catch(() => ({}))) as { values?: Record<string, string | number | boolean> };
const validation = validateProfileValues(manifest, values ?? {});
return c.json({ script: previewProfile(profileId, values ?? {}), validation, requiresConfirmation: manifest.requiresConfirmation });
});
// Exécute un profil : confirmation explicite (action_request) si requise, sinon direct.
postInstallRoutes.post("/machines/:id/profiles/:profileId/run", async (c) => {
const machineId = c.req.param("id");
const profileId = c.req.param("profileId");
const manifest = PROFILES[profileId];
if (!manifest) return c.json({ error: "Profil inconnu" }, 404);
const { values } = (await c.req.json().catch(() => ({}))) as { values?: Record<string, string | number | boolean> };
const validation = validateProfileValues(manifest, values ?? {});
if (!validation.ok) return c.json({ error: "Champs invalides", validation }, 400);
if (manifest.requiresConfirmation) {
const reqRow = createActionRequest({
machineId,
action: "post_install",
summary: `Profil ${manifest.label}`,
payload: { profileId, values: values ?? {} },
});
return c.json({ actionRequest: reqRow, requiresConfirmation: true }, 202);
}
runAction(machineId, "post_install", { profileId, values: values ?? {} }).catch((err) =>
console.error("[post_install]", (err as Error).message),
);
return c.json({ ok: true, action: "post_install", profileId }, 202);
});