feat: service execute (full-upgrade/reboot -> execution + rapport archivé)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
// server/services/execute.ts
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { mkdirSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { db, schema } from "../db/client.js";
|
||||
import { env } from "../env.js";
|
||||
import { getMachineRow, getCreds } from "./machines.js";
|
||||
import { renderTemplate } from "../templates/render.js";
|
||||
import { reduceAptLines } from "../templates/aptReduce.js";
|
||||
import { runScriptSudo } from "../ssh/client.js";
|
||||
import { parseRebootRequired } from "./aptParse.js";
|
||||
import { extractSection } from "./refresh.js";
|
||||
import { buildReportMarkdown } from "./report.js";
|
||||
import { outputHub } from "../ws/outputHub.js";
|
||||
import type { ActionType, ExecutionResult, ExecutionStatus } from "@shared/types.js";
|
||||
|
||||
const TEMPLATE_FOR: Record<ActionType, string> = {
|
||||
apt_full_upgrade: "apt/full-upgrade.sh.tpl",
|
||||
reboot: "apt/reboot.sh.tpl",
|
||||
};
|
||||
|
||||
export async function runAction(machineId: string, action: ActionType): Promise<ExecutionResult> {
|
||||
const m = getMachineRow(machineId);
|
||||
if (!m) throw new Error("Machine introuvable");
|
||||
|
||||
const executionId = `exec_${Date.now()}_${randomUUID().slice(0, 8)}`;
|
||||
const startedAt = new Date().toISOString();
|
||||
outputHub.clear(machineId);
|
||||
db.update(schema.machines).set({ status: "running" }).where(eq(schema.machines.id, machineId)).run();
|
||||
db.insert(schema.executions).values({
|
||||
id: executionId, machineId, action, mode: "manual", startedAt, status: "running",
|
||||
}).run();
|
||||
|
||||
const proxy = m.aptProxyMode === "runtime" ? m.aptProxyUrl : null;
|
||||
const script = renderTemplate(TEMPLATE_FOR[action], { aptProxy: proxy });
|
||||
|
||||
let raw = "";
|
||||
let status: ExecutionStatus = "ok";
|
||||
try {
|
||||
const res = await runScriptSudo(getCreds(m), script, (c) => {
|
||||
raw += c;
|
||||
outputHub.publish(machineId, c);
|
||||
});
|
||||
raw = res.stdout;
|
||||
if (/===SU:EXIT=(\d+)===/.exec(raw)?.[1] && /===SU:EXIT=0===/.test(raw) === false) {
|
||||
status = "error";
|
||||
}
|
||||
} catch (err) {
|
||||
status = "error";
|
||||
raw += `\n[ERREUR] ${(err as Error).message}\n`;
|
||||
}
|
||||
|
||||
const finishedAt = new Date().toISOString();
|
||||
const rebootRequired = parseRebootRequired(extractSection(raw, "===SU:REBOOT===", "===SU:EXIT") || raw);
|
||||
|
||||
// Archivage log brut + rapport.
|
||||
const dir = join(env.reportsDir, machineId);
|
||||
mkdirSync(dir, { recursive: true });
|
||||
const rawLogPath = join(dir, `${executionId}.log`);
|
||||
const reportPath = join(dir, `${executionId}.md`);
|
||||
writeFileSync(rawLogPath, raw, "utf8");
|
||||
|
||||
const result: ExecutionResult = {
|
||||
executionId, machineId, startedAt, finishedAt, mode: "manual", action, status,
|
||||
rebootRequiredAfterRun: rebootRequired,
|
||||
importantLogLines: reduceAptLines(raw),
|
||||
rawLogRef: rawLogPath, reportRef: reportPath,
|
||||
};
|
||||
writeFileSync(reportPath, buildReportMarkdown(result, m.name), "utf8");
|
||||
|
||||
db.update(schema.executions).set({
|
||||
finishedAt, status, resultJson: JSON.stringify(result), reportPath, rawLogPath,
|
||||
}).where(eq(schema.executions.id, executionId)).run();
|
||||
db.update(schema.machines).set({ status: status === "error" ? "error" : "unknown" })
|
||||
.where(eq(schema.machines.id, machineId)).run();
|
||||
|
||||
outputHub.publish(machineId, `\n===SU:DONE status=${status}===\n`);
|
||||
return result;
|
||||
}
|
||||
|
||||
export function listExecutions(machineId: string) {
|
||||
return db.select().from(schema.executions).where(eq(schema.executions.machineId, machineId)).all();
|
||||
}
|
||||
|
||||
export function getExecution(executionId: string) {
|
||||
return db.select().from(schema.executions).where(eq(schema.executions.id, executionId)).get();
|
||||
}
|
||||
Reference in New Issue
Block a user