From 0d3c978aadebd2296df5637dfefa7a591ea77b97 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 15 Sep 2024 14:48:40 -0600 Subject: [PATCH] refactor(deployments): improve build error --- .../dokploy/server/api/routers/application.ts | 9 +- apps/dokploy/server/api/routers/compose.ts | 13 +- .../server/api/services/application.ts | 131 ++++++++++-- apps/dokploy/server/api/services/compose.ts | 194 +++++++++++++----- .../server/queues/deployments-queue.ts | 87 +++++--- apps/dokploy/server/utils/builders/compose.ts | 15 +- .../server/utils/builders/docker-file.ts | 26 ++- apps/dokploy/server/utils/builders/heroku.ts | 7 +- apps/dokploy/server/utils/builders/index.ts | 6 +- .../dokploy/server/utils/builders/nixpacks.ts | 74 ++++--- apps/dokploy/server/utils/builders/paketo.ts | 7 +- apps/dokploy/server/utils/builders/static.ts | 35 +++- apps/dokploy/server/utils/builders/utils.ts | 1 - apps/dokploy/server/utils/docker/domain.ts | 19 +- apps/dokploy/server/utils/providers/docker.ts | 12 +- apps/dokploy/server/utils/providers/raw.ts | 6 +- 16 files changed, 464 insertions(+), 178 deletions(-) diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts index 21eb569a6..d72f7021f 100644 --- a/apps/dokploy/server/api/routers/application.ts +++ b/apps/dokploy/server/api/routers/application.ts @@ -189,12 +189,14 @@ export const applicationRouter = createTRPCRouter({ redeploy: protectedProcedure .input(apiFindOneApplication) .mutation(async ({ input }) => { + const application = await findApplicationById(input.applicationId); const jobData: DeploymentJob = { applicationId: input.applicationId, titleLog: "Rebuild deployment", descriptionLog: "", type: "redeploy", applicationType: "application", + server: !!application.serverId, }; await myQueue.add( "deployments", @@ -334,13 +336,14 @@ export const applicationRouter = createTRPCRouter({ deploy: protectedProcedure .input(apiFindOneApplication) .mutation(async ({ input, ctx }) => { - // const application = await findApplicationById(input.applicationId); + const application = await findApplicationById(input.applicationId); const jobData: DeploymentJob = { applicationId: input.applicationId, titleLog: "Manual deployment", descriptionLog: "", type: "deploy", applicationType: "application", + server: !!application.serverId, }; await myQueue.add( "deployments", @@ -350,10 +353,6 @@ export const applicationRouter = createTRPCRouter({ removeOnFail: true, }, ); - // if (!application.serverId) { - // } else { - // await enqueueDeploymentJob(application.serverId, jobData); - // } }), cleanQueues: protectedProcedure diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index 05514cf1b..bccb53752 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -169,14 +169,6 @@ export const composeRouter = createTRPCRouter({ deploy: protectedProcedure .input(apiFindCompose) .mutation(async ({ input }) => { - // const jobData: DeploymentJob = { - // composeId: input.composeId, - // titleLog: "Manual deployment", - // type: "deploy", - // applicationType: "compose", - // descriptionLog: "", - // }; - const compose = await findComposeById(input.composeId); const jobData: DeploymentJob = { composeId: input.composeId, @@ -184,6 +176,7 @@ export const composeRouter = createTRPCRouter({ type: "deploy", applicationType: "compose", descriptionLog: "", + server: !!compose.serverId, }; await myQueue.add( "deployments", @@ -193,10 +186,6 @@ export const composeRouter = createTRPCRouter({ removeOnFail: true, }, ); - // if (!compose.serverId) { - // } else { - // await enqueueDeploymentJob(compose.serverId, jobData); - // } }), redeploy: protectedProcedure .input(apiFindCompose) diff --git a/apps/dokploy/server/api/services/application.ts b/apps/dokploy/server/api/services/application.ts index c17a18fe4..3e537f797 100644 --- a/apps/dokploy/server/api/services/application.ts +++ b/apps/dokploy/server/api/services/application.ts @@ -260,10 +260,66 @@ export const rebuildApplication = async ({ description: descriptionLog, }); + try { + if (application.sourceType === "github") { + await buildApplication(application, deployment.logPath); + } else if (application.sourceType === "gitlab") { + await buildApplication(application, deployment.logPath); + } else if (application.sourceType === "bitbucket") { + await buildApplication(application, deployment.logPath); + } else if (application.sourceType === "docker") { + await buildDocker(application, deployment.logPath); + } else if (application.sourceType === "git") { + await buildApplication(application, deployment.logPath); + } else if (application.sourceType === "drop") { + await buildApplication(application, deployment.logPath); + } + await updateDeploymentStatus(deployment.deploymentId, "done"); + await updateApplicationStatus(applicationId, "done"); + } catch (error) { + await updateDeploymentStatus(deployment.deploymentId, "error"); + await updateApplicationStatus(applicationId, "error"); + throw error; + } + + return true; +}; + +export const deployRemoteApplication = async ({ + applicationId, + titleLog = "Manual deployment", + descriptionLog = "", +}: { + applicationId: string; + titleLog: string; + descriptionLog: string; +}) => { + const application = await findApplicationById(applicationId); + const buildLink = `${await getDokployUrl()}/dashboard/project/${application.projectId}/services/application/${application.applicationId}?tab=deployments`; + const deployment = await createDeployment({ + applicationId: applicationId, + title: titleLog, + description: descriptionLog, + }); + try { if (application.serverId) { let command = "set -e;"; - if (application.sourceType === "docker") { + if (application.sourceType === "github") { + command += await getGithubCloneCommand(application, deployment.logPath); + } else if (application.sourceType === "gitlab") { + command += await getGitlabCloneCommand(application, deployment.logPath); + } else if (application.sourceType === "bitbucket") { + command += await getBitbucketCloneCommand( + application, + deployment.logPath, + ); + } else if (application.sourceType === "git") { + command += await getCustomGitCloneCommand( + application, + deployment.logPath, + ); + } else if (application.sourceType === "docker") { command += await buildRemoteDocker(application, deployment.logPath); } @@ -272,20 +328,67 @@ export const rebuildApplication = async ({ } await execAsyncRemote(application.serverId, command); await mechanizeDockerContainer(application); - } else { - if (application.sourceType === "github") { - await buildApplication(application, deployment.logPath); - } else if (application.sourceType === "gitlab") { - await buildApplication(application, deployment.logPath); - } else if (application.sourceType === "bitbucket") { - await buildApplication(application, deployment.logPath); - } else if (application.sourceType === "docker") { - await buildDocker(application, deployment.logPath); - } else if (application.sourceType === "git") { - await buildApplication(application, deployment.logPath); - } else if (application.sourceType === "drop") { - await buildApplication(application, deployment.logPath); + } + + await updateDeploymentStatus(deployment.deploymentId, "done"); + await updateApplicationStatus(applicationId, "done"); + + await sendBuildSuccessNotifications({ + projectName: application.project.name, + applicationName: application.name, + applicationType: "application", + buildLink, + }); + } catch (error) { + await updateDeploymentStatus(deployment.deploymentId, "error"); + await updateApplicationStatus(applicationId, "error"); + await sendBuildErrorNotifications({ + projectName: application.project.name, + applicationName: application.name, + applicationType: "application", + // @ts-ignore + errorMessage: error?.message || "Error to build", + buildLink, + }); + + console.log( + "Error on ", + application.buildType, + "/", + application.sourceType, + error, + ); + + throw error; + } + + return true; +}; + +export const rebuildRemoteApplication = async ({ + applicationId, + titleLog = "Rebuild deployment", + descriptionLog = "", +}: { + applicationId: string; + titleLog: string; + descriptionLog: string; +}) => { + const application = await findApplicationById(applicationId); + const deployment = await createDeployment({ + applicationId: applicationId, + title: titleLog, + description: descriptionLog, + }); + + try { + if (application.serverId) { + if (application.sourceType !== "docker") { + let command = "set -e;"; + command += getBuildCommand(application, deployment.logPath); + await execAsyncRemote(application.serverId, command); } + await mechanizeDockerContainer(application); } await updateDeploymentStatus(deployment.deploymentId, "done"); await updateApplicationStatus(applicationId, "done"); diff --git a/apps/dokploy/server/api/services/compose.ts b/apps/dokploy/server/api/services/compose.ts index e4ea573b2..d7a5aae4d 100644 --- a/apps/dokploy/server/api/services/compose.ts +++ b/apps/dokploy/server/api/services/compose.ts @@ -210,64 +210,18 @@ export const deployCompose = async ({ }); try { - 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); - } - async function* sequentialSteps() { - yield execAsyncRemote(compose.serverId, command); - yield getBuildComposeCommand(compose, deployment.logPath); - } - - const steps = sequentialSteps(); - for await (const step of steps) { - step; - } - - console.log(" ---- done ----"); - } 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); + 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 updateDeploymentStatus(deployment.deploymentId, "done"); await updateCompose(composeId, { composeStatus: "done", @@ -334,6 +288,132 @@ export const rebuildCompose = async ({ return true; }; +export const deployRemoteCompose = async ({ + composeId, + titleLog = "Manual deployment", + descriptionLog = "", +}: { + composeId: string; + titleLog: string; + descriptionLog: string; +}) => { + const compose = await findComposeById(composeId); + const buildLink = `${await getDokployUrl()}/dashboard/project/${compose.projectId}/services/compose/${compose.composeId}?tab=deployments`; + const deployment = await createDeploymentCompose({ + composeId: composeId, + title: titleLog, + description: descriptionLog, + }); + + try { + 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, deployment.logPath); + } + + async function* sequentialSteps() { + yield execAsyncRemote(compose.serverId, command); + yield getBuildComposeCommand(compose, deployment.logPath); + } + + const steps = sequentialSteps(); + for await (const step of steps) { + step; + } + + console.log(" ---- done ----"); + } + + await updateDeploymentStatus(deployment.deploymentId, "done"); + await updateCompose(composeId, { + composeStatus: "done", + }); + + await sendBuildSuccessNotifications({ + projectName: compose.project.name, + applicationName: compose.name, + applicationType: "compose", + buildLink, + }); + } catch (error) { + await updateDeploymentStatus(deployment.deploymentId, "error"); + await updateCompose(composeId, { + composeStatus: "error", + }); + await sendBuildErrorNotifications({ + projectName: compose.project.name, + applicationName: compose.name, + applicationType: "compose", + // @ts-ignore + errorMessage: error?.message || "Error to build", + buildLink, + }); + throw error; + } +}; + +export const rebuildRemoteCompose = async ({ + composeId, + titleLog = "Rebuild deployment", + descriptionLog = "", +}: { + composeId: string; + titleLog: string; + descriptionLog: string; +}) => { + const compose = await findComposeById(composeId); + const deployment = await createDeploymentCompose({ + composeId: composeId, + title: titleLog, + description: descriptionLog, + }); + + try { + if (compose.serverId) { + await getBuildComposeCommand(compose, deployment.logPath); + } + + await updateDeploymentStatus(deployment.deploymentId, "done"); + await updateCompose(composeId, { + composeStatus: "done", + }); + } catch (error) { + await updateDeploymentStatus(deployment.deploymentId, "error"); + await updateCompose(composeId, { + composeStatus: "error", + }); + throw error; + } + + return true; +}; + export const removeCompose = async (compose: Compose) => { try { const projectPath = join(COMPOSE_PATH, compose.appName); diff --git a/apps/dokploy/server/queues/deployments-queue.ts b/apps/dokploy/server/queues/deployments-queue.ts index bc27f9a2d..7fcae4f39 100644 --- a/apps/dokploy/server/queues/deployments-queue.ts +++ b/apps/dokploy/server/queues/deployments-queue.ts @@ -1,12 +1,16 @@ import { type Job, Worker } from "bullmq"; import { deployApplication, + deployRemoteApplication, rebuildApplication, + rebuildRemoteApplication, updateApplicationStatus, } from "../api/services/application"; import { deployCompose, + deployRemoteCompose, rebuildCompose, + rebuildRemoteCompose, updateCompose, } from "../api/services/compose"; import { myQueue, redisConfig } from "./queueSetup"; @@ -16,6 +20,7 @@ type DeployJob = applicationId: string; titleLog: string; descriptionLog: string; + server?: boolean; type: "deploy" | "redeploy"; applicationType: "application"; } @@ -23,6 +28,7 @@ type DeployJob = composeId: string; titleLog: string; descriptionLog: string; + server?: boolean; type: "deploy" | "redeploy"; applicationType: "compose"; }; @@ -35,35 +41,68 @@ export const deploymentWorker = new Worker( 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, - }); + if (job.data.server) { + if (job.data.type === "redeploy") { + await rebuildRemoteApplication({ + applicationId: job.data.applicationId, + titleLog: job.data.titleLog, + descriptionLog: job.data.descriptionLog, + }); + } else if (job.data.type === "deploy") { + await deployRemoteApplication({ + applicationId: job.data.applicationId, + titleLog: job.data.titleLog, + descriptionLog: job.data.descriptionLog, + }); + } + } else { + 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, - }); + + if (job.data.server) { + if (job.data.type === "redeploy") { + await rebuildRemoteCompose({ + composeId: job.data.composeId, + titleLog: job.data.titleLog, + descriptionLog: job.data.descriptionLog, + }); + } else if (job.data.type === "deploy") { + await deployRemoteCompose({ + composeId: job.data.composeId, + titleLog: job.data.titleLog, + descriptionLog: job.data.descriptionLog, + }); + } + } else { + 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) { diff --git a/apps/dokploy/server/utils/builders/compose.ts b/apps/dokploy/server/utils/builders/compose.ts index 243b518b9..5a5f72da1 100644 --- a/apps/dokploy/server/utils/builders/compose.ts +++ b/apps/dokploy/server/utils/builders/compose.ts @@ -9,6 +9,7 @@ import { COMPOSE_PATH } from "@/server/constants"; import type { InferResultType } from "@/server/types/with"; import boxen from "boxen"; import { + getComposePath, writeDomainsToCompose, writeDomainsToComposeRemote, } from "../docker/domain"; @@ -73,12 +74,17 @@ export const getBuildComposeCommand = async ( compose: ComposeNested, logPath: string, ) => { - const { sourceType, appName, mounts, composeType, domains } = compose; + 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 newCompose = await writeDomainsToComposeRemote(compose, domains); + const newCompose = await writeDomainsToComposeRemote( + compose, + domains, + logPath, + ); const logContent = ` App Name: ${appName} Build Compose 🐳 @@ -107,6 +113,11 @@ Compose Type: ${composeType} ✅`; ${envCommand} cd "${projectPath}"; + + if [ ! -f "${composePath}" ]; then + echo "❌ Error: Compose file not found" >> "${logPath}"; + exit 1; + fi docker ${command.split(" ").join(" ")} >> "${logPath}" 2>&1 || { echo "Error: ❌ Docker command failed" >> "${logPath}"; exit 1; } diff --git a/apps/dokploy/server/utils/builders/docker-file.ts b/apps/dokploy/server/utils/builders/docker-file.ts index 3ea8e2fa4..ab5cc0669 100644 --- a/apps/dokploy/server/utils/builders/docker-file.ts +++ b/apps/dokploy/server/utils/builders/docker-file.ts @@ -6,7 +6,7 @@ import { getDockerContextPath, } from "../filesystem/directory"; import { spawnAsync } from "../process/spawnAsync"; -import { createEnvFile } from "./utils"; +import { createEnvFile, createEnvFileCommand } from "./utils"; export const buildCustomDocker = async ( application: ApplicationNested, @@ -86,11 +86,27 @@ export const getDockerCommand = ( commandArgs.push("--build-arg", arg); } - const command = ` + /* + Do not generate an environment file when publishDirectory is specified, + as it could be publicly exposed. + */ + let command = ""; + if (!publishDirectory) { + command += createEnvFileCommand(dockerFilePath, env); + } + + command = ` echo "Building ${appName}" >> ${logPath}; -cd ${dockerContextPath} || exit 1; -docker ${commandArgs.join(" ")} >> ${logPath} 2>&1; -echo "Docker build completed." >> ${logPath}; +cd ${dockerContextPath} >> ${logPath} 2>> ${logPath} || { + echo "❌ The path ${dockerContextPath} does not exist" >> ${logPath}; + exit 1; +} + +docker ${commandArgs.join(" ")} >> ${logPath} 2>> ${logPath} || { + echo "❌ Docker build failed" >> ${logPath}; + exit 1; +} +echo "✅ Docker build completed." >> ${logPath}; `; return command; diff --git a/apps/dokploy/server/utils/builders/heroku.ts b/apps/dokploy/server/utils/builders/heroku.ts index 0bbc7630b..be7f7877b 100644 --- a/apps/dokploy/server/utils/builders/heroku.ts +++ b/apps/dokploy/server/utils/builders/heroku.ts @@ -62,8 +62,11 @@ export const getHerokuCommand = ( const command = `pack ${args.join(" ")}`; const bashCommand = ` echo "Starting heroku build..." >> ${logPath}; -${command} >> ${logPath} 2>&1; -echo "Heroku build completed." >> ${logPath}; +${command} >> ${logPath} 2>> ${logPath} || { + echo "❌ Heroku build failed" >> ${logPath}; + exit 1; +} +echo "✅ Heroku build completed." >> ${logPath}; `; return bashCommand; diff --git a/apps/dokploy/server/utils/builders/index.ts b/apps/dokploy/server/utils/builders/index.ts index 3ceea8311..c38152b2e 100644 --- a/apps/dokploy/server/utils/builders/index.ts +++ b/apps/dokploy/server/utils/builders/index.ts @@ -16,7 +16,7 @@ import { buildCustomDocker, getDockerCommand } from "./docker-file"; import { buildHeroku, getHerokuCommand } from "./heroku"; import { buildNixpacks, getNixpacksCommand } from "./nixpacks"; import { buildPaketo, getPaketoCommand } from "./paketo"; -import { buildStatic } from "./static"; +import { buildStatic, getStaticCommand } from "./static"; import { findServerById } from "@/server/api/services/server"; import { readSSHKey } from "../filesystem/ssh"; import { getRemoteDocker } from "../servers/remote-docker"; @@ -81,8 +81,8 @@ export const getBuildCommand = ( return getHerokuCommand(application, logPath); case "paketo_buildpacks": return getPaketoCommand(application, logPath); - // case "static": - // return buildStatic(application, writeStream); + case "static": + return getStaticCommand(application, logPath); case "dockerfile": return getDockerCommand(application, logPath); } diff --git a/apps/dokploy/server/utils/builders/nixpacks.ts b/apps/dokploy/server/utils/builders/nixpacks.ts index b1704aed9..c0ce90479 100644 --- a/apps/dokploy/server/utils/builders/nixpacks.ts +++ b/apps/dokploy/server/utils/builders/nixpacks.ts @@ -1,12 +1,11 @@ import type { WriteStream } from "node:fs"; import path from "node:path"; -import { buildStatic } from "@/server/utils/builders/static"; +import { buildStatic, getStaticCommand } from "@/server/utils/builders/static"; import { nanoid } from "nanoid"; import type { ApplicationNested } from "."; import { prepareEnvironmentVariables } from "../docker/utils"; import { getBuildAppDirectory } from "../filesystem/directory"; import { spawnAsync } from "../process/spawnAsync"; -import { executeCommand } from "../servers/command"; export const buildNixpacks = async ( application: ApplicationNested, @@ -83,47 +82,44 @@ export const getNixpacksCommand = ( const buildContainerId = `${appName}-${nanoid(10)}`; const envVariables = prepareEnvironmentVariables(env); - try { - const args = ["build", buildAppDirectory, "--name", appName]; + const args = ["build", buildAppDirectory, "--name", appName]; - for (const env of envVariables) { - args.push("--env", env); - } + for (const env of envVariables) { + args.push("--env", env); + } - if (publishDirectory) { - /* No need for any start command, since we'll use nginx later on */ - args.push("--no-error-without-start"); - } - console.log("args", args); - const command = `nixpacks ${args.join(" ")}`; - const bashCommand = ` + if (publishDirectory) { + /* No need for any start command, since we'll use nginx later on */ + args.push("--no-error-without-start"); + } + console.log("args", args); + const command = `nixpacks ${args.join(" ")}`; + let bashCommand = ` echo "Starting nixpacks build..." >> ${logPath}; -${command} >> ${logPath} 2>&1; -echo "Nixpacks build completed." >> ${logPath}; +${command} >> ${logPath} 2>> ${logPath} || { + echo "❌ Nixpacks build failed" >> ${logPath}; + exit 1; +} +echo "✅ Nixpacks build completed." >> ${logPath}; `; - /* - Run the container with the image created by nixpacks, - and copy the artifacts on the host filesystem. - Then, remove the container and create a static build. - */ - - if (publishDirectory) { - } - - // if (publishDirectory) { - // bashCommand += ` - // docker create --name ${buildContainerId} ${appName} - // docker cp ${buildContainerId}:/app/${publishDirectory} ${path.join(buildAppDirectory, publishDirectory)} - // docker rm ${buildContainerId} - // buildStatic ${application} ${writeStream} - // `; - // } - - return bashCommand; - } catch (e) { - // await spawnAsync("docker", ["rm", buildContainerId], writeToStream); - - throw e; + /* + Run the container with the image created by nixpacks, + and copy the artifacts on the host filesystem. + Then, remove the container and create a static build. + */ + if (publishDirectory) { + bashCommand += ` +docker create --name ${buildContainerId} ${appName} +docker cp ${buildContainerId}:/app/${publishDirectory} ${path.join(buildAppDirectory, publishDirectory)} >> ${logPath} 2>> ${logPath} || { + docker rm ${buildContainerId} + echo "❌ Copying ${publishDirectory} to ${path.join(buildAppDirectory, publishDirectory)} failed" >> ${logPath}; + exit 1; +} +docker rm ${buildContainerId} +${getStaticCommand(application, logPath)} + `; } + + return bashCommand; }; diff --git a/apps/dokploy/server/utils/builders/paketo.ts b/apps/dokploy/server/utils/builders/paketo.ts index 2faa4de68..1faf42aea 100644 --- a/apps/dokploy/server/utils/builders/paketo.ts +++ b/apps/dokploy/server/utils/builders/paketo.ts @@ -61,8 +61,11 @@ export const getPaketoCommand = ( const command = `pack ${args.join(" ")}`; const bashCommand = ` echo "Starting Paketo build..." >> ${logPath}; -${command} >> ${logPath} 2>&1; -echo "Paketo build completed." >> ${logPath}; +${command} >> ${logPath} 2>> ${logPath} || { + echo "❌ Paketo build failed" >> ${logPath}; + exit 1; +} +echo "✅ Paketo build completed." >> ${logPath}; `; return bashCommand; diff --git a/apps/dokploy/server/utils/builders/static.ts b/apps/dokploy/server/utils/builders/static.ts index 37f1beefe..2f3d4bc2f 100644 --- a/apps/dokploy/server/utils/builders/static.ts +++ b/apps/dokploy/server/utils/builders/static.ts @@ -1,7 +1,10 @@ import type { WriteStream } from "node:fs"; -import { buildCustomDocker } from "@/server/utils/builders/docker-file"; +import { + buildCustomDocker, + getDockerCommand, +} from "@/server/utils/builders/docker-file"; import type { ApplicationNested } from "."; -import { createFile } from "../docker/utils"; +import { createFile, getCreateFileCommand } from "../docker/utils"; import { getBuildAppDirectory } from "../filesystem/directory"; export const buildStatic = async ( @@ -36,3 +39,31 @@ export const buildStatic = async ( throw e; } }; + +export const getStaticCommand = ( + application: ApplicationNested, + logPath: string, +) => { + const { publishDirectory } = application; + const buildAppDirectory = getBuildAppDirectory(application); + + let command = getCreateFileCommand( + buildAppDirectory, + "Dockerfile", + [ + "FROM nginx:alpine", + "WORKDIR /usr/share/nginx/html/", + `COPY ${publishDirectory || "."} .`, + ].join("\n"), + ); + + command += getDockerCommand( + { + ...application, + buildType: "dockerfile", + dockerfile: "Dockerfile", + }, + logPath, + ); + return command; +}; diff --git a/apps/dokploy/server/utils/builders/utils.ts b/apps/dokploy/server/utils/builders/utils.ts index 78c46de76..3eeb45225 100644 --- a/apps/dokploy/server/utils/builders/utils.ts +++ b/apps/dokploy/server/utils/builders/utils.ts @@ -13,7 +13,6 @@ export const createEnvFile = (directory: string, env: string | null) => { export const createEnvFileCommand = (directory: string, env: string | null) => { const envFilePath = join(dirname(directory), ".env"); - // let command = `` if (!existsSync(dirname(envFilePath))) { mkdirSync(dirname(envFilePath), { recursive: true }); } diff --git a/apps/dokploy/server/utils/docker/domain.ts b/apps/dokploy/server/utils/docker/domain.ts index 5ad5b7968..f51effb21 100644 --- a/apps/dokploy/server/utils/docker/domain.ts +++ b/apps/dokploy/server/utils/docker/domain.ts @@ -96,7 +96,14 @@ export const loadDockerComposeRemote = async ( if (!compose.serverId) { return null; } - const { stdout } = await execAsyncRemote(compose.serverId, `cat ${path}`); + 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; @@ -135,21 +142,25 @@ export const writeDomainsToCompose = async ( export const writeDomainsToComposeRemote = async ( compose: Compose, domains: Domain[], + logPath: string, ) => { if (!domains.length) { return ""; } - const composeConverted = await addDomainToCompose(compose, domains); - const path = getComposePath(compose); try { + const composeConverted = await addDomainToCompose(compose, domains); + const path = getComposePath(compose); if (compose.serverId) { const composeString = dump(composeConverted, { lineWidth: 1000 }); const encodedContent = encodeBase64(composeString); return `echo "${encodedContent}" | base64 -d > "${path}";`; } } catch (error) { - throw error; + 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 diff --git a/apps/dokploy/server/utils/providers/docker.ts b/apps/dokploy/server/utils/providers/docker.ts index 9f5e431aa..77c4db57f 100644 --- a/apps/dokploy/server/utils/providers/docker.ts +++ b/apps/dokploy/server/utils/providers/docker.ts @@ -59,24 +59,26 @@ export const buildRemoteDocker = async ( throw new Error("Docker image not found"); } let command = ` -echo "Building ${sourceType}" >> ${logPath}; echo "Pulling ${dockerImage}" >> ${logPath}; `; if (username && password) { command += ` if ! docker login --username ${username} --password ${password} https://index.docker.io/v1/ >> ${logPath} 2>&1; then - echo "Error logging in to Docker Hub" >> ${logPath}; + echo "❌ Login failed" >> ${logPath}; exit 1; fi `; } command += ` -echo "Pulling ${dockerImage}" >> ${logPath}; -docker pull ${dockerImage} >> ${logPath} 2>&1; -`; +docker pull ${dockerImage} >> ${logPath} 2>> ${logPath} || { + echo "❌ Pulling image failed" >> ${logPath}; + exit 1; +} +echo "✅ Pulling image completed." >> ${logPath}; +`; return command; } catch (error) { throw error; diff --git a/apps/dokploy/server/utils/providers/raw.ts b/apps/dokploy/server/utils/providers/raw.ts index ea559675d..fc522c7e2 100644 --- a/apps/dokploy/server/utils/providers/raw.ts +++ b/apps/dokploy/server/utils/providers/raw.ts @@ -29,7 +29,10 @@ export const createComposeFile = async (compose: Compose, logPath: string) => { } }; -export const getCreateComposeFileCommand = (compose: Compose) => { +export const getCreateComposeFileCommand = ( + compose: Compose, + logPath: string, +) => { const { appName, composeFile } = compose; const outputPath = join(COMPOSE_PATH, appName, "code"); const filePath = join(outputPath, "docker-compose.yml"); @@ -38,6 +41,7 @@ export const getCreateComposeFileCommand = (compose: Compose) => { rm -rf ${outputPath}; mkdir -p ${outputPath}; echo "${encodedContent}" | base64 -d > "${filePath}"; + echo "File 'docker-compose.yml' created: ✅" >> ${logPath}; `; return bashCommand; };