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; };