mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-26 01:25:22 +02:00
refactor(multi-server): fix deploy on docker compose
This commit is contained in:
@@ -12,7 +12,7 @@ import {
|
||||
writeDomainsToCompose,
|
||||
writeDomainsToComposeRemote,
|
||||
} from "../docker/domain";
|
||||
import { prepareEnvironmentVariables } from "../docker/utils";
|
||||
import { encodeBase64, prepareEnvironmentVariables } from "../docker/utils";
|
||||
import { spawnAsync } from "../process/spawnAsync";
|
||||
import { execAsyncRemote } from "../process/execAsync";
|
||||
|
||||
@@ -106,9 +106,7 @@ docker ${command.split(" ").join(" ")} >> ${logPath} 2>&1;
|
||||
echo "Docker Compose Deployed: ✅" >> ${logPath};
|
||||
`;
|
||||
|
||||
await execAsyncRemote(compose.serverId, bashCommand);
|
||||
|
||||
return bashCommand;
|
||||
return await execAsyncRemote(compose.serverId, bashCommand);
|
||||
};
|
||||
|
||||
const sanitizeCommand = (command: string) => {
|
||||
@@ -174,6 +172,7 @@ export const getCreateEnvFileCommand = (compose: ComposeNested) => {
|
||||
join(COMPOSE_PATH, appName, "code", "docker-compose.yml");
|
||||
|
||||
const envFilePath = join(dirname(composeFilePath), ".env");
|
||||
|
||||
let envContent = env || "";
|
||||
if (!envContent.includes("DOCKER_CONFIG")) {
|
||||
envContent += "\nDOCKER_CONFIG=/root/.docker/config.json";
|
||||
@@ -184,8 +183,10 @@ export const getCreateEnvFileCommand = (compose: ComposeNested) => {
|
||||
}
|
||||
|
||||
const envFileContent = prepareEnvironmentVariables(envContent).join("\n");
|
||||
|
||||
const encodedContent = encodeBase64(envFileContent);
|
||||
return `
|
||||
mkdir -p ${envFilePath};
|
||||
echo "${envFileContent}" > ${envFilePath};
|
||||
touch ${envFilePath};
|
||||
echo "${encodedContent}" | base64 -d > "${envFilePath}";
|
||||
`;
|
||||
};
|
||||
|
||||
@@ -32,6 +32,7 @@ import type {
|
||||
PropertiesNetworks,
|
||||
} from "./types";
|
||||
import { execAsyncRemote } from "../process/execAsync";
|
||||
import { encodeBase64 } from "./utils";
|
||||
|
||||
export const cloneCompose = async (compose: Compose) => {
|
||||
if (compose.sourceType === "github") {
|
||||
@@ -144,13 +145,14 @@ export const writeDomainsToComposeRemote = async (
|
||||
try {
|
||||
if (compose.serverId) {
|
||||
const composeString = dump(composeConverted, { lineWidth: 1000 });
|
||||
return `printf '%s' '${composeString.replace(/'/g, "'\\''")}' > ${path}`;
|
||||
const encodedContent = encodeBase64(composeString);
|
||||
return `echo "${encodedContent}" | base64 -d > "${path}";`;
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// (node:59875) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 SIGTERM listeners added to [process]. Use emitter.setMaxListeners() to increase limit
|
||||
export const addDomainToCompose = async (
|
||||
compose: Compose,
|
||||
domains: Domain[],
|
||||
|
||||
@@ -410,6 +410,8 @@ export const createFile = async (
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
export const encodeBase64 = (content: string) =>
|
||||
Buffer.from(content, "utf-8").toString("base64");
|
||||
|
||||
export const getCreateFileCommand = (
|
||||
outputPath: string,
|
||||
@@ -422,10 +424,10 @@ export const getCreateFileCommand = (
|
||||
}
|
||||
|
||||
const directory = path.dirname(fullPath);
|
||||
|
||||
const encodedContent = encodeBase64(content);
|
||||
return `
|
||||
mkdir -p ${directory};
|
||||
echo "${content}" > ${fullPath};
|
||||
echo "${encodedContent}" | base64 -d > "${fullPath}";
|
||||
`;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,39 +1,81 @@
|
||||
import { exec } from "node:child_process";
|
||||
import util from "node:util";
|
||||
import { connectSSH } from "../servers/connection";
|
||||
import { findServerById } from "@/server/api/services/server";
|
||||
import { readSSHKey } from "../filesystem/ssh";
|
||||
import { Client } from "ssh2";
|
||||
export const execAsync = util.promisify(exec);
|
||||
|
||||
export const execAsyncRemote = async (
|
||||
serverId: string,
|
||||
serverId: string | null,
|
||||
command: string,
|
||||
): Promise<{ stdout: string; stderr: string }> => {
|
||||
const client = await connectSSH(serverId);
|
||||
if (!serverId) return { stdout: "", stderr: "" };
|
||||
const server = await findServerById(serverId);
|
||||
if (!server.sshKeyId) throw new Error("No SSH key available for this server");
|
||||
|
||||
const keys = await readSSHKey(server.sshKeyId);
|
||||
|
||||
const conn = new Client();
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
return new Promise((resolve, reject) => {
|
||||
client.exec(command, (err, stream) => {
|
||||
if (err) {
|
||||
client.end();
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
|
||||
stream
|
||||
.on("data", (data: string) => {
|
||||
stdout += data.toString();
|
||||
})
|
||||
.on("close", (code, signal) => {
|
||||
client.end();
|
||||
if (code === 0) {
|
||||
resolve({ stdout, stderr });
|
||||
} else {
|
||||
reject(new Error(`Command exited with code ${code}`));
|
||||
}
|
||||
})
|
||||
.stderr.on("data", (data) => {
|
||||
stderr += data.toString();
|
||||
conn
|
||||
.once("ready", () => {
|
||||
console.log("Client :: ready");
|
||||
conn.exec(command, (err, stream) => {
|
||||
if (err) throw err;
|
||||
stream
|
||||
.on("close", (code, signal) => {
|
||||
console.log(
|
||||
`Stream :: close :: code: ${code}, signal: ${signal}`,
|
||||
);
|
||||
conn.end();
|
||||
if (code === 0) {
|
||||
resolve({ stdout, stderr });
|
||||
} else {
|
||||
reject(new Error(`Command exited with code ${code}`));
|
||||
}
|
||||
})
|
||||
.on("data", (data: string) => {
|
||||
stdout += data.toString();
|
||||
})
|
||||
.stderr.on("data", (data) => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
.connect({
|
||||
host: server.ipAddress,
|
||||
port: server.port,
|
||||
username: server.username,
|
||||
privateKey: keys.privateKey,
|
||||
timeout: 99999,
|
||||
});
|
||||
|
||||
// client.exec(command, (err, stream) => {
|
||||
// if (err) {
|
||||
// client.end();
|
||||
// return reject(err);
|
||||
// }
|
||||
|
||||
// let stdout = "";
|
||||
// let stderr = "";
|
||||
|
||||
// stream
|
||||
// .on("data", (data: string) => {
|
||||
// stdout += data.toString();
|
||||
// })
|
||||
// .on("close", (code, signal) => {
|
||||
// client.end();
|
||||
// if (code === 0) {
|
||||
// resolve({ stdout, stderr });
|
||||
// } else {
|
||||
// reject(new Error(`Command exited with code ${code}`));
|
||||
// }
|
||||
// })
|
||||
// .stderr.on("data", (data) => {
|
||||
// stderr += data.toString();
|
||||
// });
|
||||
// });
|
||||
});
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { Compose } from "@/server/api/services/compose";
|
||||
import { COMPOSE_PATH } from "@/server/constants";
|
||||
import { recreateDirectory } from "../filesystem/directory";
|
||||
import { execAsyncRemote } from "../process/execAsync";
|
||||
import { encodeBase64 } from "../docker/utils";
|
||||
|
||||
export const createComposeFile = async (compose: Compose, logPath: string) => {
|
||||
const { appName, composeFile } = compose;
|
||||
@@ -32,13 +33,13 @@ export const getCreateComposeFileCommand = (compose: Compose) => {
|
||||
const { appName, composeFile } = compose;
|
||||
const outputPath = join(COMPOSE_PATH, appName, "code");
|
||||
const filePath = join(outputPath, "docker-compose.yml");
|
||||
const command = [];
|
||||
command.push(`rm -rf ${outputPath};`);
|
||||
command.push(`mkdir -p ${outputPath};`);
|
||||
command.push(
|
||||
`printf '%s' '${composeFile.replace(/'/g, "'\\''")}' > ${filePath};`,
|
||||
);
|
||||
return command.join("\n");
|
||||
const encodedContent = encodeBase64(composeFile);
|
||||
const bashCommand = `
|
||||
rm -rf ${outputPath};
|
||||
mkdir -p ${outputPath};
|
||||
echo "${encodedContent}" | base64 -d > "${filePath}";
|
||||
`;
|
||||
return bashCommand;
|
||||
};
|
||||
|
||||
export const createComposeFileRaw = async (compose: Compose) => {
|
||||
@@ -59,9 +60,10 @@ export const createComposeFileRawRemote = async (compose: Compose) => {
|
||||
const filePath = join(outputPath, "docker-compose.yml");
|
||||
|
||||
try {
|
||||
const encodedContent = encodeBase64(composeFile);
|
||||
const command = `
|
||||
mkdir -p ${outputPath};
|
||||
echo "${composeFile}" > ${filePath};
|
||||
echo "${encodedContent}" | base64 -d > "${filePath}";
|
||||
`;
|
||||
await execAsyncRemote(serverId, command);
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,24 +1,3 @@
|
||||
import { findServerById } from "@/server/api/services/server";
|
||||
import { Client } from "ssh2";
|
||||
import { readSSHKey } from "../filesystem/ssh";
|
||||
|
||||
export const connectSSH = async (serverId: string) => {
|
||||
const server = await findServerById(serverId);
|
||||
if (!server.sshKeyId) throw new Error("No SSH key available for this server");
|
||||
|
||||
const keys = await readSSHKey(server.sshKeyId);
|
||||
const client = new Client();
|
||||
|
||||
return new Promise<Client>((resolve, reject) => {
|
||||
client
|
||||
.once("ready", () => resolve(client))
|
||||
.on("error", reject)
|
||||
.connect({
|
||||
host: server.ipAddress,
|
||||
port: server.port,
|
||||
username: server.username,
|
||||
privateKey: keys.privateKey,
|
||||
timeout: 99999,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -67,7 +67,7 @@ const connectToServer = async (serverId: string, logPath: string) => {
|
||||
const keys = await readSSHKey(server.sshKeyId);
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
client
|
||||
.on("ready", () => {
|
||||
.once("ready", () => {
|
||||
console.log("Client :: ready");
|
||||
const bashCommand = `
|
||||
|
||||
|
||||
Reference in New Issue
Block a user