diff --git a/server/services/execute.ts b/server/services/execute.ts index a38b3a3..80051a1 100644 --- a/server/services/execute.ts +++ b/server/services/execute.ts @@ -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}`] }); diff --git a/server/services/machines.ts b/server/services/machines.ts index 9f98c40..b52aa08 100644 --- a/server/services/machines.ts +++ b/server/services/machines.ts @@ -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 = { 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; diff --git a/server/services/postInstall.ts b/server/services/postInstall.ts index 5f0a0ae..5a0848f 100644 --- a/server/services/postInstall.ts +++ b/server/services/postInstall.ts @@ -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 { + 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; +}