diff --git a/apps/dokploy/components/dashboard/application/deployments/show-deployment.tsx b/apps/dokploy/components/dashboard/application/deployments/show-deployment.tsx index 6f69fba6..36f77049 100644 --- a/apps/dokploy/components/dashboard/application/deployments/show-deployment.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/show-deployment.tsx @@ -11,7 +11,7 @@ interface Props { logPath: string | null; open: boolean; onClose: () => void; - serverId: string; + serverId?: string; } export const ShowDeployment = ({ logPath, open, onClose, serverId }: Props) => { const [data, setData] = useState(""); diff --git a/apps/dokploy/components/dashboard/compose/deployments/show-deployment-compose.tsx b/apps/dokploy/components/dashboard/compose/deployments/show-deployment-compose.tsx index e0128d93..e7910469 100644 --- a/apps/dokploy/components/dashboard/compose/deployments/show-deployment-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/deployments/show-deployment-compose.tsx @@ -9,10 +9,16 @@ import { useEffect, useRef, useState } from "react"; interface Props { logPath: string | null; + serverId?: string; open: boolean; onClose: () => void; } -export const ShowDeploymentCompose = ({ logPath, open, onClose }: Props) => { +export const ShowDeploymentCompose = ({ + logPath, + open, + onClose, + serverId, +}: Props) => { const [data, setData] = useState(""); const endOfLogsRef = useRef(null); @@ -21,7 +27,7 @@ export const ShowDeploymentCompose = ({ logPath, open, onClose }: Props) => { const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; - const wsUrl = `${protocol}//${window.location.host}/listen-deployment?logPath=${logPath}`; + const wsUrl = `${protocol}//${window.location.host}/listen-deployment?logPath=${logPath}&serverId=${serverId}`; const ws = new WebSocket(wsUrl); ws.onmessage = (e) => { diff --git a/apps/dokploy/components/dashboard/compose/deployments/show-deployments-compose.tsx b/apps/dokploy/components/dashboard/compose/deployments/show-deployments-compose.tsx index cb4210b3..54c0ad2e 100644 --- a/apps/dokploy/components/dashboard/compose/deployments/show-deployments-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/deployments/show-deployments-compose.tsx @@ -111,6 +111,7 @@ export const ShowDeploymentsCompose = ({ composeId }: Props) => { )} setActiveLog(null)} logPath={activeLog} diff --git a/apps/dokploy/components/dashboard/compose/general/actions.tsx b/apps/dokploy/components/dashboard/compose/general/actions.tsx index 754fb345..5e0d9547 100644 --- a/apps/dokploy/components/dashboard/compose/general/actions.tsx +++ b/apps/dokploy/components/dashboard/compose/general/actions.tsx @@ -116,6 +116,7 @@ export const ComposeActions = ({ composeId }: Props) => { )} + {data?.server?.name} ); }; diff --git a/apps/dokploy/components/dashboard/project/add-application.tsx b/apps/dokploy/components/dashboard/project/add-application.tsx index 14383017..69a256a4 100644 --- a/apps/dokploy/components/dashboard/project/add-application.tsx +++ b/apps/dokploy/components/dashboard/project/add-application.tsx @@ -173,9 +173,7 @@ export const AddApplication = ({ projectId, projectName }: Props) => { {server.name} ))} - - Registries ({servers?.length}) - + Servers ({servers?.length}) diff --git a/apps/dokploy/components/dashboard/project/add-compose.tsx b/apps/dokploy/components/dashboard/project/add-compose.tsx index a6697eb6..5f698f35 100644 --- a/apps/dokploy/components/dashboard/project/add-compose.tsx +++ b/apps/dokploy/components/dashboard/project/add-compose.tsx @@ -22,7 +22,9 @@ import { Input } from "@/components/ui/input"; import { Select, SelectContent, + SelectGroup, SelectItem, + SelectLabel, SelectTrigger, SelectValue, } from "@/components/ui/select"; @@ -51,6 +53,9 @@ const AddComposeSchema = z.object({ "App name supports lowercase letters, numbers, '-' and can only start and end letters, and does not support continuous '-'", }), description: z.string().optional(), + serverId: z.string().min(1, { + message: "Server is required", + }), }); type AddCompose = z.infer; @@ -63,6 +68,7 @@ interface Props { export const AddCompose = ({ projectId, projectName }: Props) => { const utils = api.useUtils(); const slug = slugify(projectName); + const { data: servers } = api.server.all.useQuery(); const { mutateAsync, isLoading, error, isError } = api.compose.create.useMutation(); @@ -72,6 +78,7 @@ export const AddCompose = ({ projectId, projectName }: Props) => { description: "", composeType: "docker-compose", appName: `${slug}-`, + serverId: "", }, resolver: zodResolver(AddComposeSchema), }); @@ -87,6 +94,7 @@ export const AddCompose = ({ projectId, projectName }: Props) => { projectId, composeType: data.composeType, appName: data.appName, + serverId: data.serverId, }) .then(async () => { toast.success("Compose Created"); @@ -148,6 +156,37 @@ export const AddCompose = ({ projectId, projectName }: Props) => { )} /> + ( + + Select a Server + + + + )} + /> { + // const jobData: DeploymentJob = { + // composeId: input.composeId, + // titleLog: "Manual deployment", + // type: "deploy", + // applicationType: "compose", + // descriptionLog: "", + // }; + // await myQueue.add( + // "deployments", + // { ...jobData }, + // { + // removeOnComplete: true, + // removeOnFail: true, + // }, + // ); + const compose = await findComposeById(input.composeId); const jobData: DeploymentJob = { composeId: input.composeId, titleLog: "Manual deployment", @@ -169,14 +186,10 @@ export const composeRouter = createTRPCRouter({ applicationType: "compose", descriptionLog: "", }; - await myQueue.add( - "deployments", - { ...jobData }, - { - removeOnComplete: true, - removeOnFail: true, - }, - ); + if (!compose.serverId) { + } else { + await enqueueDeploymentJob(compose.serverId, jobData); + } }), redeploy: protectedProcedure .input(apiFindCompose) diff --git a/apps/dokploy/server/api/services/compose.ts b/apps/dokploy/server/api/services/compose.ts index 2db9a343..6cb26985 100644 --- a/apps/dokploy/server/api/services/compose.ts +++ b/apps/dokploy/server/api/services/compose.ts @@ -3,23 +3,43 @@ import { COMPOSE_PATH } from "@/server/constants"; import { db } from "@/server/db"; import { type apiCreateCompose, compose } from "@/server/db/schema"; import { generateAppName } from "@/server/db/schema/utils"; -import { buildCompose } from "@/server/utils/builders/compose"; +import { + buildCompose, + getBuildComposeCommand, +} from "@/server/utils/builders/compose"; import { randomizeSpecificationFile } from "@/server/utils/docker/compose"; import { cloneCompose, loadDockerCompose } from "@/server/utils/docker/domain"; import { sendBuildErrorNotifications } from "@/server/utils/notifications/build-error"; import { sendBuildSuccessNotifications } from "@/server/utils/notifications/build-success"; import { execAsync } from "@/server/utils/process/execAsync"; -import { cloneBitbucketRepository } from "@/server/utils/providers/bitbucket"; -import { cloneGitRepository } from "@/server/utils/providers/git"; -import { cloneGithubRepository } from "@/server/utils/providers/github"; -import { cloneGitlabRepository } from "@/server/utils/providers/gitlab"; -import { createComposeFile } from "@/server/utils/providers/raw"; +import { + cloneBitbucketRepository, + getBitbucketCloneCommand, +} from "@/server/utils/providers/bitbucket"; +import { + cloneGitRepository, + getCustomGitCloneCommand, +} from "@/server/utils/providers/git"; +import { + cloneGithubRepository, + getGithubCloneCommand, +} from "@/server/utils/providers/github"; +import { + cloneGitlabRepository, + getGitlabCloneCommand, +} from "@/server/utils/providers/gitlab"; +import { + createComposeFile, + getCreateComposeFileCommand, +} from "@/server/utils/providers/raw"; import { generatePassword } from "@/templates/utils"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; import { getDokployUrl } from "./admin"; import { createDeploymentCompose, updateDeploymentStatus } from "./deployment"; import { validUniqueServerAppName } from "./project"; +import { getBuildCommand } from "@/server/utils/builders"; +import { executeCommand } from "@/server/utils/servers/command"; export type Compose = typeof compose.$inferSelect; @@ -97,6 +117,7 @@ export const findComposeById = async (composeId: string) => { github: true, gitlab: true, bitbucket: true, + server: true, }, }); if (!result) { @@ -173,18 +194,55 @@ export const deployCompose = async ({ }); try { - if (compose.sourceType === "github") { - await cloneGithubRepository(compose, deployment.logPath, true); - } else if (compose.sourceType === "gitlab") { - await cloneGitlabRepository(compose, deployment.logPath, true); - } else if (compose.sourceType === "bitbucket") { - await cloneBitbucketRepository(compose, deployment.logPath, true); - } else if (compose.sourceType === "git") { - await cloneGitRepository(compose, deployment.logPath, true); - } else if (compose.sourceType === "raw") { - await createComposeFile(compose, deployment.logPath); + if (compose.serverId) { + let command = ` + set -e + `; + if (compose.sourceType === "github") { + command += await getGithubCloneCommand( + compose, + deployment.logPath, + true, + ); + } else if (compose.sourceType === "gitlab") { + command += await getGitlabCloneCommand( + compose, + deployment.logPath, + true, + ); + } else if (compose.sourceType === "bitbucket") { + command += await getBitbucketCloneCommand( + compose, + deployment.logPath, + true, + ); + } else if (compose.sourceType === "git") { + command += await getCustomGitCloneCommand( + compose, + deployment.logPath, + true, + ); + } else if (compose.sourceType === "raw") { + command += getCreateComposeFileCommand(compose); + } + command += getBuildComposeCommand(compose, deployment.logPath); + await executeCommand(compose.serverId, command); + } else { + if (compose.sourceType === "github") { + await cloneGithubRepository(compose, deployment.logPath, true); + } else if (compose.sourceType === "gitlab") { + await cloneGitlabRepository(compose, deployment.logPath, true); + } else if (compose.sourceType === "bitbucket") { + await cloneBitbucketRepository(compose, deployment.logPath, true); + } else if (compose.sourceType === "git") { + await cloneGitRepository(compose, deployment.logPath, true); + } else if (compose.sourceType === "raw") { + await createComposeFile(compose, deployment.logPath); + } + + await buildCompose(compose, deployment.logPath); } - await buildCompose(compose, deployment.logPath); + await updateDeploymentStatus(deployment.deploymentId, "done"); await updateCompose(composeId, { composeStatus: "done", diff --git a/apps/dokploy/server/api/services/deployment.ts b/apps/dokploy/server/api/services/deployment.ts index ddec15e1..2dfd2b4d 100644 --- a/apps/dokploy/server/api/services/deployment.ts +++ b/apps/dokploy/server/api/services/deployment.ts @@ -100,14 +100,27 @@ export const createDeploymentCompose = async ( try { const compose = await findComposeById(deployment.composeId); - await removeLastTenComposeDeployments(deployment.composeId); + // await removeLastTenComposeDeployments(deployment.composeId); const formattedDateTime = format(new Date(), "yyyy-MM-dd:HH:mm:ss"); const fileName = `${compose.appName}-${formattedDateTime}.log`; const logFilePath = path.join(LOGS_PATH, compose.appName, fileName); - await fsPromises.mkdir(path.join(LOGS_PATH, compose.appName), { - recursive: true, - }); - await fsPromises.writeFile(logFilePath, "Initializing deployment"); + + if (compose.serverId) { + const server = await findServerById(compose.serverId); + + const command = ` +mkdir -p ${LOGS_PATH}/${compose.appName}; +echo "Initializing deployment" >> ${logFilePath}; +`; + + await executeCommand(server.serverId, command); + } else { + await fsPromises.mkdir(path.join(LOGS_PATH, compose.appName), { + recursive: true, + }); + await fsPromises.writeFile(logFilePath, "Initializing deployment"); + } + const deploymentCreate = await db .insert(deployments) .values({ diff --git a/apps/dokploy/server/db/schema/compose.ts b/apps/dokploy/server/db/schema/compose.ts index 2580395d..662ca4c4 100644 --- a/apps/dokploy/server/db/schema/compose.ts +++ b/apps/dokploy/server/db/schema/compose.ts @@ -136,6 +136,7 @@ export const apiCreateCompose = createSchema.pick({ projectId: true, composeType: true, appName: true, + serverId: true, }); export const apiCreateComposeByTemplate = createSchema diff --git a/apps/dokploy/server/queues/deployments-queue.ts b/apps/dokploy/server/queues/deployments-queue.ts index e541447a..2f5344d5 100644 --- a/apps/dokploy/server/queues/deployments-queue.ts +++ b/apps/dokploy/server/queues/deployments-queue.ts @@ -32,43 +32,43 @@ export type DeploymentJob = DeployJob; // export const deploymentWorker = new Worker( // "deployments", // async (job: Job) => { -// try { -// if (job.data.applicationType === "application") { -// await updateApplicationStatus(job.data.applicationId, "running"); -// if (job.data.type === "redeploy") { -// await rebuildApplication({ -// applicationId: job.data.applicationId, -// titleLog: job.data.titleLog, -// descriptionLog: job.data.descriptionLog, -// }); -// } else if (job.data.type === "deploy") { -// await deployApplication({ -// applicationId: job.data.applicationId, -// titleLog: job.data.titleLog, -// descriptionLog: job.data.descriptionLog, -// }); -// } -// } else if (job.data.applicationType === "compose") { -// await updateCompose(job.data.composeId, { -// composeStatus: "running", -// }); -// if (job.data.type === "deploy") { -// await deployCompose({ -// composeId: job.data.composeId, -// titleLog: job.data.titleLog, -// descriptionLog: job.data.descriptionLog, -// }); -// } else if (job.data.type === "redeploy") { -// await rebuildCompose({ -// composeId: job.data.composeId, -// titleLog: job.data.titleLog, -// descriptionLog: job.data.descriptionLog, -// }); -// } -// } -// } catch (error) { -// console.log("Error", error); +// try { +// if (job.data.applicationType === "application") { +// await updateApplicationStatus(job.data.applicationId, "running"); +// if (job.data.type === "redeploy") { +// await rebuildApplication({ +// applicationId: job.data.applicationId, +// titleLog: job.data.titleLog, +// descriptionLog: job.data.descriptionLog, +// }); +// } else if (job.data.type === "deploy") { +// await deployApplication({ +// applicationId: job.data.applicationId, +// titleLog: job.data.titleLog, +// descriptionLog: job.data.descriptionLog, +// }); // } +// } else if (job.data.applicationType === "compose") { +// await updateCompose(job.data.composeId, { +// composeStatus: "running", +// }); +// if (job.data.type === "deploy") { +// await deployCompose({ +// composeId: job.data.composeId, +// titleLog: job.data.titleLog, +// descriptionLog: job.data.descriptionLog, +// }); +// } else if (job.data.type === "redeploy") { +// await rebuildCompose({ +// composeId: job.data.composeId, +// titleLog: job.data.titleLog, +// descriptionLog: job.data.descriptionLog, +// }); +// } +// } +// } catch (error) { +// console.log("Error", error); +// } // }, // { // autorun: false, diff --git a/apps/dokploy/server/queues/queueSetup.ts b/apps/dokploy/server/queues/queueSetup.ts index bceaf33e..d7540b05 100644 --- a/apps/dokploy/server/queues/queueSetup.ts +++ b/apps/dokploy/server/queues/queueSetup.ts @@ -3,8 +3,14 @@ import { findServerById, type Server } from "../api/services/server"; import type { DeploymentJob } from "./deployments-queue"; import { deployApplication, + rebuildApplication, updateApplicationStatus, } from "../api/services/application"; +import { + updateCompose, + deployCompose, + rebuildCompose, +} from "../api/services/compose"; export const redisConfig: ConnectionOptions = { host: "31.220.108.27", @@ -54,15 +60,42 @@ async function setupServerQueueAndWorker(server: Server) { `deployments-${server.serverId}`, async (job: Job) => { // Ejecuta el trabajo de despliegue - if (job.data.applicationType === "application") { - await updateApplicationStatus(job.data.applicationId, "running"); - if (job.data.type === "deploy") { - await deployApplication({ - applicationId: job.data.applicationId, - titleLog: job.data.titleLog, - descriptionLog: job.data.descriptionLog, + try { + if (job.data.applicationType === "application") { + await updateApplicationStatus(job.data.applicationId, "running"); + if (job.data.type === "redeploy") { + await rebuildApplication({ + applicationId: job.data.applicationId, + titleLog: job.data.titleLog, + descriptionLog: job.data.descriptionLog, + }); + } else if (job.data.type === "deploy") { + await deployApplication({ + applicationId: job.data.applicationId, + titleLog: job.data.titleLog, + descriptionLog: job.data.descriptionLog, + }); + } + } else if (job.data.applicationType === "compose") { + await updateCompose(job.data.composeId, { + composeStatus: "running", }); + if (job.data.type === "deploy") { + await deployCompose({ + composeId: job.data.composeId, + titleLog: job.data.titleLog, + descriptionLog: job.data.descriptionLog, + }); + } else if (job.data.type === "redeploy") { + await rebuildCompose({ + composeId: job.data.composeId, + titleLog: job.data.titleLog, + descriptionLog: job.data.descriptionLog, + }); + } } + } catch (error) { + console.log("Error", error); } }, { diff --git a/apps/dokploy/server/utils/builders/compose.ts b/apps/dokploy/server/utils/builders/compose.ts index 10e56aba..6143c415 100644 --- a/apps/dokploy/server/utils/builders/compose.ts +++ b/apps/dokploy/server/utils/builders/compose.ts @@ -65,6 +65,30 @@ Compose Type: ${composeType} ✅`; } }; +export const getBuildComposeCommand = ( + compose: ComposeNested, + logPath: string, +) => { + const { sourceType, appName, mounts, composeType, domains } = compose; + const command = createCommand(compose); + const projectPath = join(COMPOSE_PATH, compose.appName, "code"); + const logContent = ` +App Name: ${appName} +Build Compose 🐳 +Detected: ${mounts.length} mounts 📂 +Command: docker ${command} +Source Type: docker ${sourceType} ✅ +Compose Type: ${composeType} ✅`; + + const bashCommand = ` +echo "${logContent}" >> ${logPath}; +cd ${projectPath} || exit 1; +docker ${command.split(" ").join(" ")} >> ${logPath} 2>&1; +echo "Docker Compose Deployed: ✅" >> ${logPath}; +`; + return bashCommand; +}; + const sanitizeCommand = (command: string) => { const sanitizedCommand = command.trim(); diff --git a/apps/dokploy/server/utils/providers/raw.ts b/apps/dokploy/server/utils/providers/raw.ts index 9bf818a3..47603638 100644 --- a/apps/dokploy/server/utils/providers/raw.ts +++ b/apps/dokploy/server/utils/providers/raw.ts @@ -27,6 +27,13 @@ export const createComposeFile = async (compose: Compose, logPath: string) => { } }; +export const getCreateComposeFileCommand = (compose: Compose) => { + const { appName, composeFile } = compose; + const outputPath = join(COMPOSE_PATH, appName, "code"); + const filePath = join(outputPath, "docker-compose.yml"); + return `echo "${composeFile}" > ${filePath}`; +}; + export const createComposeFileRaw = async (compose: Compose) => { const { appName, composeFile } = compose; const outputPath = join(COMPOSE_PATH, appName, "code");