Files
system_update/server/routes/actions.ts
T
gilles bafb085995 feat(os): profils Proxmox/RPi + machine_probe + proxy persistent (tâche 2 SJ-7)
- templates proxmox/ (update-analyze: dépôts PVE ; full-upgrade) et raspbian/
  (update-analyze: espace disque ; full-upgrade)
- execute résout les actions APT par profil OS (resolveTemplate) → proxmox/
  raspbian si dispo, sinon fallback apt/ (non-régression debian/ubuntu vérifiée)
- machine_probe (lecture seule) : template + parseProbe/proposeCorrections (TDD)
  → propose os_family/machine_kind/virtualization, persiste machine_hardware,
  n'applique jamais auto ; branche execute + allowlist route
- apt_proxy_persistent : ActionType + template idempotent (/etc/apt/apt.conf.d/
  01proxy, backup) + TemplateVars.aptProxyUrl + allowlist route

tsc 0 · 95 tests · build OK · résolution OS vérifiée.

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

53 lines
2.1 KiB
TypeScript

// server/routes/actions.ts
import { Hono } from "hono";
import { readFileSync } from "node:fs";
import { runAction, listExecutions, getExecution } from "../services/execute.js";
import type { ActionType } from "@shared/types.js";
export const actionsRoutes = new Hono();
// Actions autorisées par l'API. Les actions destructives Docker
// (docker_compose_apply/down, docker_prune_images agressif) restent hors API
// jusqu'au socle de validation (action_requests, SJ-6).
const ALLOWED_ACTIONS: ActionType[] = [
"apt_full_upgrade",
"reboot",
// Docker passifs / non-applicatifs (SJ-4/SJ-5).
"docker_scan",
"docker_inspect_current",
"docker_pull_check",
// SJ-7 : sonde (lecture seule) + proxy APT persistant (action explicite idempotente).
"machine_probe",
"apt_proxy_persistent",
];
// Actions Docker ciblant un stack précis : stackId obligatoire.
const NEED_STACK: ActionType[] = ["docker_inspect_current", "docker_pull_check"];
actionsRoutes.post("/:id/actions", async (c) => {
const { action, stackId } = (await c.req.json()) as { action: ActionType; stackId?: string };
if (!ALLOWED_ACTIONS.includes(action)) {
return c.json({ error: "Action non autorisée" }, 400);
}
if (NEED_STACK.includes(action) && !stackId) {
return c.json({ error: "stackId requis pour cette action" }, 400);
}
// Exécution lancée en arrière-plan; le suivi se fait via WebSocket.
runAction(c.req.param("id"), action, stackId ? { stackId } : undefined).catch((err) =>
console.error("[action]", (err as Error).message),
);
return c.json({ ok: true, action }, 202);
});
actionsRoutes.get("/:id/executions", (c) => c.json(listExecutions(c.req.param("id"))));
actionsRoutes.get("/:id/executions/:execId", (c) => {
const e = getExecution(c.req.param("execId"));
return e ? c.json(e) : c.json({ error: "Exécution introuvable" }, 404);
});
actionsRoutes.get("/:id/executions/:execId/report", (c) => {
const e = getExecution(c.req.param("execId"));
if (!e?.reportPath) return c.json({ error: "Rapport introuvable" }, 404);
return c.text(readFileSync(e.reportPath, "utf8"));
});