mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
120 lines
2.9 KiB
TypeScript
120 lines
2.9 KiB
TypeScript
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);
|
|
|
|
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));
|
|
};
|