diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index f3f1e96f..41f2b0fd 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -102,11 +102,6 @@ export * from "./utils/traefik/security"; export * from "./utils/traefik/types"; export * from "./utils/traefik/web-server"; -export * from "./wss/docker-container-logs"; -export * from "./wss/docker-container-terminal"; -export * from "./wss/docker-stats"; -export * from "./wss/listen-deployment"; -export * from "./wss/terminal"; export * from "./wss/utils"; export * from "./utils/access-log/handler"; diff --git a/packages/server/src/services/auth.ts b/packages/server/src/services/auth.ts index 11e2d24c..598e39e3 100644 --- a/packages/server/src/services/auth.ts +++ b/packages/server/src/services/auth.ts @@ -7,7 +7,7 @@ import { auth, users, } from "@dokploy/server/db/schema"; -import { getPublicIpWithFallback } from "@dokploy/server/wss/terminal"; +import { getPublicIpWithFallback } from "@dokploy/server/wss/utils"; import { TRPCError } from "@trpc/server"; import * as bcrypt from "bcrypt"; import { eq } from "drizzle-orm"; diff --git a/packages/server/src/wss/docker-container-logs.ts b/packages/server/src/wss/docker-container-logs.ts deleted file mode 100644 index 75292018..00000000 --- a/packages/server/src/wss/docker-container-logs.ts +++ /dev/null @@ -1,133 +0,0 @@ -import type http from "node:http"; -import { findServerById } from "@dokploy/server/services/server"; -import { spawn } from "node-pty"; -import { Client } from "ssh2"; -import { WebSocketServer } from "ws"; -import { validateWebSocketRequest } from "../auth/auth"; -import { getShell } from "./utils"; - -export const setupDockerContainerLogsWebSocketServer = ( - server: http.Server, -) => { - const wssTerm = new WebSocketServer({ - noServer: true, - path: "/docker-container-logs", - }); - - server.on("upgrade", (req, socket, head) => { - const { pathname } = new URL(req.url || "", `http://${req.headers.host}`); - - if (pathname === "/_next/webpack-hmr") { - return; - } - if (pathname === "/docker-container-logs") { - wssTerm.handleUpgrade(req, socket, head, function done(ws) { - wssTerm.emit("connection", ws, req); - }); - } - }); - - // eslint-disable-next-line @typescript-eslint/no-misused-promises - wssTerm.on("connection", async (ws, req) => { - const url = new URL(req.url || "", `http://${req.headers.host}`); - const containerId = url.searchParams.get("containerId"); - const tail = url.searchParams.get("tail"); - const serverId = url.searchParams.get("serverId"); - const { user, session } = await validateWebSocketRequest(req); - - if (!containerId) { - ws.close(4000, "containerId no provided"); - return; - } - - if (!user || !session) { - ws.close(); - return; - } - try { - if (serverId) { - const server = await findServerById(serverId); - - if (!server.sshKeyId) return; - const client = new Client(); - new Promise((resolve, reject) => { - client - .once("ready", () => { - const command = ` - bash -c "docker container logs --tail ${tail} --follow ${containerId}" - `; - client.exec(command, (err, stream) => { - if (err) { - console.error("Execution error:", err); - reject(err); - return; - } - stream - .on("close", () => { - client.end(); - resolve(); - }) - .on("data", (data: string) => { - ws.send(data.toString()); - }) - .stderr.on("data", (data) => { - ws.send(data.toString()); - }); - }); - }) - .connect({ - host: server.ipAddress, - port: server.port, - username: server.username, - privateKey: server.sshKey?.privateKey, - timeout: 99999, - }); - }); - } else { - const shell = getShell(); - const ptyProcess = spawn( - shell, - [ - "-c", - `docker container logs --tail ${tail} --follow ${containerId}`, - ], - { - name: "xterm-256color", - cwd: process.env.HOME, - env: process.env, - encoding: "utf8", - cols: 80, - rows: 30, - }, - ); - - ptyProcess.onData((data) => { - ws.send(data); - }); - ws.on("close", () => { - ptyProcess.kill(); - }); - ws.on("message", (message) => { - try { - let command: string | Buffer[] | Buffer | ArrayBuffer; - if (Buffer.isBuffer(message)) { - command = message.toString("utf8"); - } else { - command = message; - } - ptyProcess.write(command.toString()); - } catch (error) { - // @ts-ignore - const errorMessage = error?.message as unknown as string; - ws.send(errorMessage); - } - }); - } - } catch (error) { - // @ts-ignore - const errorMessage = error?.message as unknown as string; - - ws.send(errorMessage); - } - }); -}; diff --git a/packages/server/src/wss/docker-container-terminal.ts b/packages/server/src/wss/docker-container-terminal.ts deleted file mode 100644 index 0cb174b6..00000000 --- a/packages/server/src/wss/docker-container-terminal.ts +++ /dev/null @@ -1,152 +0,0 @@ -import type http from "node:http"; -import { findServerById } from "@dokploy/server/services/server"; -import { spawn } from "node-pty"; -import { Client } from "ssh2"; -import { WebSocketServer } from "ws"; -import { validateWebSocketRequest } from "../auth/auth"; -import { getShell } from "./utils"; - -export const setupDockerContainerTerminalWebSocketServer = ( - server: http.Server, -) => { - const wssTerm = new WebSocketServer({ - noServer: true, - path: "/docker-container-terminal", - }); - - server.on("upgrade", (req, socket, head) => { - const { pathname } = new URL(req.url || "", `http://${req.headers.host}`); - - if (pathname === "/_next/webpack-hmr") { - return; - } - if (pathname === "/docker-container-terminal") { - wssTerm.handleUpgrade(req, socket, head, function done(ws) { - wssTerm.emit("connection", ws, req); - }); - } - }); - - // eslint-disable-next-line @typescript-eslint/no-misused-promises - wssTerm.on("connection", async (ws, req) => { - const url = new URL(req.url || "", `http://${req.headers.host}`); - const containerId = url.searchParams.get("containerId"); - const activeWay = url.searchParams.get("activeWay"); - const serverId = url.searchParams.get("serverId"); - const { user, session } = await validateWebSocketRequest(req); - - if (!containerId) { - ws.close(4000, "containerId no provided"); - return; - } - - if (!user || !session) { - ws.close(); - return; - } - try { - if (serverId) { - const server = await findServerById(serverId); - if (!server.sshKeyId) - throw new Error("No SSH key available for this server"); - - const conn = new Client(); - let stdout = ""; - let stderr = ""; - conn - .once("ready", () => { - conn.exec( - `docker exec -it ${containerId} ${activeWay}`, - { pty: true }, - (err, stream) => { - if (err) throw err; - - stream - .on("close", (code: number, signal: string) => { - ws.send(`\nContainer closed with code: ${code}\n`); - conn.end(); - }) - .on("data", (data: string) => { - stdout += data.toString(); - ws.send(data.toString()); - }) - .stderr.on("data", (data) => { - stderr += data.toString(); - ws.send(data.toString()); - console.error("Error: ", data.toString()); - }); - - ws.on("message", (message) => { - try { - let command: string | Buffer[] | Buffer | ArrayBuffer; - if (Buffer.isBuffer(message)) { - command = message.toString("utf8"); - } else { - command = message; - } - stream.write(command.toString()); - } catch (error) { - // @ts-ignore - const errorMessage = error?.message as unknown as string; - ws.send(errorMessage); - } - }); - - ws.on("close", () => { - stream.end(); - }); - }, - ); - }) - .connect({ - host: server.ipAddress, - port: server.port, - username: server.username, - privateKey: server.sshKey?.privateKey, - timeout: 99999, - }); - } else { - const shell = getShell(); - const ptyProcess = spawn( - shell, - ["-c", `docker exec -it ${containerId} ${activeWay}`], - { - name: "xterm-256color", - cwd: process.env.HOME, - env: process.env, - encoding: "utf8", - cols: 80, - rows: 30, - }, - ); - - ptyProcess.onData((data) => { - ws.send(data); - }); - ws.on("close", () => { - ptyProcess.kill(); - }); - ws.on("message", (message) => { - try { - let command: string | Buffer[] | Buffer | ArrayBuffer; - if (Buffer.isBuffer(message)) { - command = message.toString("utf8"); - } else { - command = message; - } - ptyProcess.write(command.toString()); - } catch (error) { - // @ts-ignore - const errorMessage = error?.message as unknown as string; - ws.send(errorMessage); - } - }); - } - } catch (error) { - // @ts-ignore - const errorMessage = error?.message as unknown as string; - - ws.send(errorMessage); - } - }); -}; diff --git a/packages/server/src/wss/docker-stats.ts b/packages/server/src/wss/docker-stats.ts deleted file mode 100644 index ed1dc46f..00000000 --- a/packages/server/src/wss/docker-stats.ts +++ /dev/null @@ -1,96 +0,0 @@ -import type http from "node:http"; -import { WebSocketServer } from "ws"; -import { validateWebSocketRequest } from "../auth/auth"; -import { docker } from "../constants"; -import { - getLastAdvancedStatsFile, - recordAdvancedStats, -} from "../monitoring/utilts"; - -export const setupDockerStatsMonitoringSocketServer = ( - server: http.Server, -) => { - const wssTerm = new WebSocketServer({ - noServer: true, - path: "/listen-docker-stats-monitoring", - }); - - server.on("upgrade", (req, socket, head) => { - const { pathname } = new URL(req.url || "", `http://${req.headers.host}`); - - if (pathname === "/_next/webpack-hmr") { - return; - } - if (pathname === "/listen-docker-stats-monitoring") { - wssTerm.handleUpgrade(req, socket, head, function done(ws) { - wssTerm.emit("connection", ws, req); - }); - } - }); - - wssTerm.on("connection", async (ws, req) => { - const url = new URL(req.url || "", `http://${req.headers.host}`); - const appName = url.searchParams.get("appName"); - const appType = (url.searchParams.get("appType") || "application") as - | "application" - | "stack" - | "docker-compose"; - const { user, session } = await validateWebSocketRequest(req); - - if (!appName) { - ws.close(4000, "appName no provided"); - return; - } - - if (!user || !session) { - ws.close(); - return; - } - const intervalId = setInterval(async () => { - try { - const filter = { - status: ["running"], - ...(appType === "application" && { - label: [`com.docker.swarm.service.name=${appName}`], - }), - ...(appType === "stack" && { - label: [`com.docker.swarm.task.name=${appName}`], - }), - ...(appType === "docker-compose" && { - name: [appName], - }), - }; - - const containers = await docker.listContainers({ - filters: JSON.stringify(filter), - }); - - const container = containers[0]; - if (!container || container?.State !== "running") { - ws.close(4000, "Container not running"); - return; - } - - const stats = await docker.getContainer(container.Id).stats({ - stream: false, - }); - - await recordAdvancedStats(stats, appName); - const data = await getLastAdvancedStatsFile(appName); - - ws.send( - JSON.stringify({ - data, - }), - ); - } catch (error) { - // @ts-ignore - ws.close(4000, `Error: ${error.message}`); - } - }, 1300); - - ws.on("close", () => { - clearInterval(intervalId); - }); - }); -}; diff --git a/packages/server/src/wss/listen-deployment.ts b/packages/server/src/wss/listen-deployment.ts deleted file mode 100644 index 363a3cc8..00000000 --- a/packages/server/src/wss/listen-deployment.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { spawn } from "node:child_process"; -import type http from "node:http"; -import { findServerById } from "@dokploy/server/services/server"; -import { Client } from "ssh2"; -import { WebSocketServer } from "ws"; -import { validateWebSocketRequest } from "../auth/auth"; - -export const setupDeploymentLogsWebSocketServer = ( - server: http.Server, -) => { - const wssTerm = new WebSocketServer({ - noServer: true, - path: "/listen-deployment", - }); - - server.on("upgrade", (req, socket, head) => { - const { pathname } = new URL(req.url || "", `http://${req.headers.host}`); - - if (pathname === "/_next/webpack-hmr") { - return; - } - if (pathname === "/listen-deployment") { - wssTerm.handleUpgrade(req, socket, head, function done(ws) { - wssTerm.emit("connection", ws, req); - }); - } - }); - - wssTerm.on("connection", async (ws, req) => { - const url = new URL(req.url || "", `http://${req.headers.host}`); - const logPath = url.searchParams.get("logPath"); - const serverId = url.searchParams.get("serverId"); - const { user, session } = await validateWebSocketRequest(req); - - if (!logPath) { - ws.close(4000, "logPath no provided"); - return; - } - - if (!user || !session) { - ws.close(); - return; - } - - try { - if (serverId) { - const server = await findServerById(serverId); - - if (!server.sshKeyId) return; - const client = new Client(); - new Promise((resolve, reject) => { - client - .on("ready", () => { - const command = ` - tail -n +1 -f ${logPath}; - `; - client.exec(command, (err, stream) => { - if (err) { - console.error("Execution error:", err); - reject(err); - return; - } - stream - .on("close", () => { - client.end(); - resolve(); - }) - .on("data", (data: string) => { - ws.send(data.toString()); - }) - .stderr.on("data", (data) => { - ws.send(data.toString()); - }); - }); - }) - .connect({ - host: server.ipAddress, - port: server.port, - username: server.username, - privateKey: server.sshKey?.privateKey, - timeout: 99999, - }); - }); - } else { - const tail = spawn("tail", ["-n", "+1", "-f", logPath]); - - tail.stdout.on("data", (data) => { - ws.send(data.toString()); - }); - - tail.stderr.on("data", (data) => { - ws.send(new Error(`tail error: ${data.toString()}`).message); - }); - } - } catch (error) { - // @ts-ignore - // const errorMessage = error?.message as unknown as string; - ws.send(errorMessage); - } - }); -}; diff --git a/packages/server/src/wss/terminal.ts b/packages/server/src/wss/terminal.ts deleted file mode 100644 index 562040d7..00000000 --- a/packages/server/src/wss/terminal.ts +++ /dev/null @@ -1,107 +0,0 @@ -import type http from "node:http"; -import path from "node:path"; -import { findServerById } from "@dokploy/server/services/server"; -import { spawn } from "node-pty"; -import { publicIpv4, publicIpv6 } from "public-ip"; -import { WebSocketServer } from "ws"; -import { validateWebSocketRequest } from "../auth/auth"; -import { paths } from "../constants"; - -export const getPublicIpWithFallback = async () => { - // @ts-ignore - let ip = null; - try { - ip = await publicIpv4(); - } catch (error) { - console.log( - "Error to obtain public IPv4 address, falling back to IPv6", - // @ts-ignore - error.message, - ); - try { - ip = await publicIpv6(); - } catch (error) { - // @ts-ignore - console.error("Error to obtain public IPv6 address", error.message); - ip = null; - } - } - return ip; -}; - -export const setupTerminalWebSocketServer = ( - server: http.Server, -) => { - const wssTerm = new WebSocketServer({ - noServer: true, - path: "/terminal", - }); - - server.on("upgrade", (req, socket, head) => { - const { pathname } = new URL(req.url || "", `http://${req.headers.host}`); - if (pathname === "/_next/webpack-hmr") { - return; - } - if (pathname === "/terminal") { - wssTerm.handleUpgrade(req, socket, head, function done(ws) { - wssTerm.emit("connection", ws, req); - }); - } - }); - - wssTerm.on("connection", async (ws, req) => { - const url = new URL(req.url || "", `http://${req.headers.host}`); - const serverId = url.searchParams.get("serverId"); - const { user, session } = await validateWebSocketRequest(req); - if (!user || !session || !serverId) { - ws.close(); - return; - } - - const server = await findServerById(serverId); - - if (!server) { - ws.close(); - return; - } - const { SSH_PATH } = paths(); - const privateKey = path.join(SSH_PATH, `${server.sshKeyId}_rsa`); - const sshCommand = [ - "ssh", - "-o", - "StrictHostKeyChecking=no", - "-i", - privateKey, - `${server.username}@${server.ipAddress}`, - ]; - const ptyProcess = spawn("ssh", sshCommand.slice(1), { - name: "xterm-256color", - cwd: process.env.HOME, - env: process.env, - encoding: "utf8", - cols: 80, - rows: 30, - }); - - ptyProcess.onData((data) => { - ws.send(data); - }); - ws.on("message", (message) => { - try { - let command: string | Buffer[] | Buffer | ArrayBuffer; - if (Buffer.isBuffer(message)) { - command = message.toString("utf8"); - } else { - command = message; - } - ptyProcess.write(command.toString()); - } catch (error) { - console.log(error); - } - }); - - ws.on("close", () => { - ptyProcess.kill(); - }); - }); -}; diff --git a/packages/server/src/wss/utils.ts b/packages/server/src/wss/utils.ts index b5567127..8c6217a2 100644 --- a/packages/server/src/wss/utils.ts +++ b/packages/server/src/wss/utils.ts @@ -1,4 +1,5 @@ import os from "node:os"; +import { publicIpv4, publicIpv6 } from "public-ip"; export const getShell = () => { switch (os.platform()) { @@ -10,3 +11,25 @@ export const getShell = () => { return "bash"; } }; + +export const getPublicIpWithFallback = async () => { + // @ts-ignore + let ip = null; + try { + ip = await publicIpv4(); + } catch (error) { + console.log( + "Error to obtain public IPv4 address, falling back to IPv6", + // @ts-ignore + error.message, + ); + try { + ip = await publicIpv6(); + } catch (error) { + // @ts-ignore + console.error("Error to obtain public IPv6 address", error.message); + ip = null; + } + } + return ip; +};