164 lines · 3.9 KB · TypeScript
RawDownload
import { exec, execFile } from "node:child_process";import util from "node:util";import { findServerById } from "@dokploy/server/services/server";import { Client } from "ssh2"; export const execAsync = util.promisify(exec); interface ExecOptions {	cwd?: string;	env?: NodeJS.ProcessEnv;} export const execAsyncStream = (	command: string,	onData?: (data: string) => void,	options: ExecOptions = {},): Promise<{ stdout: string; stderr: string }> => {	return new Promise((resolve, reject) => {		let stdoutComplete = "";		let stderrComplete = ""; 		const childProcess = exec(command, options, (error) => {			if (error) {				reject(error);				return;			}			resolve({ stdout: stdoutComplete, stderr: stderrComplete });		}); 		childProcess.stdout?.on("data", (data: Buffer | string) => {			const stringData = data.toString();			stdoutComplete += stringData;			if (onData) {				onData(stringData);			}		}); 		childProcess.stderr?.on("data", (data: Buffer | string) => {			const stringData = data.toString();			stderrComplete += stringData;			if (onData) {				onData(stringData);			}		}); 		childProcess.on("error", (error) => {			console.log(error);			reject(error);		});	});}; export const execFileAsync = async (	command: string,	args: string[],	options: { input?: string } = {},): Promise<{ stdout: string; stderr: string }> => {	const child = execFile(command, args); 	if (options.input && child.stdin) {		child.stdin.write(options.input);		child.stdin.end();	} 	return new Promise((resolve, reject) => {		let stdout = "";		let stderr = ""; 		child.stdout?.on("data", (data) => {			stdout += data.toString();		}); 		child.stderr?.on("data", (data) => {			stderr += data.toString();		}); 		child.on("close", (code) => {			if (code === 0) {				resolve({ stdout, stderr });			} else {				reject(					new Error(`Command failed with code ${code}. Stderr: ${stderr}`),				);			}		}); 		child.on("error", reject);	});}; export const execAsyncRemote = async (	serverId: string | null,	command: string,	onData?: (data: string) => void,): Promise<{ stdout: string; stderr: string }> => {	if (!serverId) return { stdout: "", stderr: "" };	const server = await findServerById(serverId);	if (!server.sshKeyId) throw new Error("No SSH key available for this server"); 	let stdout = "";	let stderr = "";	return new Promise((resolve, reject) => {		const conn = new Client(); 		sleep(1000);		conn			.once("ready", () => {				conn.exec(command, (err, stream) => {					if (err) {						onData?.(err.message);						throw err;					}					stream						.on("close", (code: number, _signal: string) => {							conn.end();							if (code === 0) {								resolve({ stdout, stderr });							} else {								reject(									new Error(										`Command exited with code ${code}. Stderr: ${stderr}, command: ${command}`,									),								);							}						})						.on("data", (data: string) => {							stdout += data.toString();							onData?.(data.toString());						})						.stderr.on("data", (data) => {							stderr += data.toString();							onData?.(data.toString());						});				});			})			.on("error", (err) => {				conn.end();				if (err.level === "client-authentication") {					onData?.(						`Authentication failed: Invalid SSH private key. ❌ Error: ${err.message} ${err.level}`,					);					reject(						new Error(							`Authentication failed: Invalid SSH private key. ❌ Error: ${err.message} ${err.level}`,						),					);				} else {					onData?.(`SSH connection error: ${err.message}`);					reject(new Error(`SSH connection error: ${err.message}`));				}			})			.connect({				host: server.ipAddress,				port: server.port,				username: server.username,				privateKey: server.sshKey?.privateKey,				timeout: 99999,			});	});}; export const sleep = (ms: number) => {	return new Promise((resolve) => setTimeout(resolve, ms));};