f5f361a349
Suite revue batch D. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
89 lines
3.5 KiB
TypeScript
89 lines
3.5 KiB
TypeScript
// 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+===/.test(raw) && !/===SU:EXIT=0===/.test(raw)) {
|
|
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();
|
|
}
|