From a8f94540f9144e3170f5172ed0c4219053f53e4d Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 9 Feb 2025 02:20:40 -0600 Subject: [PATCH] refactor: lint --- .../compose/general/compose-file-editor.tsx | 2 +- .../compose/general/show-utilities.tsx | 8 +- apps/dokploy/server/api/routers/compose.ts | 826 +++++++++--------- packages/server/src/db/schema/compose.ts | 272 +++--- packages/server/src/utils/builders/compose.ts | 318 +++---- packages/server/src/utils/docker/collision.ts | 48 +- packages/server/src/utils/docker/domain.ts | 494 +++++------ 7 files changed, 984 insertions(+), 984 deletions(-) diff --git a/apps/dokploy/components/dashboard/compose/general/compose-file-editor.tsx b/apps/dokploy/components/dashboard/compose/general/compose-file-editor.tsx index 47497219..71b92814 100644 --- a/apps/dokploy/components/dashboard/compose/general/compose-file-editor.tsx +++ b/apps/dokploy/components/dashboard/compose/general/compose-file-editor.tsx @@ -14,8 +14,8 @@ import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; import { validateAndFormatYAML } from "../../application/advanced/traefik/update-traefik-config"; -import { RandomizeCompose } from "./randomize-compose"; import { RandomizeDeployable } from "./isolated-deployment"; +import { RandomizeCompose } from "./randomize-compose"; import { ShowUtilities } from "./show-utilities"; interface Props { diff --git a/apps/dokploy/components/dashboard/compose/general/show-utilities.tsx b/apps/dokploy/components/dashboard/compose/general/show-utilities.tsx index 6a3721e4..214102ce 100644 --- a/apps/dokploy/components/dashboard/compose/general/show-utilities.tsx +++ b/apps/dokploy/components/dashboard/compose/general/show-utilities.tsx @@ -1,6 +1,4 @@ -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { IsolatedDeployment } from "./isolated-deployment"; -import { RandomizeCompose } from "./randomize-compose"; +import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, @@ -9,8 +7,10 @@ import { DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useState } from "react"; -import { Button } from "@/components/ui/button"; +import { IsolatedDeployment } from "./isolated-deployment"; +import { RandomizeCompose } from "./randomize-compose"; interface Props { composeId: string; diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index 380fc70f..f28f33a1 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -1,22 +1,22 @@ import { slugify } from "@/lib/slug"; import { db } from "@/server/db"; import { - apiCreateCompose, - apiCreateComposeByTemplate, - apiDeleteCompose, - apiFetchServices, - apiFindCompose, - apiRandomizeCompose, - apiUpdateCompose, - compose, + apiCreateCompose, + apiCreateComposeByTemplate, + apiDeleteCompose, + apiFetchServices, + apiFindCompose, + apiRandomizeCompose, + apiUpdateCompose, + compose, } from "@/server/db/schema"; import { cleanQueuesByCompose, myQueue } from "@/server/queues/queueSetup"; import { templates } from "@/templates/templates"; import type { TemplatesKeys } from "@/templates/types/templates-data.type"; import { - generatePassword, - loadTemplateModule, - readTemplateComposeFile, + generatePassword, + loadTemplateModule, + readTemplateComposeFile, } from "@/templates/utils"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; @@ -28,443 +28,443 @@ import { createTRPCRouter, protectedProcedure } from "../trpc"; import type { DeploymentJob } from "@/server/queues/queue-types"; import { deploy } from "@/server/utils/deploy"; import { - IS_CLOUD, - addDomainToCompose, - addNewService, - checkServiceAccess, - cloneCompose, - cloneComposeRemote, - createCommand, - createCompose, - createComposeByTemplate, - createDomain, - createMount, - findAdminById, - findComposeById, - findDomainsByComposeId, - findProjectById, - findServerById, - loadServices, - randomizeComposeFile, - randomizeIsolatedDeploymentComposeFile, - removeCompose, - removeComposeDirectory, - removeDeploymentsByComposeId, - startCompose, - stopCompose, - updateCompose, + IS_CLOUD, + addDomainToCompose, + addNewService, + checkServiceAccess, + cloneCompose, + cloneComposeRemote, + createCommand, + createCompose, + createComposeByTemplate, + createDomain, + createMount, + findAdminById, + findComposeById, + findDomainsByComposeId, + findProjectById, + findServerById, + loadServices, + randomizeComposeFile, + randomizeIsolatedDeploymentComposeFile, + removeCompose, + removeComposeDirectory, + removeDeploymentsByComposeId, + startCompose, + stopCompose, + updateCompose, } from "@dokploy/server"; export const composeRouter = createTRPCRouter({ - create: protectedProcedure - .input(apiCreateCompose) - .mutation(async ({ ctx, input }) => { - try { - if (ctx.user.rol === "user") { - await checkServiceAccess(ctx.user.authId, input.projectId, "create"); - } + create: protectedProcedure + .input(apiCreateCompose) + .mutation(async ({ ctx, input }) => { + try { + if (ctx.user.rol === "user") { + await checkServiceAccess(ctx.user.authId, input.projectId, "create"); + } - if (IS_CLOUD && !input.serverId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You need to use a server to create a compose", - }); - } - const project = await findProjectById(input.projectId); - if (project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to access this project", - }); - } - const newService = await createCompose(input); + if (IS_CLOUD && !input.serverId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You need to use a server to create a compose", + }); + } + const project = await findProjectById(input.projectId); + if (project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this project", + }); + } + const newService = await createCompose(input); - if (ctx.user.rol === "user") { - await addNewService(ctx.user.authId, newService.composeId); - } + if (ctx.user.rol === "user") { + await addNewService(ctx.user.authId, newService.composeId); + } - return newService; - } catch (error) { - throw error; - } - }), + return newService; + } catch (error) { + throw error; + } + }), - one: protectedProcedure - .input(apiFindCompose) - .query(async ({ input, ctx }) => { - if (ctx.user.rol === "user") { - await checkServiceAccess(ctx.user.authId, input.composeId, "access"); - } + one: protectedProcedure + .input(apiFindCompose) + .query(async ({ input, ctx }) => { + if (ctx.user.rol === "user") { + await checkServiceAccess(ctx.user.authId, input.composeId, "access"); + } - const compose = await findComposeById(input.composeId); - if (compose.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to access this compose", - }); - } - return compose; - }), + const compose = await findComposeById(input.composeId); + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this compose", + }); + } + return compose; + }), - update: protectedProcedure - .input(apiUpdateCompose) - .mutation(async ({ input, ctx }) => { - const compose = await findComposeById(input.composeId); - if (compose.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to update this compose", - }); - } - return updateCompose(input.composeId, input); - }), - delete: protectedProcedure - .input(apiDeleteCompose) - .mutation(async ({ input, ctx }) => { - if (ctx.user.rol === "user") { - await checkServiceAccess(ctx.user.authId, input.composeId, "delete"); - } - const composeResult = await findComposeById(input.composeId); + update: protectedProcedure + .input(apiUpdateCompose) + .mutation(async ({ input, ctx }) => { + const compose = await findComposeById(input.composeId); + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to update this compose", + }); + } + return updateCompose(input.composeId, input); + }), + delete: protectedProcedure + .input(apiDeleteCompose) + .mutation(async ({ input, ctx }) => { + if (ctx.user.rol === "user") { + await checkServiceAccess(ctx.user.authId, input.composeId, "delete"); + } + const composeResult = await findComposeById(input.composeId); - if (composeResult.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to delete this compose", - }); - } - 4; + if (composeResult.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to delete this compose", + }); + } + 4; - const result = await db - .delete(compose) - .where(eq(compose.composeId, input.composeId)) - .returning(); + const result = await db + .delete(compose) + .where(eq(compose.composeId, input.composeId)) + .returning(); - const cleanupOperations = [ - async () => await removeCompose(composeResult, input.deleteVolumes), - async () => await removeDeploymentsByComposeId(composeResult), - async () => await removeComposeDirectory(composeResult.appName), - ]; + const cleanupOperations = [ + async () => await removeCompose(composeResult, input.deleteVolumes), + async () => await removeDeploymentsByComposeId(composeResult), + async () => await removeComposeDirectory(composeResult.appName), + ]; - for (const operation of cleanupOperations) { - try { - await operation(); - } catch (error) {} - } + for (const operation of cleanupOperations) { + try { + await operation(); + } catch (error) {} + } - return result[0]; - }), - cleanQueues: protectedProcedure - .input(apiFindCompose) - .mutation(async ({ input, ctx }) => { - const compose = await findComposeById(input.composeId); - if (compose.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to clean this compose", - }); - } - await cleanQueuesByCompose(input.composeId); - }), + return result[0]; + }), + cleanQueues: protectedProcedure + .input(apiFindCompose) + .mutation(async ({ input, ctx }) => { + const compose = await findComposeById(input.composeId); + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to clean this compose", + }); + } + await cleanQueuesByCompose(input.composeId); + }), - loadServices: protectedProcedure - .input(apiFetchServices) - .query(async ({ input, ctx }) => { - const compose = await findComposeById(input.composeId); - if (compose.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to load this compose", - }); - } - return await loadServices(input.composeId, input.type); - }), - fetchSourceType: protectedProcedure - .input(apiFindCompose) - .mutation(async ({ input, ctx }) => { - try { - const compose = await findComposeById(input.composeId); + loadServices: protectedProcedure + .input(apiFetchServices) + .query(async ({ input, ctx }) => { + const compose = await findComposeById(input.composeId); + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to load this compose", + }); + } + return await loadServices(input.composeId, input.type); + }), + fetchSourceType: protectedProcedure + .input(apiFindCompose) + .mutation(async ({ input, ctx }) => { + try { + const compose = await findComposeById(input.composeId); - if (compose.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to fetch this compose", - }); - } - if (compose.serverId) { - await cloneComposeRemote(compose); - } else { - await cloneCompose(compose); - } - return compose.sourceType; - } catch (err) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error fetching source type", - cause: err, - }); - } - }), + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to fetch this compose", + }); + } + if (compose.serverId) { + await cloneComposeRemote(compose); + } else { + await cloneCompose(compose); + } + return compose.sourceType; + } catch (err) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error fetching source type", + cause: err, + }); + } + }), - randomizeCompose: protectedProcedure - .input(apiRandomizeCompose) - .mutation(async ({ input, ctx }) => { - const compose = await findComposeById(input.composeId); - if (compose.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to randomize this compose", - }); - } - return await randomizeComposeFile(input.composeId, input.suffix); - }), - isolatedDeployment: protectedProcedure - .input(apiRandomizeCompose) - .mutation(async ({ input, ctx }) => { - const compose = await findComposeById(input.composeId); - if (compose.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to randomize this compose", - }); - } - return await randomizeIsolatedDeploymentComposeFile( - input.composeId, - input.suffix - ); - }), - getConvertedCompose: protectedProcedure - .input(apiFindCompose) - .query(async ({ input, ctx }) => { - const compose = await findComposeById(input.composeId); - if (compose.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to get this compose", - }); - } - const domains = await findDomainsByComposeId(input.composeId); - const composeFile = await addDomainToCompose(compose, domains); - return dump(composeFile, { - lineWidth: 1000, - }); - }), + randomizeCompose: protectedProcedure + .input(apiRandomizeCompose) + .mutation(async ({ input, ctx }) => { + const compose = await findComposeById(input.composeId); + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to randomize this compose", + }); + } + return await randomizeComposeFile(input.composeId, input.suffix); + }), + isolatedDeployment: protectedProcedure + .input(apiRandomizeCompose) + .mutation(async ({ input, ctx }) => { + const compose = await findComposeById(input.composeId); + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to randomize this compose", + }); + } + return await randomizeIsolatedDeploymentComposeFile( + input.composeId, + input.suffix, + ); + }), + getConvertedCompose: protectedProcedure + .input(apiFindCompose) + .query(async ({ input, ctx }) => { + const compose = await findComposeById(input.composeId); + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to get this compose", + }); + } + const domains = await findDomainsByComposeId(input.composeId); + const composeFile = await addDomainToCompose(compose, domains); + return dump(composeFile, { + lineWidth: 1000, + }); + }), - deploy: protectedProcedure - .input(apiFindCompose) - .mutation(async ({ input, ctx }) => { - const compose = await findComposeById(input.composeId); + deploy: protectedProcedure + .input(apiFindCompose) + .mutation(async ({ input, ctx }) => { + const compose = await findComposeById(input.composeId); - if (compose.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to deploy this compose", - }); - } - const jobData: DeploymentJob = { - composeId: input.composeId, - titleLog: "Manual deployment", - type: "deploy", - applicationType: "compose", - descriptionLog: "", - server: !!compose.serverId, - }; + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to deploy this compose", + }); + } + const jobData: DeploymentJob = { + composeId: input.composeId, + titleLog: "Manual deployment", + type: "deploy", + applicationType: "compose", + descriptionLog: "", + server: !!compose.serverId, + }; - if (IS_CLOUD && compose.serverId) { - jobData.serverId = compose.serverId; - await deploy(jobData); - return true; - } - await myQueue.add( - "deployments", - { ...jobData }, - { - removeOnComplete: true, - removeOnFail: true, - } - ); - }), - redeploy: protectedProcedure - .input(apiFindCompose) - .mutation(async ({ input, ctx }) => { - const compose = await findComposeById(input.composeId); - if (compose.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to redeploy this compose", - }); - } - const jobData: DeploymentJob = { - composeId: input.composeId, - titleLog: "Rebuild deployment", - type: "redeploy", - applicationType: "compose", - descriptionLog: "", - server: !!compose.serverId, - }; - if (IS_CLOUD && compose.serverId) { - jobData.serverId = compose.serverId; - await deploy(jobData); - return true; - } - await myQueue.add( - "deployments", - { ...jobData }, - { - removeOnComplete: true, - removeOnFail: true, - } - ); - }), - stop: protectedProcedure - .input(apiFindCompose) - .mutation(async ({ input, ctx }) => { - const compose = await findComposeById(input.composeId); - if (compose.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to stop this compose", - }); - } - await stopCompose(input.composeId); + if (IS_CLOUD && compose.serverId) { + jobData.serverId = compose.serverId; + await deploy(jobData); + return true; + } + await myQueue.add( + "deployments", + { ...jobData }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); + }), + redeploy: protectedProcedure + .input(apiFindCompose) + .mutation(async ({ input, ctx }) => { + const compose = await findComposeById(input.composeId); + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to redeploy this compose", + }); + } + const jobData: DeploymentJob = { + composeId: input.composeId, + titleLog: "Rebuild deployment", + type: "redeploy", + applicationType: "compose", + descriptionLog: "", + server: !!compose.serverId, + }; + if (IS_CLOUD && compose.serverId) { + jobData.serverId = compose.serverId; + await deploy(jobData); + return true; + } + await myQueue.add( + "deployments", + { ...jobData }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); + }), + stop: protectedProcedure + .input(apiFindCompose) + .mutation(async ({ input, ctx }) => { + const compose = await findComposeById(input.composeId); + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to stop this compose", + }); + } + await stopCompose(input.composeId); - return true; - }), - start: protectedProcedure - .input(apiFindCompose) - .mutation(async ({ input, ctx }) => { - const compose = await findComposeById(input.composeId); - if (compose.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to stop this compose", - }); - } - await startCompose(input.composeId); + return true; + }), + start: protectedProcedure + .input(apiFindCompose) + .mutation(async ({ input, ctx }) => { + const compose = await findComposeById(input.composeId); + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to stop this compose", + }); + } + await startCompose(input.composeId); - return true; - }), - getDefaultCommand: protectedProcedure - .input(apiFindCompose) - .query(async ({ input, ctx }) => { - const compose = await findComposeById(input.composeId); + return true; + }), + getDefaultCommand: protectedProcedure + .input(apiFindCompose) + .query(async ({ input, ctx }) => { + const compose = await findComposeById(input.composeId); - if (compose.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to get this compose", - }); - } - const command = createCommand(compose); - return `docker ${command}`; - }), - refreshToken: protectedProcedure - .input(apiFindCompose) - .mutation(async ({ input, ctx }) => { - const compose = await findComposeById(input.composeId); - if (compose.project.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to refresh this compose", - }); - } - await updateCompose(input.composeId, { - refreshToken: nanoid(), - }); - return true; - }), - deployTemplate: protectedProcedure - .input(apiCreateComposeByTemplate) - .mutation(async ({ ctx, input }) => { - if (ctx.user.rol === "user") { - await checkServiceAccess(ctx.user.authId, input.projectId, "create"); - } + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to get this compose", + }); + } + const command = createCommand(compose); + return `docker ${command}`; + }), + refreshToken: protectedProcedure + .input(apiFindCompose) + .mutation(async ({ input, ctx }) => { + const compose = await findComposeById(input.composeId); + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to refresh this compose", + }); + } + await updateCompose(input.composeId, { + refreshToken: nanoid(), + }); + return true; + }), + deployTemplate: protectedProcedure + .input(apiCreateComposeByTemplate) + .mutation(async ({ ctx, input }) => { + if (ctx.user.rol === "user") { + await checkServiceAccess(ctx.user.authId, input.projectId, "create"); + } - if (IS_CLOUD && !input.serverId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You need to use a server to create a compose", - }); - } + if (IS_CLOUD && !input.serverId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You need to use a server to create a compose", + }); + } - const composeFile = await readTemplateComposeFile(input.id); + const composeFile = await readTemplateComposeFile(input.id); - const generate = await loadTemplateModule(input.id as TemplatesKeys); + const generate = await loadTemplateModule(input.id as TemplatesKeys); - const admin = await findAdminById(ctx.user.adminId); - let serverIp = admin.serverIp || "127.0.0.1"; + const admin = await findAdminById(ctx.user.adminId); + let serverIp = admin.serverIp || "127.0.0.1"; - const project = await findProjectById(input.projectId); + const project = await findProjectById(input.projectId); - if (input.serverId) { - const server = await findServerById(input.serverId); - serverIp = server.ipAddress; - } else if (process.env.NODE_ENV === "development") { - serverIp = "127.0.0.1"; - } - const projectName = slugify(`${project.name} ${input.id}`); - const { envs, mounts, domains } = generate({ - serverIp: serverIp || "", - projectName: projectName, - }); + if (input.serverId) { + const server = await findServerById(input.serverId); + serverIp = server.ipAddress; + } else if (process.env.NODE_ENV === "development") { + serverIp = "127.0.0.1"; + } + const projectName = slugify(`${project.name} ${input.id}`); + const { envs, mounts, domains } = generate({ + serverIp: serverIp || "", + projectName: projectName, + }); - const compose = await createComposeByTemplate({ - ...input, - composeFile: composeFile, - env: envs?.join("\n"), - serverId: input.serverId, - name: input.id, - sourceType: "raw", - appName: `${projectName}-${generatePassword(6)}`, - }); + const compose = await createComposeByTemplate({ + ...input, + composeFile: composeFile, + env: envs?.join("\n"), + serverId: input.serverId, + name: input.id, + sourceType: "raw", + appName: `${projectName}-${generatePassword(6)}`, + }); - if (ctx.user.rol === "user") { - await addNewService(ctx.user.authId, compose.composeId); - } + if (ctx.user.rol === "user") { + await addNewService(ctx.user.authId, compose.composeId); + } - if (mounts && mounts?.length > 0) { - for (const mount of mounts) { - await createMount({ - filePath: mount.filePath, - mountPath: "", - content: mount.content, - serviceId: compose.composeId, - serviceType: "compose", - type: "file", - }); - } - } + if (mounts && mounts?.length > 0) { + for (const mount of mounts) { + await createMount({ + filePath: mount.filePath, + mountPath: "", + content: mount.content, + serviceId: compose.composeId, + serviceType: "compose", + type: "file", + }); + } + } - if (domains && domains?.length > 0) { - for (const domain of domains) { - await createDomain({ - ...domain, - domainType: "compose", - certificateType: "none", - composeId: compose.composeId, - }); - } - } + if (domains && domains?.length > 0) { + for (const domain of domains) { + await createDomain({ + ...domain, + domainType: "compose", + certificateType: "none", + composeId: compose.composeId, + }); + } + } - return null; - }), + return null; + }), - templates: protectedProcedure.query(async () => { - const templatesData = templates.map((t) => ({ - name: t.name, - description: t.description, - id: t.id, - links: t.links, - tags: t.tags, - logo: t.logo, - version: t.version, - })); + templates: protectedProcedure.query(async () => { + const templatesData = templates.map((t) => ({ + name: t.name, + description: t.description, + id: t.id, + links: t.links, + tags: t.tags, + logo: t.logo, + version: t.version, + })); - return templatesData; - }), + return templatesData; + }), - getTags: protectedProcedure.query(async ({ input }) => { - const allTags = templates.flatMap((template) => template.tags); - const uniqueTags = _.uniq(allTags); - return uniqueTags; - }), + getTags: protectedProcedure.query(async ({ input }) => { + const allTags = templates.flatMap((template) => template.tags); + const uniqueTags = _.uniq(allTags); + return uniqueTags; + }), }); diff --git a/packages/server/src/db/schema/compose.ts b/packages/server/src/db/schema/compose.ts index e0f13c29..ca8344ee 100644 --- a/packages/server/src/db/schema/compose.ts +++ b/packages/server/src/db/schema/compose.ts @@ -16,170 +16,170 @@ import { sshKeys } from "./ssh-key"; import { generateAppName } from "./utils"; export const sourceTypeCompose = pgEnum("sourceTypeCompose", [ - "git", - "github", - "gitlab", - "bitbucket", - "raw", + "git", + "github", + "gitlab", + "bitbucket", + "raw", ]); export const composeType = pgEnum("composeType", ["docker-compose", "stack"]); export const compose = pgTable("compose", { - composeId: text("composeId") - .notNull() - .primaryKey() - .$defaultFn(() => nanoid()), - name: text("name").notNull(), - appName: text("appName") - .notNull() - .$defaultFn(() => generateAppName("compose")), - description: text("description"), - env: text("env"), - composeFile: text("composeFile").notNull().default(""), - refreshToken: text("refreshToken").$defaultFn(() => nanoid()), - sourceType: sourceTypeCompose("sourceType").notNull().default("github"), - composeType: composeType("composeType").notNull().default("docker-compose"), - // Github - repository: text("repository"), - owner: text("owner"), - branch: text("branch"), - autoDeploy: boolean("autoDeploy").$defaultFn(() => true), - // Gitlab - gitlabProjectId: integer("gitlabProjectId"), - gitlabRepository: text("gitlabRepository"), - gitlabOwner: text("gitlabOwner"), - gitlabBranch: text("gitlabBranch"), - gitlabPathNamespace: text("gitlabPathNamespace"), - // Bitbucket - bitbucketRepository: text("bitbucketRepository"), - bitbucketOwner: text("bitbucketOwner"), - bitbucketBranch: text("bitbucketBranch"), - // Git - customGitUrl: text("customGitUrl"), - customGitBranch: text("customGitBranch"), - customGitSSHKeyId: text("customGitSSHKeyId").references( - () => sshKeys.sshKeyId, - { - onDelete: "set null", - } - ), - command: text("command").notNull().default(""), - // - composePath: text("composePath").notNull().default("./docker-compose.yml"), - suffix: text("suffix").notNull().default(""), - randomize: boolean("randomize").notNull().default(false), - isolatedDeployment: boolean("isolatedDeployment").notNull().default(false), - composeStatus: applicationStatus("composeStatus").notNull().default("idle"), - projectId: text("projectId") - .notNull() - .references(() => projects.projectId, { onDelete: "cascade" }), - createdAt: text("createdAt") - .notNull() - .$defaultFn(() => new Date().toISOString()), + composeId: text("composeId") + .notNull() + .primaryKey() + .$defaultFn(() => nanoid()), + name: text("name").notNull(), + appName: text("appName") + .notNull() + .$defaultFn(() => generateAppName("compose")), + description: text("description"), + env: text("env"), + composeFile: text("composeFile").notNull().default(""), + refreshToken: text("refreshToken").$defaultFn(() => nanoid()), + sourceType: sourceTypeCompose("sourceType").notNull().default("github"), + composeType: composeType("composeType").notNull().default("docker-compose"), + // Github + repository: text("repository"), + owner: text("owner"), + branch: text("branch"), + autoDeploy: boolean("autoDeploy").$defaultFn(() => true), + // Gitlab + gitlabProjectId: integer("gitlabProjectId"), + gitlabRepository: text("gitlabRepository"), + gitlabOwner: text("gitlabOwner"), + gitlabBranch: text("gitlabBranch"), + gitlabPathNamespace: text("gitlabPathNamespace"), + // Bitbucket + bitbucketRepository: text("bitbucketRepository"), + bitbucketOwner: text("bitbucketOwner"), + bitbucketBranch: text("bitbucketBranch"), + // Git + customGitUrl: text("customGitUrl"), + customGitBranch: text("customGitBranch"), + customGitSSHKeyId: text("customGitSSHKeyId").references( + () => sshKeys.sshKeyId, + { + onDelete: "set null", + }, + ), + command: text("command").notNull().default(""), + // + composePath: text("composePath").notNull().default("./docker-compose.yml"), + suffix: text("suffix").notNull().default(""), + randomize: boolean("randomize").notNull().default(false), + isolatedDeployment: boolean("isolatedDeployment").notNull().default(false), + composeStatus: applicationStatus("composeStatus").notNull().default("idle"), + projectId: text("projectId") + .notNull() + .references(() => projects.projectId, { onDelete: "cascade" }), + createdAt: text("createdAt") + .notNull() + .$defaultFn(() => new Date().toISOString()), - githubId: text("githubId").references(() => github.githubId, { - onDelete: "set null", - }), - gitlabId: text("gitlabId").references(() => gitlab.gitlabId, { - onDelete: "set null", - }), - bitbucketId: text("bitbucketId").references(() => bitbucket.bitbucketId, { - onDelete: "set null", - }), - serverId: text("serverId").references(() => server.serverId, { - onDelete: "cascade", - }), + githubId: text("githubId").references(() => github.githubId, { + onDelete: "set null", + }), + gitlabId: text("gitlabId").references(() => gitlab.gitlabId, { + onDelete: "set null", + }), + bitbucketId: text("bitbucketId").references(() => bitbucket.bitbucketId, { + onDelete: "set null", + }), + serverId: text("serverId").references(() => server.serverId, { + onDelete: "cascade", + }), }); export const composeRelations = relations(compose, ({ one, many }) => ({ - project: one(projects, { - fields: [compose.projectId], - references: [projects.projectId], - }), - deployments: many(deployments), - mounts: many(mounts), - customGitSSHKey: one(sshKeys, { - fields: [compose.customGitSSHKeyId], - references: [sshKeys.sshKeyId], - }), - domains: many(domains), - github: one(github, { - fields: [compose.githubId], - references: [github.githubId], - }), - gitlab: one(gitlab, { - fields: [compose.gitlabId], - references: [gitlab.gitlabId], - }), - bitbucket: one(bitbucket, { - fields: [compose.bitbucketId], - references: [bitbucket.bitbucketId], - }), - server: one(server, { - fields: [compose.serverId], - references: [server.serverId], - }), + project: one(projects, { + fields: [compose.projectId], + references: [projects.projectId], + }), + deployments: many(deployments), + mounts: many(mounts), + customGitSSHKey: one(sshKeys, { + fields: [compose.customGitSSHKeyId], + references: [sshKeys.sshKeyId], + }), + domains: many(domains), + github: one(github, { + fields: [compose.githubId], + references: [github.githubId], + }), + gitlab: one(gitlab, { + fields: [compose.gitlabId], + references: [gitlab.gitlabId], + }), + bitbucket: one(bitbucket, { + fields: [compose.bitbucketId], + references: [bitbucket.bitbucketId], + }), + server: one(server, { + fields: [compose.serverId], + references: [server.serverId], + }), })); const createSchema = createInsertSchema(compose, { - name: z.string().min(1), - description: z.string(), - env: z.string().optional(), - composeFile: z.string().min(1), - projectId: z.string(), - customGitSSHKeyId: z.string().optional(), - command: z.string().optional(), - composePath: z.string().min(1), - composeType: z.enum(["docker-compose", "stack"]).optional(), + name: z.string().min(1), + description: z.string(), + env: z.string().optional(), + composeFile: z.string().min(1), + projectId: z.string(), + customGitSSHKeyId: z.string().optional(), + command: z.string().optional(), + composePath: z.string().min(1), + composeType: z.enum(["docker-compose", "stack"]).optional(), }); export const apiCreateCompose = createSchema.pick({ - name: true, - description: true, - projectId: true, - composeType: true, - appName: true, - serverId: true, + name: true, + description: true, + projectId: true, + composeType: true, + appName: true, + serverId: true, }); export const apiCreateComposeByTemplate = createSchema - .pick({ - projectId: true, - }) - .extend({ - id: z.string().min(1), - serverId: z.string().optional(), - }); + .pick({ + projectId: true, + }) + .extend({ + id: z.string().min(1), + serverId: z.string().optional(), + }); export const apiFindCompose = z.object({ - composeId: z.string().min(1), + composeId: z.string().min(1), }); export const apiDeleteCompose = z.object({ - composeId: z.string().min(1), - deleteVolumes: z.boolean(), + composeId: z.string().min(1), + deleteVolumes: z.boolean(), }); export const apiFetchServices = z.object({ - composeId: z.string().min(1), - type: z.enum(["fetch", "cache"]).optional().default("cache"), + composeId: z.string().min(1), + type: z.enum(["fetch", "cache"]).optional().default("cache"), }); export const apiUpdateCompose = createSchema - .partial() - .extend({ - composeId: z.string(), - composeFile: z.string().optional(), - command: z.string().optional(), - }) - .omit({ serverId: true }); + .partial() + .extend({ + composeId: z.string(), + composeFile: z.string().optional(), + command: z.string().optional(), + }) + .omit({ serverId: true }); export const apiRandomizeCompose = createSchema - .pick({ - composeId: true, - }) - .extend({ - suffix: z.string().optional(), - composeId: z.string().min(1), - }); + .pick({ + composeId: true, + }) + .extend({ + suffix: z.string().optional(), + composeId: z.string().min(1), + }); diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts index 648b598f..6f2e8f4e 100644 --- a/packages/server/src/utils/builders/compose.ts +++ b/packages/server/src/utils/builders/compose.ts @@ -1,117 +1,117 @@ import { - createWriteStream, - existsSync, - mkdirSync, - readFileSync, - writeFileSync, + createWriteStream, + existsSync, + mkdirSync, + readFileSync, + writeFileSync, } from "node:fs"; import { dirname, join } from "node:path"; import { paths } from "@dokploy/server/constants"; import type { InferResultType } from "@dokploy/server/types/with"; import boxen from "boxen"; import { - writeDomainsToCompose, - writeDomainsToComposeRemote, + writeDomainsToCompose, + writeDomainsToComposeRemote, } from "../docker/domain"; import { - encodeBase64, - getEnviromentVariablesObject, - prepareEnvironmentVariables, + encodeBase64, + getEnviromentVariablesObject, + prepareEnvironmentVariables, } from "../docker/utils"; import { execAsync, execAsyncRemote } from "../process/execAsync"; import { spawnAsync } from "../process/spawnAsync"; export type ComposeNested = InferResultType< - "compose", - { project: true; mounts: true; domains: true } + "compose", + { project: true; mounts: true; domains: true } >; export const buildCompose = async (compose: ComposeNested, logPath: string) => { - const writeStream = createWriteStream(logPath, { flags: "a" }); - const { sourceType, appName, mounts, composeType, domains } = compose; - try { - const { COMPOSE_PATH } = paths(); - const command = createCommand(compose); - await writeDomainsToCompose(compose, domains); - createEnvFile(compose); + const writeStream = createWriteStream(logPath, { flags: "a" }); + const { sourceType, appName, mounts, composeType, domains } = compose; + try { + const { COMPOSE_PATH } = paths(); + const command = createCommand(compose); + await writeDomainsToCompose(compose, domains); + createEnvFile(compose); - if (compose.isolatedDeployment) { - await execAsync( - `docker network inspect ${compose.appName} >/dev/null 2>&1 || docker network create --attachable ${compose.appName}` - ); - } + if (compose.isolatedDeployment) { + await execAsync( + `docker network inspect ${compose.appName} >/dev/null 2>&1 || docker network create --attachable ${compose.appName}`, + ); + } - const logContent = ` + const logContent = ` App Name: ${appName} Build Compose 🐳 Detected: ${mounts.length} mounts 📂 Command: docker ${command} Source Type: docker ${sourceType} ✅ Compose Type: ${composeType} ✅`; - const logBox = boxen(logContent, { - padding: { - left: 1, - right: 1, - bottom: 1, - }, - width: 80, - borderStyle: "double", - }); - writeStream.write(`\n${logBox}\n`); - const projectPath = join(COMPOSE_PATH, compose.appName, "code"); + const logBox = boxen(logContent, { + padding: { + left: 1, + right: 1, + bottom: 1, + }, + width: 80, + borderStyle: "double", + }); + writeStream.write(`\n${logBox}\n`); + const projectPath = join(COMPOSE_PATH, compose.appName, "code"); - await spawnAsync( - "docker", - [...command.split(" ")], - (data) => { - if (writeStream.writable) { - writeStream.write(data.toString()); - } - }, - { - cwd: projectPath, - env: { - NODE_ENV: process.env.NODE_ENV, - PATH: process.env.PATH, - ...(composeType === "stack" && { - ...getEnviromentVariablesObject(compose.env, compose.project.env), - }), - }, - } - ); + await spawnAsync( + "docker", + [...command.split(" ")], + (data) => { + if (writeStream.writable) { + writeStream.write(data.toString()); + } + }, + { + cwd: projectPath, + env: { + NODE_ENV: process.env.NODE_ENV, + PATH: process.env.PATH, + ...(composeType === "stack" && { + ...getEnviromentVariablesObject(compose.env, compose.project.env), + }), + }, + }, + ); - if (compose.isolatedDeployment) { - await execAsync( - `docker network connect ${compose.appName} $(docker ps --filter "name=dokploy-traefik" -q) >/dev/null 2>&1` - ); - } + if (compose.isolatedDeployment) { + await execAsync( + `docker network connect ${compose.appName} $(docker ps --filter "name=dokploy-traefik" -q) >/dev/null 2>&1`, + ); + } - writeStream.write("Docker Compose Deployed: ✅"); - } catch (error) { - writeStream.write(`Error ❌ ${(error as Error).message}`); - throw error; - } finally { - writeStream.end(); - } + writeStream.write("Docker Compose Deployed: ✅"); + } catch (error) { + writeStream.write(`Error ❌ ${(error as Error).message}`); + throw error; + } finally { + writeStream.end(); + } }; export const getBuildComposeCommand = async ( - compose: ComposeNested, - logPath: string + compose: ComposeNested, + logPath: string, ) => { - const { COMPOSE_PATH } = paths(true); - const { sourceType, appName, mounts, composeType, domains, composePath } = - compose; - const command = createCommand(compose); - const envCommand = getCreateEnvFileCommand(compose); - const projectPath = join(COMPOSE_PATH, compose.appName, "code"); - const exportEnvCommand = getExportEnvCommand(compose); + const { COMPOSE_PATH } = paths(true); + const { sourceType, appName, mounts, composeType, domains, composePath } = + compose; + const command = createCommand(compose); + const envCommand = getCreateEnvFileCommand(compose); + const projectPath = join(COMPOSE_PATH, compose.appName, "code"); + const exportEnvCommand = getExportEnvCommand(compose); - const newCompose = await writeDomainsToComposeRemote( - compose, - domains, - logPath - ); - const logContent = ` + const newCompose = await writeDomainsToComposeRemote( + compose, + domains, + logPath, + ); + const logContent = ` App Name: ${appName} Build Compose 🐳 Detected: ${mounts.length} mounts 📂 @@ -119,17 +119,17 @@ Command: docker ${command} Source Type: docker ${sourceType} ✅ Compose Type: ${composeType} ✅`; - const logBox = boxen(logContent, { - padding: { - left: 1, - right: 1, - bottom: 1, - }, - width: 80, - borderStyle: "double", - }); + const logBox = boxen(logContent, { + padding: { + left: 1, + right: 1, + bottom: 1, + }, + width: 80, + borderStyle: "double", + }); - const bashCommand = ` + const bashCommand = ` set -e { echo "${logBox}" >> "${logPath}" @@ -152,106 +152,106 @@ Compose Type: ${composeType} ✅`; } `; - return await execAsyncRemote(compose.serverId, bashCommand); + return await execAsyncRemote(compose.serverId, bashCommand); }; const sanitizeCommand = (command: string) => { - const sanitizedCommand = command.trim(); + const sanitizedCommand = command.trim(); - const parts = sanitizedCommand.split(/\s+/); + const parts = sanitizedCommand.split(/\s+/); - const restCommand = parts.map((arg) => arg.replace(/^"(.*)"$/, "$1")); + const restCommand = parts.map((arg) => arg.replace(/^"(.*)"$/, "$1")); - return restCommand.join(" "); + return restCommand.join(" "); }; export const createCommand = (compose: ComposeNested) => { - const { composeType, appName, sourceType } = compose; - if (compose.command) { - return `${sanitizeCommand(compose.command)}`; - } + const { composeType, appName, sourceType } = compose; + if (compose.command) { + return `${sanitizeCommand(compose.command)}`; + } - const path = - sourceType === "raw" ? "docker-compose.yml" : compose.composePath; - let command = ""; + const path = + sourceType === "raw" ? "docker-compose.yml" : compose.composePath; + let command = ""; - if (composeType === "docker-compose") { - command = `compose -p ${appName} -f ${path} up -d --build --remove-orphans`; - } else if (composeType === "stack") { - command = `stack deploy -c ${path} ${appName} --prune`; - } + if (composeType === "docker-compose") { + command = `compose -p ${appName} -f ${path} up -d --build --remove-orphans`; + } else if (composeType === "stack") { + command = `stack deploy -c ${path} ${appName} --prune`; + } - return command; + return command; }; const createEnvFile = (compose: ComposeNested) => { - const { COMPOSE_PATH } = paths(); - const { env, composePath, appName } = compose; - const composeFilePath = - join(COMPOSE_PATH, appName, "code", composePath) || - join(COMPOSE_PATH, appName, "code", "docker-compose.yml"); + const { COMPOSE_PATH } = paths(); + const { env, composePath, appName } = compose; + const composeFilePath = + join(COMPOSE_PATH, appName, "code", composePath) || + join(COMPOSE_PATH, appName, "code", "docker-compose.yml"); - const envFilePath = join(dirname(composeFilePath), ".env"); - let envContent = env || ""; - if (!envContent.includes("DOCKER_CONFIG")) { - envContent += "\nDOCKER_CONFIG=/root/.docker/config.json"; - } + const envFilePath = join(dirname(composeFilePath), ".env"); + let envContent = env || ""; + if (!envContent.includes("DOCKER_CONFIG")) { + envContent += "\nDOCKER_CONFIG=/root/.docker/config.json"; + } - if (compose.randomize) { - envContent += `\nCOMPOSE_PREFIX=${compose.suffix}`; - } + if (compose.randomize) { + envContent += `\nCOMPOSE_PREFIX=${compose.suffix}`; + } - const envFileContent = prepareEnvironmentVariables( - envContent, - compose.project.env - ).join("\n"); + const envFileContent = prepareEnvironmentVariables( + envContent, + compose.project.env, + ).join("\n"); - if (!existsSync(dirname(envFilePath))) { - mkdirSync(dirname(envFilePath), { recursive: true }); - } - writeFileSync(envFilePath, envFileContent); + if (!existsSync(dirname(envFilePath))) { + mkdirSync(dirname(envFilePath), { recursive: true }); + } + writeFileSync(envFilePath, envFileContent); }; export const getCreateEnvFileCommand = (compose: ComposeNested) => { - const { COMPOSE_PATH } = paths(true); - const { env, composePath, appName } = compose; - const composeFilePath = - join(COMPOSE_PATH, appName, "code", composePath) || - join(COMPOSE_PATH, appName, "code", "docker-compose.yml"); + const { COMPOSE_PATH } = paths(true); + const { env, composePath, appName } = compose; + const composeFilePath = + join(COMPOSE_PATH, appName, "code", composePath) || + join(COMPOSE_PATH, appName, "code", "docker-compose.yml"); - const envFilePath = join(dirname(composeFilePath), ".env"); + const envFilePath = join(dirname(composeFilePath), ".env"); - let envContent = env || ""; - if (!envContent.includes("DOCKER_CONFIG")) { - envContent += "\nDOCKER_CONFIG=/root/.docker/config.json"; - } + let envContent = env || ""; + if (!envContent.includes("DOCKER_CONFIG")) { + envContent += "\nDOCKER_CONFIG=/root/.docker/config.json"; + } - if (compose.randomize) { - envContent += `\nCOMPOSE_PREFIX=${compose.suffix}`; - } + if (compose.randomize) { + envContent += `\nCOMPOSE_PREFIX=${compose.suffix}`; + } - const envFileContent = prepareEnvironmentVariables( - envContent, - compose.project.env - ).join("\n"); + const envFileContent = prepareEnvironmentVariables( + envContent, + compose.project.env, + ).join("\n"); - const encodedContent = encodeBase64(envFileContent); - return ` + const encodedContent = encodeBase64(envFileContent); + return ` touch ${envFilePath}; echo "${encodedContent}" | base64 -d > "${envFilePath}"; `; }; const getExportEnvCommand = (compose: ComposeNested) => { - if (compose.composeType !== "stack") return ""; + if (compose.composeType !== "stack") return ""; - const envVars = getEnviromentVariablesObject( - compose.env, - compose.project.env - ); - const exports = Object.entries(envVars) - .map(([key, value]) => `export ${key}=${JSON.stringify(value)}`) - .join("\n"); + const envVars = getEnviromentVariablesObject( + compose.env, + compose.project.env, + ); + const exports = Object.entries(envVars) + .map(([key, value]) => `export ${key}=${JSON.stringify(value)}`) + .join("\n"); - return exports ? `\n# Export environment variables\n${exports}\n` : ""; + return exports ? `\n# Export environment variables\n${exports}\n` : ""; }; diff --git a/packages/server/src/utils/docker/collision.ts b/packages/server/src/utils/docker/collision.ts index e0fa725a..d3f131c6 100644 --- a/packages/server/src/utils/docker/collision.ts +++ b/packages/server/src/utils/docker/collision.ts @@ -6,41 +6,41 @@ import { addSuffixToAllVolumes } from "./compose/volume"; import type { ComposeSpecification } from "./types"; export const addAppNameToPreventCollision = ( - composeData: ComposeSpecification, - appName: string + composeData: ComposeSpecification, + appName: string, ): ComposeSpecification => { - let updatedComposeData = { ...composeData }; + let updatedComposeData = { ...composeData }; - updatedComposeData = addAppNameToAllServiceNames(updatedComposeData, appName); - updatedComposeData = addSuffixToAllVolumes(updatedComposeData, appName); - return updatedComposeData; + updatedComposeData = addAppNameToAllServiceNames(updatedComposeData, appName); + updatedComposeData = addSuffixToAllVolumes(updatedComposeData, appName); + return updatedComposeData; }; export const randomizeIsolatedDeploymentComposeFile = async ( - composeId: string, - suffix?: string + composeId: string, + suffix?: string, ) => { - const compose = await findComposeById(composeId); - const composeFile = compose.composeFile; - const composeData = load(composeFile) as ComposeSpecification; + const compose = await findComposeById(composeId); + const composeFile = compose.composeFile; + const composeData = load(composeFile) as ComposeSpecification; - const randomSuffix = suffix || compose.appName || generateRandomHash(); + const randomSuffix = suffix || compose.appName || generateRandomHash(); - const newComposeFile = addAppNameToPreventCollision( - composeData, - randomSuffix - ); + const newComposeFile = addAppNameToPreventCollision( + composeData, + randomSuffix, + ); - return dump(newComposeFile); + return dump(newComposeFile); }; export const randomizeDeployableSpecificationFile = ( - composeSpec: ComposeSpecification, - suffix?: string + composeSpec: ComposeSpecification, + suffix?: string, ) => { - if (!suffix) { - return composeSpec; - } - const newComposeFile = addAppNameToPreventCollision(composeSpec, suffix); - return newComposeFile; + if (!suffix) { + return composeSpec; + } + const newComposeFile = addAppNameToPreventCollision(composeSpec, suffix); + return newComposeFile; }; diff --git a/packages/server/src/utils/docker/domain.ts b/packages/server/src/utils/docker/domain.ts index f9eade7d..8a1b0608 100644 --- a/packages/server/src/utils/docker/domain.ts +++ b/packages/server/src/utils/docker/domain.ts @@ -7,346 +7,346 @@ import type { Domain } from "@dokploy/server/services/domain"; import { dump, load } from "js-yaml"; import { execAsyncRemote } from "../process/execAsync"; import { - cloneRawBitbucketRepository, - cloneRawBitbucketRepositoryRemote, + cloneRawBitbucketRepository, + cloneRawBitbucketRepositoryRemote, } from "../providers/bitbucket"; import { - cloneGitRawRepository, - cloneRawGitRepositoryRemote, + cloneGitRawRepository, + cloneRawGitRepositoryRemote, } from "../providers/git"; import { - cloneRawGithubRepository, - cloneRawGithubRepositoryRemote, + cloneRawGithubRepository, + cloneRawGithubRepositoryRemote, } from "../providers/github"; import { - cloneRawGitlabRepository, - cloneRawGitlabRepositoryRemote, + cloneRawGitlabRepository, + cloneRawGitlabRepositoryRemote, } from "../providers/gitlab"; import { - createComposeFileRaw, - createComposeFileRawRemote, + createComposeFileRaw, + createComposeFileRawRemote, } from "../providers/raw"; import { randomizeDeployableSpecificationFile } from "./collision"; import { randomizeSpecificationFile } from "./compose"; import type { - ComposeSpecification, - DefinitionsService, - PropertiesNetworks, + ComposeSpecification, + DefinitionsService, + PropertiesNetworks, } from "./types"; import { encodeBase64 } from "./utils"; export const cloneCompose = async (compose: Compose) => { - if (compose.sourceType === "github") { - await cloneRawGithubRepository(compose); - } else if (compose.sourceType === "gitlab") { - await cloneRawGitlabRepository(compose); - } else if (compose.sourceType === "bitbucket") { - await cloneRawBitbucketRepository(compose); - } else if (compose.sourceType === "git") { - await cloneGitRawRepository(compose); - } else if (compose.sourceType === "raw") { - await createComposeFileRaw(compose); - } + if (compose.sourceType === "github") { + await cloneRawGithubRepository(compose); + } else if (compose.sourceType === "gitlab") { + await cloneRawGitlabRepository(compose); + } else if (compose.sourceType === "bitbucket") { + await cloneRawBitbucketRepository(compose); + } else if (compose.sourceType === "git") { + await cloneGitRawRepository(compose); + } else if (compose.sourceType === "raw") { + await createComposeFileRaw(compose); + } }; export const cloneComposeRemote = async (compose: Compose) => { - if (compose.sourceType === "github") { - await cloneRawGithubRepositoryRemote(compose); - } else if (compose.sourceType === "gitlab") { - await cloneRawGitlabRepositoryRemote(compose); - } else if (compose.sourceType === "bitbucket") { - await cloneRawBitbucketRepositoryRemote(compose); - } else if (compose.sourceType === "git") { - await cloneRawGitRepositoryRemote(compose); - } else if (compose.sourceType === "raw") { - await createComposeFileRawRemote(compose); - } + if (compose.sourceType === "github") { + await cloneRawGithubRepositoryRemote(compose); + } else if (compose.sourceType === "gitlab") { + await cloneRawGitlabRepositoryRemote(compose); + } else if (compose.sourceType === "bitbucket") { + await cloneRawBitbucketRepositoryRemote(compose); + } else if (compose.sourceType === "git") { + await cloneRawGitRepositoryRemote(compose); + } else if (compose.sourceType === "raw") { + await createComposeFileRawRemote(compose); + } }; export const getComposePath = (compose: Compose) => { - const { COMPOSE_PATH } = paths(!!compose.serverId); - const { appName, sourceType, composePath } = compose; - let path = ""; + const { COMPOSE_PATH } = paths(!!compose.serverId); + const { appName, sourceType, composePath } = compose; + let path = ""; - if (sourceType === "raw") { - path = "docker-compose.yml"; - } else { - path = composePath; - } + if (sourceType === "raw") { + path = "docker-compose.yml"; + } else { + path = composePath; + } - return join(COMPOSE_PATH, appName, "code", path); + return join(COMPOSE_PATH, appName, "code", path); }; export const loadDockerCompose = async ( - compose: Compose + compose: Compose, ): Promise => { - const path = getComposePath(compose); + const path = getComposePath(compose); - if (existsSync(path)) { - const yamlStr = readFileSync(path, "utf8"); - const parsedConfig = load(yamlStr) as ComposeSpecification; - return parsedConfig; - } - return null; + if (existsSync(path)) { + const yamlStr = readFileSync(path, "utf8"); + const parsedConfig = load(yamlStr) as ComposeSpecification; + return parsedConfig; + } + return null; }; export const loadDockerComposeRemote = async ( - compose: Compose + compose: Compose, ): Promise => { - const path = getComposePath(compose); - try { - if (!compose.serverId) { - return null; - } - const { stdout, stderr } = await execAsyncRemote( - compose.serverId, - `cat ${path}` - ); + const path = getComposePath(compose); + try { + if (!compose.serverId) { + return null; + } + const { stdout, stderr } = await execAsyncRemote( + compose.serverId, + `cat ${path}`, + ); - if (stderr) { - return null; - } - if (!stdout) return null; - const parsedConfig = load(stdout) as ComposeSpecification; - return parsedConfig; - } catch (err) { - return null; - } + if (stderr) { + return null; + } + if (!stdout) return null; + const parsedConfig = load(stdout) as ComposeSpecification; + return parsedConfig; + } catch (err) { + return null; + } }; export const readComposeFile = async (compose: Compose) => { - const path = getComposePath(compose); - if (existsSync(path)) { - const yamlStr = readFileSync(path, "utf8"); - return yamlStr; - } - return null; + const path = getComposePath(compose); + if (existsSync(path)) { + const yamlStr = readFileSync(path, "utf8"); + return yamlStr; + } + return null; }; export const writeDomainsToCompose = async ( - compose: Compose, - domains: Domain[] + compose: Compose, + domains: Domain[], ) => { - if (!domains.length) { - return; - } - const composeConverted = await addDomainToCompose(compose, domains); + if (!domains.length) { + return; + } + const composeConverted = await addDomainToCompose(compose, domains); - const path = getComposePath(compose); - const composeString = dump(composeConverted, { lineWidth: 1000 }); - try { - await writeFile(path, composeString, "utf8"); - } catch (error) { - throw error; - } + const path = getComposePath(compose); + const composeString = dump(composeConverted, { lineWidth: 1000 }); + try { + await writeFile(path, composeString, "utf8"); + } catch (error) { + throw error; + } }; export const writeDomainsToComposeRemote = async ( - compose: Compose, - domains: Domain[], - logPath: string + compose: Compose, + domains: Domain[], + logPath: string, ) => { - if (!domains.length) { - return ""; - } + if (!domains.length) { + return ""; + } - try { - const composeConverted = await addDomainToCompose(compose, domains); - const path = getComposePath(compose); + try { + const composeConverted = await addDomainToCompose(compose, domains); + const path = getComposePath(compose); - if (!composeConverted) { - return ` + if (!composeConverted) { + return ` echo "❌ Error: Compose file not found" >> ${logPath}; exit 1; `; - } - if (compose.serverId) { - const composeString = dump(composeConverted, { lineWidth: 1000 }); - const encodedContent = encodeBase64(composeString); - return `echo "${encodedContent}" | base64 -d > "${path}";`; - } - } catch (error) { - // @ts-ignore - return `echo "❌ Has occured an error: ${error?.message || error}" >> ${logPath}; + } + if (compose.serverId) { + const composeString = dump(composeConverted, { lineWidth: 1000 }); + const encodedContent = encodeBase64(composeString); + return `echo "${encodedContent}" | base64 -d > "${path}";`; + } + } catch (error) { + // @ts-ignore + return `echo "❌ Has occured an error: ${error?.message || error}" >> ${logPath}; exit 1; `; - } + } }; // (node:59875) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 SIGTERM listeners added to [process]. Use emitter.setMaxListeners() to increase limit export const addDomainToCompose = async ( - compose: Compose, - domains: Domain[] + compose: Compose, + domains: Domain[], ) => { - const { appName } = compose; + const { appName } = compose; - let result: ComposeSpecification | null; + let result: ComposeSpecification | null; - if (compose.serverId) { - result = await loadDockerComposeRemote(compose); // aca hay que ir al servidor e ir a traer el compose file al servidor - } else { - result = await loadDockerCompose(compose); - } + if (compose.serverId) { + result = await loadDockerComposeRemote(compose); // aca hay que ir al servidor e ir a traer el compose file al servidor + } else { + result = await loadDockerCompose(compose); + } - if (!result || domains.length === 0) { - return null; - } + if (!result || domains.length === 0) { + return null; + } - if (compose.isolatedDeployment) { - const randomized = randomizeDeployableSpecificationFile( - result, - compose.suffix || compose.appName - ); - result = randomized; - } else if (compose.randomize) { - const randomized = randomizeSpecificationFile(result, compose.suffix); - result = randomized; - } + if (compose.isolatedDeployment) { + const randomized = randomizeDeployableSpecificationFile( + result, + compose.suffix || compose.appName, + ); + result = randomized; + } else if (compose.randomize) { + const randomized = randomizeSpecificationFile(result, compose.suffix); + result = randomized; + } - for (const domain of domains) { - const { serviceName, https } = domain; - if (!serviceName) { - throw new Error("Service name not found"); - } - if (!result?.services?.[serviceName]) { - throw new Error(`The service ${serviceName} not found in the compose`); - } + for (const domain of domains) { + const { serviceName, https } = domain; + if (!serviceName) { + throw new Error("Service name not found"); + } + if (!result?.services?.[serviceName]) { + throw new Error(`The service ${serviceName} not found in the compose`); + } - const httpLabels = await createDomainLabels(appName, domain, "web"); - if (https) { - const httpsLabels = await createDomainLabels( - appName, - domain, - "websecure" - ); - httpLabels.push(...httpsLabels); - } + const httpLabels = await createDomainLabels(appName, domain, "web"); + if (https) { + const httpsLabels = await createDomainLabels( + appName, + domain, + "websecure", + ); + httpLabels.push(...httpsLabels); + } - let labels: DefinitionsService["labels"] = []; - if (compose.composeType === "docker-compose") { - if (!result.services[serviceName].labels) { - result.services[serviceName].labels = []; - } + let labels: DefinitionsService["labels"] = []; + if (compose.composeType === "docker-compose") { + if (!result.services[serviceName].labels) { + result.services[serviceName].labels = []; + } - labels = result.services[serviceName].labels; - } else { - // Stack Case - if (!result.services[serviceName].deploy) { - result.services[serviceName].deploy = {}; - } - if (!result.services[serviceName].deploy.labels) { - result.services[serviceName].deploy.labels = []; - } + labels = result.services[serviceName].labels; + } else { + // Stack Case + if (!result.services[serviceName].deploy) { + result.services[serviceName].deploy = {}; + } + if (!result.services[serviceName].deploy.labels) { + result.services[serviceName].deploy.labels = []; + } - labels = result.services[serviceName].deploy.labels; - } + labels = result.services[serviceName].deploy.labels; + } - if (Array.isArray(labels)) { - if (!labels.includes("traefik.enable=true")) { - labels.push("traefik.enable=true"); - } - labels.push(...httpLabels); - } + if (Array.isArray(labels)) { + if (!labels.includes("traefik.enable=true")) { + labels.push("traefik.enable=true"); + } + labels.push(...httpLabels); + } - if (!compose.isolatedDeployment) { - // Add the dokploy-network to the service - result.services[serviceName].networks = addDokployNetworkToService( - result.services[serviceName].networks - ); - } - } + if (!compose.isolatedDeployment) { + // Add the dokploy-network to the service + result.services[serviceName].networks = addDokployNetworkToService( + result.services[serviceName].networks, + ); + } + } - // Add dokploy-network to the root of the compose file - if (!compose.isolatedDeployment) { - result.networks = addDokployNetworkToRoot(result.networks); - } + // Add dokploy-network to the root of the compose file + if (!compose.isolatedDeployment) { + result.networks = addDokployNetworkToRoot(result.networks); + } - return result; + return result; }; export const writeComposeFile = async ( - compose: Compose, - composeSpec: ComposeSpecification + compose: Compose, + composeSpec: ComposeSpecification, ) => { - const path = getComposePath(compose); + const path = getComposePath(compose); - try { - const composeFile = dump(composeSpec, { - lineWidth: 1000, - }); - fs.writeFileSync(path, composeFile, "utf8"); - } catch (e) { - console.error("Error saving the YAML config file:", e); - } + try { + const composeFile = dump(composeSpec, { + lineWidth: 1000, + }); + fs.writeFileSync(path, composeFile, "utf8"); + } catch (e) { + console.error("Error saving the YAML config file:", e); + } }; export const createDomainLabels = async ( - appName: string, - domain: Domain, - entrypoint: "web" | "websecure" + appName: string, + domain: Domain, + entrypoint: "web" | "websecure", ) => { - const { host, port, https, uniqueConfigKey, certificateType, path } = domain; - const routerName = `${appName}-${uniqueConfigKey}-${entrypoint}`; - const labels = [ - `traefik.http.routers.${routerName}.rule=Host(\`${host}\`)${path && path !== "/" ? ` && PathPrefix(\`${path}\`)` : ""}`, - `traefik.http.routers.${routerName}.entrypoints=${entrypoint}`, - `traefik.http.services.${routerName}.loadbalancer.server.port=${port}`, - `traefik.http.routers.${routerName}.service=${routerName}`, - ]; + const { host, port, https, uniqueConfigKey, certificateType, path } = domain; + const routerName = `${appName}-${uniqueConfigKey}-${entrypoint}`; + const labels = [ + `traefik.http.routers.${routerName}.rule=Host(\`${host}\`)${path && path !== "/" ? ` && PathPrefix(\`${path}\`)` : ""}`, + `traefik.http.routers.${routerName}.entrypoints=${entrypoint}`, + `traefik.http.services.${routerName}.loadbalancer.server.port=${port}`, + `traefik.http.routers.${routerName}.service=${routerName}`, + ]; - if (entrypoint === "web" && https) { - labels.push( - `traefik.http.routers.${routerName}.middlewares=redirect-to-https@file` - ); - } + if (entrypoint === "web" && https) { + labels.push( + `traefik.http.routers.${routerName}.middlewares=redirect-to-https@file`, + ); + } - if (entrypoint === "websecure") { - if (certificateType === "letsencrypt") { - labels.push( - `traefik.http.routers.${routerName}.tls.certresolver=letsencrypt` - ); - } - } + if (entrypoint === "websecure") { + if (certificateType === "letsencrypt") { + labels.push( + `traefik.http.routers.${routerName}.tls.certresolver=letsencrypt`, + ); + } + } - return labels; + return labels; }; export const addDokployNetworkToService = ( - networkService: DefinitionsService["networks"] + networkService: DefinitionsService["networks"], ) => { - let networks = networkService; - const network = "dokploy-network"; - if (!networks) { - networks = []; - } + let networks = networkService; + const network = "dokploy-network"; + if (!networks) { + networks = []; + } - if (Array.isArray(networks)) { - if (!networks.includes(network)) { - networks.push(network); - } - } else if (networks && typeof networks === "object") { - if (!(network in networks)) { - networks[network] = {}; - } - } + if (Array.isArray(networks)) { + if (!networks.includes(network)) { + networks.push(network); + } + } else if (networks && typeof networks === "object") { + if (!(network in networks)) { + networks[network] = {}; + } + } - return networks; + return networks; }; export const addDokployNetworkToRoot = ( - networkRoot: PropertiesNetworks | undefined + networkRoot: PropertiesNetworks | undefined, ) => { - let networks = networkRoot; - const network = "dokploy-network"; + let networks = networkRoot; + const network = "dokploy-network"; - if (!networks) { - networks = {}; - } + if (!networks) { + networks = {}; + } - if (networks[network] || !networks[network]) { - networks[network] = { - external: true, - }; - } + if (networks[network] || !networks[network]) { + networks[network] = { + external: true, + }; + } - return networks; + return networks; };