Files
system_update/server/ssh/client.ts
T
2026-06-04 21:06:37 +02:00

92 lines
2.4 KiB
TypeScript

// server/ssh/client.ts
import { Client } from "ssh2";
export interface SshCreds {
hostname: string;
port: number;
username: string;
password: string;
sudoPassword?: string | null;
}
export interface RunResult {
stdout: string;
code: number;
}
function connect(creds: SshCreds): Promise<Client> {
return new Promise((resolve, reject) => {
const conn = new Client();
conn
.on("ready", () => resolve(conn))
.on("error", reject)
.connect({
host: creds.hostname,
port: creds.port,
username: creds.username,
password: creds.password,
readyTimeout: 15000,
});
});
}
/** Exécute une commande simple (sans sudo), renvoie stdout agrégé. */
export async function runPlain(creds: SshCreds, command: string): Promise<RunResult> {
const conn = await connect(creds);
try {
return await execStream(conn, command, null, () => {});
} finally {
conn.end();
}
}
/**
* Exécute un script shell sous sudo. Le script est encodé en base64 pour éviter
* tout problème de quoting; le mot de passe sudo est poussé sur stdin (sudo -S -p '').
* `onData` reçoit chaque chunk de sortie pour le streaming live.
*/
export async function runScriptSudo(
creds: SshCreds,
script: string,
onData: (chunk: string) => void,
): Promise<RunResult> {
const conn = await connect(creds);
try {
const b64 = Buffer.from(script, "utf8").toString("base64");
const cmd = `sudo -S -p '' sh -c "$(printf '%s' '${b64}' | base64 -d)"`;
return await execStream(conn, cmd, (creds.sudoPassword ?? creds.password) + "\n", onData);
} finally {
conn.end();
}
}
function execStream(
conn: Client,
command: string,
stdinData: string | null,
onData: (chunk: string) => void,
): Promise<RunResult> {
return new Promise((resolve, reject) => {
conn.exec(command, { pty: false }, (err, stream) => {
if (err) return reject(err);
let stdout = "";
let code = 0;
if (stdinData) {
stream.write(stdinData);
}
stream
.on("close", (c: number) => resolve({ stdout, code: c ?? code }))
.on("data", (d: Buffer) => {
const s = d.toString("utf8");
stdout += s;
onData(s);
});
stream.stderr.on("data", (d: Buffer) => {
const s = d.toString("utf8");
stdout += s;
onData(s);
});
});
});
}