// 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")); });