From c31ed2b2b0e72fab988e215c420ddc5a235e7230 Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Mon, 6 Jan 2025 11:38:08 +0100 Subject: [PATCH 1/6] feat: add iproute2 as dependency to dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 51be6469..5fa0ee2c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ WORKDIR /app # Set production ENV NODE_ENV=production -RUN apt-get update && apt-get install -y curl unzip apache2-utils && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y curl unzip apache2-utils iproute2 && rm -rf /var/lib/apt/lists/* # Copy only the necessary files COPY --from=build /prod/dokploy/.next ./.next From aa3541b67b2717a4e08bd50c50665c850ec7fe3d Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Mon, 6 Jan 2025 11:39:19 +0100 Subject: [PATCH 2/6] feat: add utils to get docker host and check if running on WSL --- apps/dokploy/server/utils/docker.ts | 40 +++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 apps/dokploy/server/utils/docker.ts diff --git a/apps/dokploy/server/utils/docker.ts b/apps/dokploy/server/utils/docker.ts new file mode 100644 index 00000000..7577c844 --- /dev/null +++ b/apps/dokploy/server/utils/docker.ts @@ -0,0 +1,40 @@ +import { execAsync } from "@dokploy/server"; + +/** Returns if the current operating system is Windows Subsystem for Linux (WSL). */ +export const isWSL = async () => { + try { + const { stdout } = await execAsync("uname -r"); + const isWSL = stdout.includes("microsoft"); + return isWSL; + } catch (error) { + return false; + } +}; + +/** Returns the Docker host IP address. */ +export const getDockerHost = async (): Promise => { + if (process.env.NODE_ENV === "production") { + if (process.platform === "linux" && !isWSL()) { + try { + // Try to get the Docker bridge IP first + const { stdout } = await execAsync( + "ip route | awk '/default/ {print $3}'", + ); + + const hostIp = stdout.trim(); + if (!hostIp) { + throw new Error("Failed to get Docker host IP"); + } + + return hostIp; + } catch (error) { + console.error("Failed to get Docker host IP:", error); + return "172.17.0.1"; // Default Docker bridge network IP + } + } + + return "host.docker.internal"; + } + + return "localhost"; +}; From d0a5427c668d118d223df57baa55948e01ec0433 Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Mon, 6 Jan 2025 11:41:34 +0100 Subject: [PATCH 3/6] fix: connecting to local server terminal --- apps/dokploy/server/wss/terminal.ts | 54 +++++++++++++++++++------ apps/dokploy/server/wss/utils.ts | 62 +++++++++-------------------- 2 files changed, 60 insertions(+), 56 deletions(-) diff --git a/apps/dokploy/server/wss/terminal.ts b/apps/dokploy/server/wss/terminal.ts index 8c661c8a..87b096c0 100644 --- a/apps/dokploy/server/wss/terminal.ts +++ b/apps/dokploy/server/wss/terminal.ts @@ -8,6 +8,17 @@ import { publicIpv4, publicIpv6 } from "public-ip"; import { Client, type ConnectConfig } from "ssh2"; import { WebSocketServer } from "ws"; import { setupLocalServerSSHKey } from "./utils"; +import { getDockerHost } from "../utils/docker"; + +const COMMAND_TO_ALLOW_LOCAL_ACCESS = ` +# ---------------------------------------- + mkdir -p $HOME/.ssh && \\ + chmod 700 $HOME/.ssh && \\ + touch $HOME/.ssh/authorized_keys && \\ + chmod 600 $HOME/.ssh/authorized_keys && \\ + cat /etc/dokploy/ssh/auto_generated-dokploy-local.pub >> $HOME/.ssh/authorized_keys && \\ + echo "✓ Dokploy SSH key added successfully. Reopen the terminal in Dokploy to reconnect." +# ----------------------------------------`; export const getPublicIpWithFallback = async () => { // @ts-ignore @@ -73,20 +84,31 @@ export const setupTerminalWebSocketServer = ( return; } - ws.send("Setting up private SSH key...\n"); - const privateKey = await setupLocalServerSSHKey(); + try { + ws.send("Setting up private SSH key...\n"); + const privateKey = await setupLocalServerSSHKey(); - if (!privateKey) { + if (!privateKey) { + ws.close(); + return; + } + + const dockerHost = await getDockerHost(); + + ws.send(`Found Docker host: ${dockerHost}\n`); + + connectionDetails = { + host: dockerHost, + port, + username, + privateKey, + }; + } catch (error) { + console.error(`Error setting up private SSH key: ${error}`); + ws.send(`Error setting up private SSH key: ${error}`); ws.close(); return; } - - connectionDetails = { - host: "localhost", - port, - username, - privateKey, - }; } else { const server = await findServerById(serverId); @@ -161,9 +183,15 @@ export const setupTerminalWebSocketServer = ( }) .on("error", (err) => { if (err.level === "client-authentication") { - ws.send( - `Authentication failed: Unauthorized ${isLocalServer ? "" : "private SSH key or "}username.\n❌ Error: ${err.message} ${err.level}`, - ); + if (isLocalServer) { + ws.send( + `Authentication failed: Please run the command below on your server to allow access. Make sure to run it as the same user as the one configured in connection settings:${COMMAND_TO_ALLOW_LOCAL_ACCESS}\nAfter running the command, reopen this window to reconnect. This procedure is required only once.`, + ); + } else { + ws.send( + `Authentication failed: Unauthorized private SSH key or username.\n❌ Error: ${err.message} ${err.level}`, + ); + } } else { ws.send(`SSH connection error: ${err.message} ❌ `); } diff --git a/apps/dokploy/server/wss/utils.ts b/apps/dokploy/server/wss/utils.ts index 9c78869d..b593595b 100644 --- a/apps/dokploy/server/wss/utils.ts +++ b/apps/dokploy/server/wss/utils.ts @@ -1,18 +1,8 @@ -import { execAsync } from "@dokploy/server"; +import { execAsync, paths } from "@dokploy/server"; import os from "node:os"; import path from "node:path"; import fs from "node:fs"; -const HOME_PATH = process.env.HOME || process.env.USERPROFILE || "/"; - -const LOCAL_SSH_KEY_PATH = path.join( - HOME_PATH, - ".ssh", - "auto_generated-dokploy-local", -); - -const AUTHORIZED_KEYS_PATH = path.join(HOME_PATH, ".ssh", "authorized_keys"); - export const getShell = () => { switch (os.platform()) { case "win32": @@ -24,39 +14,25 @@ export const getShell = () => { } }; -/** Returns private SSH key for dokploy local server terminal. Uses already created SSH key or generates a new SSH key, also automatically appends the public key to `authorized_keys`, creating the file if needed. */ +/** Returns private SSH key for dokploy local server terminal. Uses already created SSH key or generates a new SSH key. + * + * In case of permission failures when running locally, run the command below: + +``` +sudo chown -R $USER:$USER /etc/dokploy/ssh +``` + +*/ export const setupLocalServerSSHKey = async () => { - try { - if (!fs.existsSync(LOCAL_SSH_KEY_PATH)) { - // Generate new SSH key if it hasn't been created yet - await execAsync( - `ssh-keygen -t rsa -b 4096 -f ${LOCAL_SSH_KEY_PATH} -N ""`, - ); - } + const { SSH_PATH } = paths(true); + const sshKeyPath = path.join(SSH_PATH, "auto_generated-dokploy-local"); - const privateKey = fs.readFileSync(LOCAL_SSH_KEY_PATH, "utf8"); - const publicKey = fs.readFileSync(`${LOCAL_SSH_KEY_PATH}.pub`, "utf8"); - const authKeyContent = `${publicKey}\n`; - - if (!fs.existsSync(AUTHORIZED_KEYS_PATH)) { - // Create authorized_keys if it doesn't exist yet - fs.writeFileSync(AUTHORIZED_KEYS_PATH, authKeyContent, { mode: 0o600 }); - return privateKey; - } - - const existingAuthKeys = fs.readFileSync(AUTHORIZED_KEYS_PATH, "utf8"); - if (existingAuthKeys.includes(publicKey)) { - return privateKey; - } - - // Append the public key to authorized_keys - fs.appendFileSync(AUTHORIZED_KEYS_PATH, authKeyContent, { - mode: 0o600, - }); - - return privateKey; - } catch (error) { - console.error("Error getting private SSH key for local terminal:", error); - return ""; + if (!fs.existsSync(sshKeyPath)) { + // Generate new SSH key if it hasn't been created yet + await execAsync(`ssh-keygen -t rsa -b 4096 -f ${sshKeyPath} -N ""`); } + + const privateKey = fs.readFileSync(sshKeyPath, "utf8"); + + return privateKey; }; From 76c6d0256613aff49b619b5a5e34053808c32157 Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Mon, 6 Jan 2025 12:09:15 +0100 Subject: [PATCH 4/6] feat: add ssh key comment for auto generated key --- apps/dokploy/server/wss/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/server/wss/utils.ts b/apps/dokploy/server/wss/utils.ts index b593595b..e98e5016 100644 --- a/apps/dokploy/server/wss/utils.ts +++ b/apps/dokploy/server/wss/utils.ts @@ -29,7 +29,7 @@ export const setupLocalServerSSHKey = async () => { if (!fs.existsSync(sshKeyPath)) { // Generate new SSH key if it hasn't been created yet - await execAsync(`ssh-keygen -t rsa -b 4096 -f ${sshKeyPath} -N ""`); + await execAsync(`ssh-keygen -t rsa -b 4096 -f ${sshKeyPath} -N "" -C "dokploy-local-access"`); } const privateKey = fs.readFileSync(sshKeyPath, "utf8"); From 41a970c526ed588cae852aa5608957af5fb33db7 Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Mon, 6 Jan 2025 12:09:54 +0100 Subject: [PATCH 5/6] feat: add permission grant command when key generation fails --- apps/dokploy/server/wss/terminal.ts | 18 +++++++++++++++++- apps/dokploy/server/wss/utils.ts | 7 ------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/apps/dokploy/server/wss/terminal.ts b/apps/dokploy/server/wss/terminal.ts index 87b096c0..9dafba22 100644 --- a/apps/dokploy/server/wss/terminal.ts +++ b/apps/dokploy/server/wss/terminal.ts @@ -20,6 +20,12 @@ const COMMAND_TO_ALLOW_LOCAL_ACCESS = ` echo "✓ Dokploy SSH key added successfully. Reopen the terminal in Dokploy to reconnect." # ----------------------------------------`; +const COMMAND_TO_GRANT_PERMISSION_ACCESS = ` +# ---------------------------------------- + sudo chown -R $USER:$USER /etc/dokploy/ssh +# ---------------------------------------- +`; + export const getPublicIpWithFallback = async () => { // @ts-ignore let ip = null; @@ -105,7 +111,17 @@ export const setupTerminalWebSocketServer = ( }; } catch (error) { console.error(`Error setting up private SSH key: ${error}`); - ws.send(`Error setting up private SSH key: ${error}`); + ws.send(`Error setting up private SSH key: ${error}\n`); + + if ( + error instanceof Error && + error.message.includes("Permission denied") + ) { + ws.send( + `Please run the following command on your server to grant permission access and then reopen this window to reconnect:${COMMAND_TO_GRANT_PERMISSION_ACCESS}`, + ); + } + ws.close(); return; } diff --git a/apps/dokploy/server/wss/utils.ts b/apps/dokploy/server/wss/utils.ts index e98e5016..cd50130c 100644 --- a/apps/dokploy/server/wss/utils.ts +++ b/apps/dokploy/server/wss/utils.ts @@ -15,13 +15,6 @@ export const getShell = () => { }; /** Returns private SSH key for dokploy local server terminal. Uses already created SSH key or generates a new SSH key. - * - * In case of permission failures when running locally, run the command below: - -``` -sudo chown -R $USER:$USER /etc/dokploy/ssh -``` - */ export const setupLocalServerSSHKey = async () => { const { SSH_PATH } = paths(true); From ee9f4796c3ee80dac546df48bee65281e51c3bb4 Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Mon, 6 Jan 2025 12:12:23 +0100 Subject: [PATCH 6/6] style: remove spaces from commands --- apps/dokploy/server/wss/terminal.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/dokploy/server/wss/terminal.ts b/apps/dokploy/server/wss/terminal.ts index 9dafba22..3f0d6a80 100644 --- a/apps/dokploy/server/wss/terminal.ts +++ b/apps/dokploy/server/wss/terminal.ts @@ -12,17 +12,17 @@ import { getDockerHost } from "../utils/docker"; const COMMAND_TO_ALLOW_LOCAL_ACCESS = ` # ---------------------------------------- - mkdir -p $HOME/.ssh && \\ - chmod 700 $HOME/.ssh && \\ - touch $HOME/.ssh/authorized_keys && \\ - chmod 600 $HOME/.ssh/authorized_keys && \\ - cat /etc/dokploy/ssh/auto_generated-dokploy-local.pub >> $HOME/.ssh/authorized_keys && \\ - echo "✓ Dokploy SSH key added successfully. Reopen the terminal in Dokploy to reconnect." +mkdir -p $HOME/.ssh && \\ +chmod 700 $HOME/.ssh && \\ +touch $HOME/.ssh/authorized_keys && \\ +chmod 600 $HOME/.ssh/authorized_keys && \\ +cat /etc/dokploy/ssh/auto_generated-dokploy-local.pub >> $HOME/.ssh/authorized_keys && \\ +echo "✓ Dokploy SSH key added successfully. Reopen the terminal in Dokploy to reconnect." # ----------------------------------------`; const COMMAND_TO_GRANT_PERMISSION_ACCESS = ` # ---------------------------------------- - sudo chown -R $USER:$USER /etc/dokploy/ssh +sudo chown -R $USER:$USER /etc/dokploy/ssh # ---------------------------------------- `;