// SSH execution helper for integration tests. import { spawn, spawnSync } from "node:child_process"; import { log } from "./libvirt.js"; export interface SshResult { exitCode: number; stdout: string; stderr: string; } /** Execute a command via SSH and return the result. */ export function sshExec( ip: string, user: string, command: string, options?: { keyPath?: string; timeout?: number }, ): SshResult { const args = [ "-o", "StrictHostKeyChecking=no", "-o", "ConnectTimeout=10", "-o", "BatchMode=yes", ...(options?.keyPath ? ["-i", options.keyPath] : []), `${user}@${ip}`, command, ]; const result = spawnSync("ssh", args, { stdio: "pipe", timeout: options?.timeout ?? 120_000, encoding: "utf-8", }); return { exitCode: result.status ?? 1, stdout: result.stdout ?? "", stderr: result.stderr ?? "", }; } /** Execute a command via SSH with live streaming output. */ export async function sshExecStreaming( ip: string, user: string, command: string, onLine: (line: string) => void, options?: { keyPath?: string; timeout?: number }, ): Promise { return new Promise((resolve, reject) => { const args = [ "-o", "StrictHostKeyChecking=no", "-o", "ConnectTimeout=10", "-o", "BatchMode=yes", ...(options?.keyPath ? ["-i", options.keyPath] : []), `${user}@${ip}`, command, ]; const proc = spawn("ssh", args, { stdio: ["ignore", "pipe", "pipe"], timeout: options?.timeout ?? 600_000, }); let buffer = ""; proc.stdout.on("data", (data: Buffer) => { buffer += data.toString(); const lines = buffer.split("\n"); buffer = lines.pop() ?? ""; for (const line of lines) { onLine(line); } }); proc.stderr.on("data", (data: Buffer) => { const text = data.toString().trim(); if (text) onLine(`[stderr] ${text}`); }); proc.on("close", (code) => { if (buffer.trim()) onLine(buffer.trim()); resolve(code ?? 1); }); proc.on("error", (err) => { reject(err); }); }); } /** Run a command on the VM via SSH and log it with a prefix. */ export async function sshRun( ip: string, user: string, command: string, label: string, options?: { keyPath?: string; timeout?: number }, ): Promise { log(`${label}: ${command.slice(0, 80)}${command.length > 80 ? "..." : ""}`); const code = await sshExecStreaming(ip, user, command, (line) => { console.log(` ${line}`); }, options); if (code !== 0) { log(`${label}: FAILED (exit ${code})`); } return code; }