feat: add dokploy server gpu setup

This commit is contained in:
vishalkadam47
2024-11-07 02:52:41 +05:30
parent b53da82204
commit 2e6d9c34c0
5 changed files with 104 additions and 42 deletions

View File

@@ -13,6 +13,7 @@ import {
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { toast } from "sonner"; import { toast } from "sonner";
import { ShowModalLogs } from "../../web-server/show-modal-logs"; import { ShowModalLogs } from "../../web-server/show-modal-logs";
import { GPUSupportModal } from "../gpu-support-modal";
export const ShowDokployActions = () => { export const ShowDokployActions = () => {
const { mutateAsync: reloadServer, isLoading } = const { mutateAsync: reloadServer, isLoading } =
@@ -45,6 +46,7 @@ export const ShowDokployActions = () => {
<ShowModalLogs appName="dokploy"> <ShowModalLogs appName="dokploy">
<span>Watch logs</span> <span>Watch logs</span>
</ShowModalLogs> </ShowModalLogs>
<GPUSupportModal />
</DropdownMenuGroup> </DropdownMenuGroup>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>

View File

@@ -0,0 +1,36 @@
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
import { useState } from "react";
import { GPUSupport } from "./gpu-support";
export const GPUSupportModal = () => {
const [isOpen, setIsOpen] = useState(false);
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<DropdownMenuItem
className="w-full cursor-pointer"
onSelect={(e) => e.preventDefault()}
>
<span>GPU Setup</span>
</DropdownMenuItem>
</DialogTrigger>
<DialogContent className="sm:max-w-4xl overflow-y-auto max-h-screen">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
Dokploy Server GPU Setup
</DialogTitle>
</DialogHeader>
<GPUSupport serverId="" />
</DialogContent>
</Dialog>
);
};

View File

@@ -26,7 +26,7 @@ export function GPUSupport({ serverId }: GPUSupportProps) {
api.settings.checkGPUStatus.useQuery( api.settings.checkGPUStatus.useQuery(
{ serverId }, { serverId },
{ {
enabled: !!serverId, enabled: serverId !== undefined,
refetchInterval: 5000, refetchInterval: 5000,
}, },
); );
@@ -38,17 +38,20 @@ export function GPUSupport({ serverId }: GPUSupportProps) {
onSuccess: async () => { onSuccess: async () => {
toast.success("GPU support enabled successfully"); toast.success("GPU support enabled successfully");
setIsLoading(false); setIsLoading(false);
await utils.settings.checkGPUStatus.invalidate({ serverId });
await Promise.all([
utils.settings.checkGPUStatus.invalidate({ serverId }),
utils.server.invalidate(),
]);
}, },
onError: (error) => { onError: (error) => {
if (error instanceof TRPCClientError) { if (error instanceof TRPCClientError) {
const errorMessage = error.message; const errorMessage = error.message;
if (errorMessage.includes("permission denied")) { if (
toast.error("Permission denied. Please ensure proper sudo access."); errorMessage.includes(
"Permission denied. Please ensure proper sudo access.",
) ||
errorMessage.includes("sudo access required")
) {
toast.error(
"Administrator privileges required. Please enter your password when prompted.",
);
} else if (errorMessage.includes("Failed to configure GPU")) { } else if (errorMessage.includes("Failed to configure GPU")) {
toast.error( toast.error(
"GPU configuration failed. Please check system requirements.", "GPU configuration failed. Please check system requirements.",
@@ -59,13 +62,12 @@ export function GPUSupport({ serverId }: GPUSupportProps) {
} else { } else {
toast.error("Failed to enable GPU support. Please check server logs."); toast.error("Failed to enable GPU support. Please check server logs.");
} }
setIsLoading(false); setIsLoading(false);
}, },
}); });
const handleEnableGPU = async () => { const handleEnableGPU = async () => {
if (!serverId) { if (serverId === undefined) {
toast.error("No server selected"); toast.error("No server selected");
return; return;
} }
@@ -99,7 +101,7 @@ export function GPUSupport({ serverId }: GPUSupportProps) {
> >
<Button <Button
isLoading={isLoading} isLoading={isLoading}
disabled={isLoading || !serverId || isChecking} disabled={isLoading || serverId === undefined || isChecking}
> >
{isLoading {isLoading
? "Enabling GPU..." ? "Enabling GPU..."
@@ -227,7 +229,7 @@ interface StatusRowProps {
showIcon?: boolean; showIcon?: boolean;
} }
function StatusRow({ export function StatusRow({
label, label,
isEnabled, isEnabled,
description, description,

View File

@@ -661,33 +661,20 @@ export const settingsRouter = createTRPCRouter({
setupGPU: adminProcedure setupGPU: adminProcedure
.input( .input(
z.object({ z.object({
serverId: z.string(), serverId: z.string().optional(),
}), }),
) )
.mutation(async ({ input }) => { .mutation(async ({ input }) => {
if (IS_CLOUD) {
throw new Error("GPU setup is not available in cloud mode");
}
try { try {
if (IS_CLOUD) {
return { success: true };
}
if (!input.serverId) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Server ID is required",
});
}
await setupGPUSupport(input.serverId); await setupGPUSupport(input.serverId);
return { success: true }; return { success: true };
} catch (error) { } catch (error) {
throw new TRPCError({ console.error("GPU Setup Error:", error);
code: "INTERNAL_SERVER_ERROR", throw error;
message:
error instanceof Error
? error.message
: "Failed to enable GPU support",
cause: error,
});
} }
}), }),
checkGPUStatus: adminProcedure checkGPUStatus: adminProcedure
@@ -712,7 +699,15 @@ export const settingsRouter = createTRPCRouter({
gpuResources: 0, gpuResources: 0,
}; };
} }
return await checkGPUStatus(input.serverId);
try {
const status = await checkGPUStatus(input.serverId || "");
console.log("GPU Status Check Result:", status);
return status;
} catch (error) {
console.error("GPU Status Check Error:", error);
throw new Error("Failed to check GPU status");
}
}), }),
}); });
// { // {

View File

@@ -1,3 +1,4 @@
import * as fs from "node:fs/promises";
import { execAsync } from "../utils/process/execAsync"; import { execAsync } from "../utils/process/execAsync";
import { execAsyncRemote } from "../utils/process/execAsync"; import { execAsyncRemote } from "../utils/process/execAsync";
@@ -191,18 +192,36 @@ export async function setupGPUSupport(serverId?: string): Promise<void> {
"node-generic-resources": [`GPU=${initialStatus.availableGPUs}`], "node-generic-resources": [`GPU=${initialStatus.availableGPUs}`],
}; };
const setupCommands = [ // Different commands for local and remote setup
"sudo -n true",
`echo '${JSON.stringify(daemonConfig, null, 2)}' | sudo tee /etc/docker/daemon.json`,
"sudo mkdir -p /etc/nvidia-container-runtime",
'echo "swarm-resource = \\"DOCKER_RESOURCE_GPU\\"" | sudo tee -a /etc/nvidia-container-runtime/config.toml',
"sudo systemctl daemon-reload",
"sudo systemctl restart docker",
].join(" && ");
if (serverId) { if (serverId) {
// Remote server setup (using sudo)
const setupCommands = [
"sudo -n true",
`echo '${JSON.stringify(daemonConfig, null, 2)}' | sudo tee /etc/docker/daemon.json`,
"sudo mkdir -p /etc/nvidia-container-runtime",
'echo "swarm-resource = \\"DOCKER_RESOURCE_GPU\\"" | sudo tee -a /etc/nvidia-container-runtime/config.toml',
"sudo systemctl daemon-reload",
"sudo systemctl restart docker",
].join(" && ");
await execAsyncRemote(serverId, setupCommands); await execAsyncRemote(serverId, setupCommands);
} else { } else {
// Local server setup (using pkexec for GUI password prompt)
const configFile = `/tmp/docker-daemon-${Date.now()}.json`;
await fs.writeFile(configFile, JSON.stringify(daemonConfig, null, 2));
const setupCommands = [
// Use pkexec for GUI password prompt
`pkexec sh -c '
cp ${configFile} /etc/docker/daemon.json &&
mkdir -p /etc/nvidia-container-runtime &&
echo "swarm-resource = \\"DOCKER_RESOURCE_GPU\\"" >> /etc/nvidia-container-runtime/config.toml &&
systemctl daemon-reload &&
systemctl restart docker
'`,
`rm ${configFile}`, // Clean up temp file
].join(" && ");
await execAsync(setupCommands); await execAsync(setupCommands);
} }
@@ -244,6 +263,14 @@ export async function setupGPUSupport(serverId?: string): Promise<void> {
}); });
} catch (error) { } catch (error) {
console.error("GPU Setup Error:", error); console.error("GPU Setup Error:", error);
if (
error instanceof Error &&
error.message.includes("password is required")
) {
throw new Error(
"Sudo access required. Please run with appropriate permissions.",
);
}
throw error; throw error;
} }
} }