From 9a7ed91a55843336084a315f2ea6fd4927072f5f Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 8 Dec 2024 19:37:11 -0600 Subject: [PATCH 1/3] feat: add validate server --- .../settings/servers/setup-server.tsx | 14 +- .../settings/servers/validate-server.tsx | 113 ++++++++++++++++ apps/dokploy/server/api/routers/server.ts | 29 +++++ packages/server/src/index.ts | 1 + packages/server/src/setup/server-setup.ts | 8 +- packages/server/src/setup/server-validate.ts | 121 ++++++++++++++++++ 6 files changed, 281 insertions(+), 5 deletions(-) create mode 100644 apps/dokploy/components/dashboard/settings/servers/validate-server.tsx create mode 100644 packages/server/src/setup/server-validate.ts diff --git a/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx b/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx index 119d4d29..eb0d2255 100644 --- a/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx @@ -33,6 +33,7 @@ import { useState } from "react"; import { toast } from "sonner"; import { ShowDeployment } from "../../application/deployments/show-deployment"; import { GPUSupport } from "./gpu-support"; +import { ValidateServer } from "./validate-server"; interface Props { serverId: string; @@ -90,9 +91,10 @@ export const SetupServer = ({ serverId }: Props) => { ) : (
- + SSH Keys Deployments + Validate GPU Setup {
-
+
Deployments @@ -293,6 +295,14 @@ export const SetupServer = ({ serverId }: Props) => {
+ +
+ +
+
{ + const [isRefreshing, setIsRefreshing] = useState(false); + const { data, refetch, error, isLoading, isError } = + api.server.validate.useQuery( + { serverId }, + { + enabled: !!serverId, + }, + ); + const utils = api.useUtils(); + return ( + +
+ + +
+
+
+ + Setup Validation +
+ + Check if your server is ready for deployment + +
+ +
+
+ {isError && ( + + {error.message} + + )} +
+
+ + + {isLoading ? ( +
+ + Checking Server Configuration +
+ ) : ( +
+
+

Status

+

+ Shows the configuration state that changes with the Enable + GPU +

+
+ + + + + + +
+
+
+ )} +
+
+
+
+ ); +}; diff --git a/apps/dokploy/server/api/routers/server.ts b/apps/dokploy/server/api/routers/server.ts index 0d4ef87f..c9f60e80 100644 --- a/apps/dokploy/server/api/routers/server.ts +++ b/apps/dokploy/server/api/routers/server.ts @@ -26,6 +26,7 @@ import { haveActiveServices, removeDeploymentsByServerId, serverSetup, + serverValidate, updateServerById, } from "@dokploy/server"; import { TRPCError } from "@trpc/server"; @@ -118,6 +119,34 @@ export const serverRouter = createTRPCRouter({ throw error; } }), + validate: protectedProcedure + .input(apiFindOneServer) + .query(async ({ input, ctx }) => { + try { + const server = await findServerById(input.serverId); + if (server.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to validate this server", + }); + } + const response = await serverValidate(input.serverId); + return response as unknown as { + isDockerInstalled: boolean; + isRCloneInstalled: boolean; + isSwarmInstalled: boolean; + isNixpacksInstalled: boolean; + isBuildpacksInstalled: boolean; + isMainDirectoryInstalled: boolean; + }; + } catch (error) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: error instanceof Error ? error?.message : `Error: ${error}`, + cause: error as Error, + }); + } + }), remove: protectedProcedure .input(apiRemoveServer) .mutation(async ({ input, ctx }) => { diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index b8ec30e2..f3f1e96f 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -41,6 +41,7 @@ export * from "./setup/redis-setup"; export * from "./setup/server-setup"; export * from "./setup/setup"; export * from "./setup/traefik-setup"; +export * from "./setup/server-validate"; export * from "./utils/backups/index"; export * from "./utils/backups/mariadb"; diff --git a/packages/server/src/setup/server-setup.ts b/packages/server/src/setup/server-setup.ts index 73adec43..81e49779 100644 --- a/packages/server/src/setup/server-setup.ts +++ b/packages/server/src/setup/server-setup.ts @@ -132,14 +132,16 @@ const installRequirements = async (serverId: string, logPath: string) => { echo -e "---------------------------------------------\n" echo -e "1. Installing required packages (curl, wget, git, jq, openssl). " + command_exists() { + command -v "$@" > /dev/null 2>&1 + } + ${installUtilities()} echo -e "2. Validating ports. " ${validatePorts()} - command_exists() { - command -v "$@" > /dev/null 2>&1 - } + echo -e "3. Installing RClone. " ${installRClone()} diff --git a/packages/server/src/setup/server-validate.ts b/packages/server/src/setup/server-validate.ts new file mode 100644 index 00000000..26ea671f --- /dev/null +++ b/packages/server/src/setup/server-validate.ts @@ -0,0 +1,121 @@ +import { Client } from "ssh2"; +import { findServerById } from "../services/server"; + +export const validateDocker = () => ` + if command_exists docker; then + echo true + else + echo false + fi +`; + +export const validateRClone = () => ` + if command_exists rclone; then + echo true + else + echo false + fi +`; + +export const validateSwarm = () => ` + if docker info --format '{{.Swarm.LocalNodeState}}' | grep -q 'active'; then + echo true + else + echo false + fi +`; + +export const validateNixpacks = () => ` + if command_exists nixpacks; then + echo true + else + echo false + fi +`; + +export const validateBuildpacks = () => ` + if command_exists pack; then + echo true + else + echo false + fi +`; + +export const validateMainDirectory = () => ` + if [ -d "/etc/dokploy" ]; then + echo true + else + echo false + fi +`; +export const serverValidate = async (serverId: string) => { + const client = new Client(); + const server = await findServerById(serverId); + if (!server.sshKeyId) { + throw new Error("No SSH Key found"); + } + + return new Promise((resolve, reject) => { + client + .once("ready", () => { + const bashCommand = ` + command_exists() { + command -v "$@" > /dev/null 2>&1 + } + + isDockerInstalled=$(${validateDocker()}) + isRCloneInstalled=$(${validateRClone()}) + isSwarmInstalled=$(${validateSwarm()}) + isNixpacksInstalled=$(${validateNixpacks()}) + isBuildpacksInstalled=$(${validateBuildpacks()}) + isMainDirectoryInstalled=$(${validateMainDirectory()}) + + echo "{\\"isDockerInstalled\\": $isDockerInstalled, \\"isRCloneInstalled\\": $isRCloneInstalled, \\"isSwarmInstalled\\": $isSwarmInstalled, \\"isNixpacksInstalled\\": $isNixpacksInstalled, \\"isBuildpacksInstalled\\": $isBuildpacksInstalled, \\"isMainDirectoryInstalled\\": $isMainDirectoryInstalled}" + `; + client.exec(bashCommand, (err, stream) => { + if (err) { + reject(err); + return; + } + let output = ""; + stream + .on("close", () => { + client.end(); + try { + const result = JSON.parse(output.trim()); + resolve(result); + } catch (parseError) { + reject( + new Error( + `Failed to parse output: ${parseError instanceof Error ? parseError.message : parseError}`, + ), + ); + } + }) + .on("data", (data: string) => { + output += data; + }) + .stderr.on("data", (data) => {}); + }); + }) + .on("error", (err) => { + client.end(); + if (err.level === "client-authentication") { + reject( + new Error( + `Authentication failed: Invalid SSH private key. ❌ Error: ${err.message} ${err.level}`, + ), + ); + } else { + reject(new Error(`SSH connection error: ${err.message}`)); + } + }) + .connect({ + host: server.ipAddress, + port: server.port, + username: server.username, + privateKey: server.sshKey?.privateKey, + timeout: 99999, + }); + }); +}; From 012f8ff2f530f10d6a4cf522add93b4e3feee90a Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 8 Dec 2024 20:01:37 -0600 Subject: [PATCH 2/3] feat: add validate server --- .../settings/servers/validate-server.tsx | 35 +++++++++--- apps/dokploy/server/api/routers/server.ts | 21 ++++++-- packages/server/src/setup/server-validate.ts | 53 +++++++++++++------ 3 files changed, 84 insertions(+), 25 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/servers/validate-server.tsx b/apps/dokploy/components/dashboard/settings/servers/validate-server.tsx index f556cee9..d668c5df 100644 --- a/apps/dokploy/components/dashboard/settings/servers/validate-server.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/validate-server.tsx @@ -73,25 +73,48 @@ export const ValidateServer = ({ serverId }: Props) => {

Status

- Shows the configuration state that changes with the Enable - GPU + Shows the server configuration status

+ ` if command_exists docker; then - echo true + echo "$(docker --version | awk '{print $3}' | sed 's/,//') true" else - echo false + echo "0.0.0 false" fi `; export const validateRClone = () => ` if command_exists rclone; then - echo true + echo "$(rclone --version | head -n 1 | awk '{print $2}') true" else - echo false + echo "0.0.0 false" fi `; @@ -27,17 +27,17 @@ export const validateSwarm = () => ` export const validateNixpacks = () => ` if command_exists nixpacks; then - echo true + echo "$(nixpacks --version | awk '{print $2}') true" else - echo false + echo "0.0.0 false" fi `; export const validateBuildpacks = () => ` if command_exists pack; then - echo true + echo "$(pack --version | awk '{print $1}') true" else - echo false + echo "0.0.0 false" fi `; @@ -48,6 +48,15 @@ export const validateMainDirectory = () => ` echo false fi `; + +export const validateDokployNetwork = () => ` + if docker network ls | grep -q 'dokploy-network'; then + echo true + else + echo false + fi +`; + export const serverValidate = async (serverId: string) => { const client = new Client(); const server = await findServerById(serverId); @@ -63,14 +72,28 @@ export const serverValidate = async (serverId: string) => { command -v "$@" > /dev/null 2>&1 } - isDockerInstalled=$(${validateDocker()}) - isRCloneInstalled=$(${validateRClone()}) - isSwarmInstalled=$(${validateSwarm()}) - isNixpacksInstalled=$(${validateNixpacks()}) - isBuildpacksInstalled=$(${validateBuildpacks()}) - isMainDirectoryInstalled=$(${validateMainDirectory()}) + dockerVersionEnabled=$(${validateDocker()}) + rcloneVersionEnabled=$(${validateRClone()}) + nixpacksVersionEnabled=$(${validateNixpacks()}) + buildpacksVersionEnabled=$(${validateBuildpacks()}) - echo "{\\"isDockerInstalled\\": $isDockerInstalled, \\"isRCloneInstalled\\": $isRCloneInstalled, \\"isSwarmInstalled\\": $isSwarmInstalled, \\"isNixpacksInstalled\\": $isNixpacksInstalled, \\"isBuildpacksInstalled\\": $isBuildpacksInstalled, \\"isMainDirectoryInstalled\\": $isMainDirectoryInstalled}" + dockerVersion=$(echo $dockerVersionEnabled | awk '{print $1}') + dockerEnabled=$(echo $dockerVersionEnabled | awk '{print $2}') + + rcloneVersion=$(echo $rcloneVersionEnabled | awk '{print $1}') + rcloneEnabled=$(echo $rcloneVersionEnabled | awk '{print $2}') + + nixpacksVersion=$(echo $nixpacksVersionEnabled | awk '{print $1}') + nixpacksEnabled=$(echo $nixpacksVersionEnabled | awk '{print $2}') + + buildpacksVersion=$(echo $buildpacksVersionEnabled | awk '{print $1}') + buildpacksEnabled=$(echo $buildpacksVersionEnabled | awk '{print $2}') + + isDokployNetworkInstalled=$(${validateDokployNetwork()}) + isSwarmInstalled=$(${validateSwarm()}) + isMainDirectoryInstalled=$(${validateMainDirectory()}) + + echo "{\\"docker\\": {\\"version\\": \\"$dockerVersion\\", \\"enabled\\": $dockerEnabled}, \\"rclone\\": {\\"version\\": \\"$rcloneVersion\\", \\"enabled\\": $rcloneEnabled}, \\"nixpacks\\": {\\"version\\": \\"$nixpacksVersion\\", \\"enabled\\": $nixpacksEnabled}, \\"buildpacks\\": {\\"version\\": \\"$buildpacksVersion\\", \\"enabled\\": $buildpacksEnabled}, \\"isDokployNetworkInstalled\\": $isDokployNetworkInstalled, \\"isSwarmInstalled\\": $isSwarmInstalled, \\"isMainDirectoryInstalled\\": $isMainDirectoryInstalled}" `; client.exec(bashCommand, (err, stream) => { if (err) { From 6874ede933300ea961cc32b72685de2804782577 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 8 Dec 2024 20:05:16 -0600 Subject: [PATCH 3/3] refactor: show validate server enabled --- .../dashboard/settings/servers/validate-server.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/servers/validate-server.tsx b/apps/dokploy/components/dashboard/settings/servers/validate-server.tsx index d668c5df..9a8f14c0 100644 --- a/apps/dokploy/components/dashboard/settings/servers/validate-server.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/validate-server.tsx @@ -80,7 +80,7 @@ export const ValidateServer = ({ serverId }: Props) => { label="Docker Installed" isEnabled={data?.docker?.enabled} description={ - data?.docker?.version + data?.docker?.enabled ? `Installed: ${data?.docker?.version}` : undefined } @@ -89,7 +89,7 @@ export const ValidateServer = ({ serverId }: Props) => { label="RClone Installed" isEnabled={data?.rclone?.enabled} description={ - data?.rclone?.version + data?.rclone?.enabled ? `Installed: ${data?.rclone?.version}` : undefined } @@ -98,7 +98,7 @@ export const ValidateServer = ({ serverId }: Props) => { label="Nixpacks Installed" isEnabled={data?.nixpacks?.enabled} description={ - data?.nixpacks?.version + data?.nixpacks?.enabled ? `Installed: ${data?.nixpacks?.version}` : undefined } @@ -107,7 +107,7 @@ export const ValidateServer = ({ serverId }: Props) => { label="Buildpacks Installed" isEnabled={data?.buildpacks?.enabled} description={ - data?.buildpacks?.version + data?.buildpacks?.enabled ? `Installed: ${data?.buildpacks?.version}` : undefined }