feat(post-install): identity_network reboote et rebascule l'IP en BDD (tâche 4)
- updateMachine accepte name/hostname/port (correction d'identité réseau) - rebootAndRebind : reboot sur l'ancienne connexion → attente du retour sur la NOUVELLE IP (verifyReboot avec host cible) → maj BDD (hostname + nom) si OK. Sécurité : BDD inchangée si la machine ne revient pas (récupération console/backups) - execute post_install : si identity_network + reboot coché + succès, déclenche rebootAndRebind et joint le RebootResult ; statut error si reconnexion échoue tsc 0 · 113 tests · build OK. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,7 @@ function archiveExecution(args: {
|
||||
importantLines: string[];
|
||||
docker?: ExecutionResult["docker"];
|
||||
postInstall?: ExecutionResult["postInstall"];
|
||||
reboot?: ExecutionResult["reboot"];
|
||||
errors?: ExecutionResult["errors"];
|
||||
}): ExecutionResult {
|
||||
const { machineId, machineName, executionId, action, startedAt, status, raw, importantLines } = args;
|
||||
@@ -68,6 +69,7 @@ function archiveExecution(args: {
|
||||
rawLogRef: rawLogPath, reportRef: reportPath,
|
||||
...(args.docker ? { docker: args.docker } : {}),
|
||||
...(args.postInstall ? { postInstall: args.postInstall } : {}),
|
||||
...(args.reboot ? { reboot: args.reboot } : {}),
|
||||
...(args.errors && args.errors.length ? { errors: args.errors } : {}),
|
||||
};
|
||||
writeFileSync(reportPath, buildReportMarkdown(result, machineName), "utf8");
|
||||
@@ -340,7 +342,7 @@ export async function runAction(
|
||||
// --- SJ-8 : post-install (profil + champs de formulaire) ---
|
||||
if (action === "post_install") {
|
||||
if (!opts?.profileId) throw new Error("post_install requiert un profileId");
|
||||
const { runPostInstall } = await import("./postInstall.js");
|
||||
const { runPostInstall, rebootAndRebind } = await import("./postInstall.js");
|
||||
try {
|
||||
const o = await runPostInstall(machineId, opts.profileId, opts.values ?? {}, () => {});
|
||||
const r = o.result;
|
||||
@@ -351,10 +353,25 @@ export async function runAction(
|
||||
...(r.networkChange ? [` réseau : ${r.networkChange.oldEndpoint ?? "?"} → ${r.networkChange.newEndpoint ?? "?"} (reconnexion ${r.networkChange.reconnectHost ?? "?"})`] : []),
|
||||
...(r.errors?.map((e) => ` [${e.kind}] ${e.message}`) ?? []),
|
||||
];
|
||||
outputHub.publish(machineId, `\n===SU:DONE status=${o.status}===\n`);
|
||||
|
||||
// identity_network + reboot coché + succès : reboote, attend la nouvelle IP, corrige la BDD.
|
||||
let rebootResult: ExecutionResult["reboot"];
|
||||
const newHost = r.networkChange?.reconnectHost ?? r.networkChange?.newEndpoint ?? null;
|
||||
if (o.status === "ok" && opts.profileId === "identity_network" && opts.values?.rebootAfterInstall && newHost) {
|
||||
const newName = opts.values?.newHostname != null ? String(opts.values.newHostname) : null;
|
||||
rebootResult = await rebootAndRebind(machineId, newHost, newName, () => {});
|
||||
important.push(
|
||||
rebootResult.status === "ok"
|
||||
? ` reboot vérifié → machine basculée sur ${newHost} (BDD mise à jour)`
|
||||
: ` reboot/reconnexion : ${rebootResult.status} (BDD inchangée)`,
|
||||
);
|
||||
}
|
||||
|
||||
const finalStatus: ExecutionStatus = rebootResult && rebootResult.status !== "ok" ? "error" : o.status;
|
||||
outputHub.publish(machineId, `\n===SU:DONE status=${finalStatus}===\n`);
|
||||
return archiveExecution({
|
||||
machineId, machineName: m.name, executionId, action, startedAt, status: o.status, raw: o.raw,
|
||||
importantLines: important, postInstall: r, errors: r.errors,
|
||||
machineId, machineName: m.name, executionId, action, startedAt, status: finalStatus, raw: o.raw,
|
||||
importantLines: important, postInstall: r, reboot: rebootResult, errors: r.errors,
|
||||
});
|
||||
} catch (err) {
|
||||
return archiveExecution({ machineId, machineName: m.name, executionId, action, startedAt, status: "error", raw: "", importantLines: [`[ERREUR] ${(err as Error).message}`] });
|
||||
|
||||
@@ -41,6 +41,9 @@ function toView(m: MachineRow): MachineView {
|
||||
}
|
||||
|
||||
export interface UpdateMachineInput {
|
||||
name?: string;
|
||||
hostname?: string;
|
||||
port?: number;
|
||||
osFamily?: OsFamily;
|
||||
machineKind?: MachineKind;
|
||||
virtualization?: string | null;
|
||||
@@ -48,11 +51,14 @@ export interface UpdateMachineInput {
|
||||
aptProxyUrl?: string | null;
|
||||
}
|
||||
|
||||
/** Met à jour les champs de profil/proxy d'une machine (jamais les secrets). */
|
||||
/** Met à jour les champs de profil/proxy/identité d'une machine (jamais les secrets). */
|
||||
export function updateMachine(id: string, input: UpdateMachineInput): MachineView {
|
||||
const row = getMachineRow(id);
|
||||
if (!row) throw new Error("Machine introuvable");
|
||||
const patch: Partial<MachineRow> = { updatedAt: new Date().toISOString() };
|
||||
if (input.name !== undefined) patch.name = input.name;
|
||||
if (input.hostname !== undefined) patch.hostname = input.hostname;
|
||||
if (input.port !== undefined) patch.port = input.port;
|
||||
if (input.osFamily !== undefined) patch.osFamily = input.osFamily;
|
||||
if (input.machineKind !== undefined) patch.machineKind = input.machineKind;
|
||||
if (input.virtualization !== undefined) patch.virtualization = input.virtualization;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// server/services/postInstall.ts
|
||||
import { getMachineRow, getCreds } from "./machines.js";
|
||||
import { getMachineRow, getCreds, updateMachine } from "./machines.js";
|
||||
import { renderTemplate, type TemplateVars } from "../templates/render.js";
|
||||
import { runScriptSudo } from "../ssh/client.js";
|
||||
import { outputHub } from "../ws/outputHub.js";
|
||||
import type { PostInstallResult, SnapshotError } from "@shared/types.js";
|
||||
import { parseBootIdBefore, verifyReboot } from "./rebootVerify.js";
|
||||
import type { PostInstallResult, RebootResult, SnapshotError } from "@shared/types.js";
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Manifestes de profils (registre versionné en code ; templates versionnés sur disque).
|
||||
@@ -319,3 +320,49 @@ export async function runPostInstall(
|
||||
const failed = (result.errors?.length ?? 0) > 0 || (/===SU:EXIT=\d+===/.test(raw) && !/===SU:EXIT=0===/.test(raw));
|
||||
return { result, raw, status: failed ? "error" : "ok" };
|
||||
}
|
||||
|
||||
/**
|
||||
* Reboote la machine puis attend son retour sur la NOUVELLE IP, et corrige la BDD
|
||||
* (hostname SSH + nom) si la reconnexion réussit. Sécurité : si la machine ne revient
|
||||
* pas sur la nouvelle IP, la BDD n'est PAS modifiée (récupération via console + backups).
|
||||
*/
|
||||
export async function rebootAndRebind(
|
||||
machineId: string,
|
||||
newHost: string,
|
||||
newName: string | null,
|
||||
onData?: (c: string) => void,
|
||||
): Promise<RebootResult> {
|
||||
const m = getMachineRow(machineId);
|
||||
if (!m) throw new Error("Machine introuvable");
|
||||
const creds = getCreds(m);
|
||||
|
||||
// 1) Reboot sur l'ancienne connexion (capture boot_id avant).
|
||||
let raw = "";
|
||||
try {
|
||||
const res = await runScriptSudo(creds, renderTemplate("apt/reboot.sh.tpl", {}), (c) => {
|
||||
raw += c;
|
||||
onData?.(c);
|
||||
outputHub.publish(machineId, c);
|
||||
}, 0);
|
||||
raw = res.stdout;
|
||||
} catch {
|
||||
/* la connexion tombe pendant le reboot : normal */
|
||||
}
|
||||
const beforeBootId = parseBootIdBefore(raw);
|
||||
outputHub.publish(machineId, `\n[reboot] attente du retour sur ${newHost}...\n`);
|
||||
|
||||
// 2) Reconnexion sur la NOUVELLE IP.
|
||||
const reboot = await verifyReboot({ ...creds, hostname: newHost }, {
|
||||
beforeBootId,
|
||||
requestedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// 3) Bascule BDD uniquement si la machine est bien revenue sur la nouvelle IP.
|
||||
if (reboot.status === "ok") {
|
||||
updateMachine(machineId, { hostname: newHost, ...(newName ? { name: newName } : {}) });
|
||||
outputHub.publish(machineId, `\n[reboot] machine basculée sur ${newHost}, BDD mise à jour.\n`);
|
||||
} else {
|
||||
outputHub.publish(machineId, `\n[reboot] reconnexion ${newHost} échouée (${reboot.status}) — BDD inchangée.\n`);
|
||||
}
|
||||
return reboot;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user