mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-10-19 15:11:36 +00:00
Merge pull request #5246 from Dokploy/fix/traefik-remote-config-sftp-write
fix: write remote traefik config via SFTP instead of exec+base64
This commit is contained in:
@@ -4,6 +4,20 @@ import { findServerById } from "@dokploy/server/services/server";
|
||||
import { Client } from "ssh2";
|
||||
import { ExecError } from "./ExecError";
|
||||
|
||||
export class WriteFileRemoteError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public readonly context: {
|
||||
remotePath: string;
|
||||
serverId: string;
|
||||
originalError: Error;
|
||||
},
|
||||
) {
|
||||
super(message);
|
||||
this.name = "WriteFileRemoteError";
|
||||
}
|
||||
}
|
||||
|
||||
// Re-export ExecError for easier imports
|
||||
export { ExecError } from "./ExecError";
|
||||
|
||||
@@ -21,11 +35,11 @@ export const execAsync = async (
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
// @ts-ignore - exec error has these properties
|
||||
// @ts-expect-error - exec error has these properties
|
||||
const exitCode = error.code;
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const stdout = error.stdout?.toString() || "";
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const stderr = error.stderr?.toString() || "";
|
||||
|
||||
throw new ExecError(`Command execution failed: ${error.message}`, {
|
||||
@@ -61,7 +75,7 @@ export const execAsyncStream = (
|
||||
command,
|
||||
stdout: stdoutComplete,
|
||||
stderr: stderrComplete,
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
exitCode: error.code,
|
||||
originalError: error,
|
||||
}),
|
||||
@@ -249,6 +263,65 @@ export const execAsyncRemote = async (
|
||||
});
|
||||
};
|
||||
|
||||
export const writeFileRemote = async (
|
||||
serverId: string,
|
||||
remotePath: string,
|
||||
content: string,
|
||||
): Promise<void> => {
|
||||
const server = await findServerById(serverId);
|
||||
if (!server.sshKeyId) throw new Error("No SSH key available for this server");
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const conn = new Client();
|
||||
conn
|
||||
.once("ready", () => {
|
||||
conn.sftp((err, sftp) => {
|
||||
if (err) {
|
||||
conn.end();
|
||||
reject(
|
||||
new WriteFileRemoteError(`SFTP session failed: ${err.message}`, {
|
||||
remotePath,
|
||||
serverId,
|
||||
originalError: err,
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
sftp.writeFile(remotePath, content, (writeErr) => {
|
||||
conn.end();
|
||||
if (writeErr) {
|
||||
reject(
|
||||
new WriteFileRemoteError(
|
||||
`Failed to write remote file ${remotePath}: ${writeErr.message}`,
|
||||
{ remotePath, serverId, originalError: writeErr },
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
})
|
||||
.on("error", (err) => {
|
||||
conn.end();
|
||||
reject(
|
||||
new WriteFileRemoteError(`SSH connection error: ${err.message}`, {
|
||||
remotePath,
|
||||
serverId,
|
||||
originalError: err,
|
||||
}),
|
||||
);
|
||||
})
|
||||
.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));
|
||||
};
|
||||
|
||||
@@ -5,8 +5,7 @@ import { paths } from "@dokploy/server/constants";
|
||||
import type { Domain } from "@dokploy/server/services/domain";
|
||||
import { quote } from "shell-quote";
|
||||
import { parse, stringify } from "yaml";
|
||||
import { encodeBase64 } from "../docker/utils";
|
||||
import { execAsync, execAsyncRemote } from "../process/execAsync";
|
||||
import { execAsync, execAsyncRemote, writeFileRemote } from "../process/execAsync";
|
||||
import type { FileConfig, HttpLoadBalancerService } from "./file-types";
|
||||
|
||||
export const createTraefikConfig = (appName: string) => {
|
||||
@@ -228,11 +227,7 @@ export const writeConfigRemote = async (
|
||||
try {
|
||||
const { DYNAMIC_TRAEFIK_PATH } = paths(true);
|
||||
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
|
||||
const encoded = encodeBase64(traefikConfig);
|
||||
await execAsyncRemote(
|
||||
serverId,
|
||||
`echo "${encoded}" | base64 -d > ${quote([configPath])}`,
|
||||
);
|
||||
await writeFileRemote(serverId, configPath, traefikConfig);
|
||||
} catch (e) {
|
||||
console.error("Error saving the YAML config file:", e);
|
||||
}
|
||||
@@ -246,11 +241,7 @@ export const writeTraefikConfigInPath = async (
|
||||
try {
|
||||
const configPath = path.join(pathFile);
|
||||
if (serverId) {
|
||||
const encoded = encodeBase64(traefikConfig);
|
||||
await execAsyncRemote(
|
||||
serverId,
|
||||
`echo "${encoded}" | base64 -d > ${quote([configPath])}`,
|
||||
);
|
||||
await writeFileRemote(serverId, configPath, traefikConfig);
|
||||
} else {
|
||||
fs.writeFileSync(configPath, traefikConfig, "utf8");
|
||||
}
|
||||
@@ -282,11 +273,7 @@ export const writeTraefikConfigRemote = async (
|
||||
const { DYNAMIC_TRAEFIK_PATH } = paths(true);
|
||||
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
|
||||
const yamlStr = stringify(traefikConfig);
|
||||
const encoded = encodeBase64(yamlStr);
|
||||
await execAsyncRemote(
|
||||
serverId,
|
||||
`echo "${encoded}" | base64 -d > ${quote([configPath])}`,
|
||||
);
|
||||
await writeFileRemote(serverId, configPath, yamlStr);
|
||||
} catch (e) {
|
||||
console.error("Error saving the YAML config file:", e);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user