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>
This commit is contained in:
+41
-15
@@ -6,7 +6,7 @@ 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 { renderTemplate, resolveTemplate } from "../templates/render.js";
|
||||
import { reduceAptLines } from "../templates/aptReduce.js";
|
||||
import { runScriptSudo } from "../ssh/client.js";
|
||||
import { parseRebootRequired, buildAptExecutionResult } from "./aptParse.js";
|
||||
@@ -18,16 +18,16 @@ import { outputHub } from "../ws/outputHub.js";
|
||||
import { upsertMachineState, recordEvent } from "./machineState.js";
|
||||
import type { ActionType, AptExecutionResult, ExecutionResult, ExecutionStatus } from "@shared/types.js";
|
||||
|
||||
const TEMPLATE_FOR: Partial<Record<ActionType, string>> = {
|
||||
apt_full_upgrade: "apt/full-upgrade.sh.tpl",
|
||||
apt_upgrade: "apt/upgrade.sh.tpl",
|
||||
apt_autoremove: "apt/autoremove.sh.tpl",
|
||||
apt_clean: "apt/clean.sh.tpl",
|
||||
reboot: "apt/reboot.sh.tpl",
|
||||
reboot_verified: "apt/reboot.sh.tpl",
|
||||
// SJ-4 Docker (passif)
|
||||
docker_scan: "docker/scan-compose.sh.tpl",
|
||||
docker_inspect_current: "docker/inspect-compose.sh.tpl",
|
||||
// Actions APT/système résolues par profil OS (resolveTemplate → proxmox/raspbian si dispo,
|
||||
// sinon fallback apt/). La valeur est le basename d'action (sans dossier ni extension).
|
||||
const APT_ACTION_FILE: Partial<Record<ActionType, string>> = {
|
||||
apt_full_upgrade: "full-upgrade",
|
||||
apt_upgrade: "upgrade",
|
||||
apt_autoremove: "autoremove",
|
||||
apt_clean: "clean",
|
||||
reboot: "reboot",
|
||||
reboot_verified: "reboot",
|
||||
apt_proxy_persistent: "apt-proxy-persistent",
|
||||
};
|
||||
|
||||
export interface RunActionOpts {
|
||||
@@ -333,16 +333,42 @@ export async function runAction(
|
||||
}
|
||||
}
|
||||
|
||||
// --- SJ-7 : sonde machine (lecture seule) déléguée au service dédié ---
|
||||
if (action === "machine_probe") {
|
||||
const { runProbe } = await import("./machineProbe.js");
|
||||
try {
|
||||
const o = await runProbe(machineId, () => {});
|
||||
const important = [
|
||||
`machine_probe : os=${o.probe.osId ?? "?"} ${o.probe.osVersion ?? ""} arch=${o.probe.arch ?? "?"} virt=${o.probe.virt ?? "?"}`,
|
||||
`proposition : os_family=${o.proposal.osFamily} machine_kind=${o.proposal.machineKind} virtualization=${o.proposal.virtualization}`,
|
||||
...(o.changes.length ? ["corrections proposées (non appliquées) :", ...o.changes.map((c) => ` ${c}`)] : ["aucune correction proposée"]),
|
||||
];
|
||||
outputHub.publish(machineId, `\n===SU:DONE status=ok===\n`);
|
||||
return archiveExecution({ machineId, machineName: m.name, executionId, action, startedAt, status: "ok", raw: o.raw, importantLines: important });
|
||||
} catch (err) {
|
||||
return archiveExecution({ machineId, machineName: m.name, executionId, action, startedAt, status: "error", raw: "", importantLines: [`[ERREUR] ${(err as Error).message}`] });
|
||||
}
|
||||
}
|
||||
|
||||
const proxy = m.aptProxyMode === "runtime" ? m.aptProxyUrl : null;
|
||||
const rel = TEMPLATE_FOR[action];
|
||||
if (!rel) throw new Error("Action sans template: " + action);
|
||||
// Templates Docker par-stack (inspect) : injecter stackDir ; ignoré par les templates APT.
|
||||
// Résolution du template : Docker inspect = chemin direct ; sinon résolution par profil OS.
|
||||
let rel: string;
|
||||
if (action === "docker_inspect_current") {
|
||||
rel = "docker/inspect-compose.sh.tpl";
|
||||
} else {
|
||||
const file = APT_ACTION_FILE[action];
|
||||
if (!file) throw new Error("Action sans template: " + action);
|
||||
rel = resolveTemplate(file, m.osFamily);
|
||||
}
|
||||
// Docker inspect par-stack : injecter stackDir ; ignoré par les templates APT.
|
||||
let stackDir: string | null = null;
|
||||
if (opts?.stackId) {
|
||||
const st = db.select().from(schema.dockerComposeStacks).where(eq(schema.dockerComposeStacks.id, opts.stackId)).get();
|
||||
stackDir = st?.workingDir ?? null;
|
||||
}
|
||||
const script = renderTemplate(rel, { aptProxy: proxy, stackDir });
|
||||
// Proxy persistant : l'URL est passée comme variable de template (jamais un secret).
|
||||
const aptProxyUrl = action === "apt_proxy_persistent" ? m.aptProxyUrl : null;
|
||||
const script = renderTemplate(rel, { aptProxy: proxy, stackDir, aptProxyUrl });
|
||||
|
||||
const inactivity = action === "reboot" ? 0 : 600000;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user