+ {service.serverName && (
+
+
+
+ {service.serverName}
+
+
+ )}
Created
diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts
index 006d024c4..c713fd7eb 100644
--- a/apps/dokploy/server/api/routers/application.ts
+++ b/apps/dokploy/server/api/routers/application.ts
@@ -58,7 +58,11 @@ import {
applications,
} from "@/server/db/schema";
import type { DeploymentJob } from "@/server/queues/queue-types";
-import { cleanQueuesByApplication, myQueue } from "@/server/queues/queueSetup";
+import {
+ cleanQueuesByApplication,
+ killDockerBuild,
+ myQueue,
+} from "@/server/queues/queueSetup";
import { cancelDeployment, deploy } from "@/server/utils/deploy";
import { uploadFileSchema } from "@/utils/schema";
@@ -725,7 +729,21 @@ export const applicationRouter = createTRPCRouter({
}
await cleanQueuesByApplication(input.applicationId);
}),
-
+ killBuild: protectedProcedure
+ .input(apiFindOneApplication)
+ .mutation(async ({ input, ctx }) => {
+ const application = await findApplicationById(input.applicationId);
+ if (
+ application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to kill this build",
+ });
+ }
+ await killDockerBuild("application", application.serverId);
+ }),
readTraefikConfig: protectedProcedure
.input(apiFindOneApplication)
.query(async ({ input, ctx }) => {
diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts
index e2f25b763..e233dc6ca 100644
--- a/apps/dokploy/server/api/routers/compose.ts
+++ b/apps/dokploy/server/api/routers/compose.ts
@@ -3,13 +3,14 @@ import {
addNewService,
checkServiceAccess,
cloneCompose,
- cloneComposeRemote,
createCommand,
createCompose,
createComposeByTemplate,
createDomain,
createMount,
deleteMount,
+ execAsync,
+ execAsyncRemote,
findComposeById,
findDomainsByComposeId,
findEnvironmentById,
@@ -58,7 +59,11 @@ import {
compose as composeTable,
} from "@/server/db/schema";
import type { DeploymentJob } from "@/server/queues/queue-types";
-import { cleanQueuesByCompose, myQueue } from "@/server/queues/queueSetup";
+import {
+ cleanQueuesByCompose,
+ killDockerBuild,
+ myQueue,
+} from "@/server/queues/queueSetup";
import { cancelDeployment, deploy } from "@/server/utils/deploy";
import { generatePassword } from "@/templates/utils";
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
@@ -247,6 +252,21 @@ export const composeRouter = createTRPCRouter({
await cleanQueuesByCompose(input.composeId);
return { success: true, message: "Queues cleaned successfully" };
}),
+ killBuild: protectedProcedure
+ .input(apiFindCompose)
+ .mutation(async ({ input, ctx }) => {
+ const compose = await findComposeById(input.composeId);
+ if (
+ compose.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to kill this build",
+ });
+ }
+ await killDockerBuild("compose", compose.serverId);
+ }),
loadServices: protectedProcedure
.input(apiFetchServices)
@@ -302,10 +322,12 @@ export const composeRouter = createTRPCRouter({
message: "You are not authorized to fetch this compose",
});
}
+
+ const command = await cloneCompose(compose);
if (compose.serverId) {
- await cloneComposeRemote(compose);
+ await execAsyncRemote(compose.serverId, command);
} else {
- await cloneCompose(compose);
+ await execAsync(command);
}
return compose.sourceType;
} catch (err) {
diff --git a/apps/dokploy/server/api/routers/destination.ts b/apps/dokploy/server/api/routers/destination.ts
index dc5892c85..d7cbf53d5 100644
--- a/apps/dokploy/server/api/routers/destination.ts
+++ b/apps/dokploy/server/api/routers/destination.ts
@@ -47,15 +47,19 @@ export const destinationRouter = createTRPCRouter({
input;
try {
const rcloneFlags = [
- `--s3-access-key-id=${accessKey}`,
- `--s3-secret-access-key=${secretAccessKey}`,
- `--s3-region=${region}`,
- `--s3-endpoint=${endpoint}`,
+ `--s3-access-key-id="${accessKey}"`,
+ `--s3-secret-access-key="${secretAccessKey}"`,
+ `--s3-region="${region}"`,
+ `--s3-endpoint="${endpoint}"`,
"--s3-no-check-bucket",
"--s3-force-path-style",
+ "--retries 1",
+ "--low-level-retries 1",
+ "--timeout 10s",
+ "--contimeout 5s",
];
if (provider) {
- rcloneFlags.unshift(`--s3-provider=${provider}`);
+ rcloneFlags.unshift(`--s3-provider="${provider}"`);
}
const rcloneDestination = `:s3:${bucket}`;
const rcloneCommand = `rclone ls ${rcloneFlags.join(" ")} "${rcloneDestination}"`;
diff --git a/apps/dokploy/server/api/routers/notification.ts b/apps/dokploy/server/api/routers/notification.ts
index 0edba5732..14d5b8363 100644
--- a/apps/dokploy/server/api/routers/notification.ts
+++ b/apps/dokploy/server/api/routers/notification.ts
@@ -117,7 +117,7 @@ export const notificationRouter = createTRPCRouter({
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error testing the notification",
+ message: `${error instanceof Error ? error.message : "Unknown error"}`,
cause: error,
});
}
@@ -234,7 +234,7 @@ export const notificationRouter = createTRPCRouter({
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error testing the notification",
+ message: `${error instanceof Error ? error.message : "Unknown error"}`,
cause: error,
});
}
@@ -291,7 +291,7 @@ export const notificationRouter = createTRPCRouter({
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error testing the notification",
+ message: `${error instanceof Error ? error.message : "Unknown error"}`,
cause: error,
});
}
diff --git a/apps/dokploy/server/api/routers/postgres.ts b/apps/dokploy/server/api/routers/postgres.ts
index a05072ab7..3112beb66 100644
--- a/apps/dokploy/server/api/routers/postgres.ts
+++ b/apps/dokploy/server/api/routers/postgres.ts
@@ -8,6 +8,7 @@ import {
findEnvironmentById,
findPostgresById,
findProjectById,
+ getMountPath,
IS_CLOUD,
rebuildDatabase,
removePostgresById,
@@ -37,6 +38,7 @@ import {
postgres as postgresTable,
} from "@/server/db/schema";
import { cancelJobs } from "@/server/utils/backup";
+
export const postgresRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreatePostgres)
@@ -79,11 +81,13 @@ export const postgresRouter = createTRPCRouter({
);
}
+ const mountPath = getMountPath(input.dockerImage);
+
await createMount({
serviceId: newPostgres.postgresId,
serviceType: "postgres",
volumeName: `${newPostgres.appName}-data`,
- mountPath: "/var/lib/postgresql/data",
+ mountPath: mountPath,
type: "volume",
});
@@ -282,12 +286,16 @@ export const postgresRouter = createTRPCRouter({
const backups = await findBackupsByDbId(input.postgresId, "postgres");
const cleanupOperations = [
- removeService(postgres.appName, postgres.serverId),
- cancelJobs(backups),
- removePostgresById(input.postgresId),
+ async () => await removeService(postgres?.appName, postgres.serverId),
+ async () => await cancelJobs(backups),
+ async () => await removePostgresById(input.postgresId),
];
- await Promise.allSettled(cleanupOperations);
+ for (const operation of cleanupOperations) {
+ try {
+ await operation();
+ } catch (_) {}
+ }
return postgres;
}),
@@ -363,6 +371,7 @@ export const postgresRouter = createTRPCRouter({
message: "You are not authorized to update this Postgres",
});
}
+
const service = await updatePostgresById(postgresId, {
...rest,
});
diff --git a/apps/dokploy/server/api/routers/server.ts b/apps/dokploy/server/api/routers/server.ts
index d6904a7ec..8a01228f8 100644
--- a/apps/dokploy/server/api/routers/server.ts
+++ b/apps/dokploy/server/api/routers/server.ts
@@ -383,6 +383,15 @@ export const serverRouter = createTRPCRouter({
const ip = await getPublicIpWithFallback();
return ip;
}),
+ getServerTime: protectedProcedure.query(() => {
+ if (IS_CLOUD) {
+ return null;
+ }
+ return {
+ time: new Date(),
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
+ };
+ }),
getServerMetrics: protectedProcedure
.input(
z.object({
diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts
index b4968c260..e0a74f5cf 100644
--- a/apps/dokploy/server/api/routers/settings.ts
+++ b/apps/dokploy/server/api/routers/settings.ts
@@ -587,7 +587,7 @@ export const settingsRouter = createTRPCRouter({
return ports.some((port) => port.targetPort === 8080);
}),
- readStatsLogs: adminProcedure
+ readStatsLogs: protectedProcedure
.meta({
openapi: {
path: "/read-stats-logs",
@@ -650,7 +650,7 @@ export const settingsRouter = createTRPCRouter({
const processedLogs = processLogs(rawConfig as string, input?.dateRange);
return processedLogs || [];
}),
- haveActivateRequests: adminProcedure.query(async () => {
+ haveActivateRequests: protectedProcedure.query(async () => {
if (IS_CLOUD) {
return true;
}
@@ -665,7 +665,7 @@ export const settingsRouter = createTRPCRouter({
return !!parsedConfig?.accessLog?.filePath;
}),
- toggleRequests: adminProcedure
+ toggleRequests: protectedProcedure
.input(
z.object({
enable: z.boolean(),
@@ -835,7 +835,7 @@ export const settingsRouter = createTRPCRouter({
const ports = await readPorts("dokploy-traefik", input?.serverId);
return ports;
}),
- updateLogCleanup: adminProcedure
+ updateLogCleanup: protectedProcedure
.input(
z.object({
cronExpression: z.string().nullable(),
@@ -851,7 +851,7 @@ export const settingsRouter = createTRPCRouter({
return stopLogCleanup();
}),
- getLogCleanupStatus: adminProcedure.query(async () => {
+ getLogCleanupStatus: protectedProcedure.query(async () => {
return getLogCleanupStatus();
}),
diff --git a/apps/dokploy/server/api/routers/user.ts b/apps/dokploy/server/api/routers/user.ts
index d30b99b3a..baec4d6ac 100644
--- a/apps/dokploy/server/api/routers/user.ts
+++ b/apps/dokploy/server/api/routers/user.ts
@@ -4,6 +4,7 @@ import {
findNotificationById,
findOrganizationById,
findUserById,
+ getDokployUrl,
getUserByToken,
IS_CLOUD,
removeUserById,
@@ -419,11 +420,10 @@ export const userRouter = createTRPCRouter({
});
}
- const admin = await findAdmin();
const host =
process.env.NODE_ENV === "development"
? "http://localhost:3000"
- : admin.user.host;
+ : await getDokployUrl();
const inviteLink = `${host}/invitation?token=${input.invitationId}`;
const organization = await findOrganizationById(
diff --git a/apps/dokploy/server/queues/deployments-queue.ts b/apps/dokploy/server/queues/deployments-queue.ts
index b8dfb8cd0..4c117e7e3 100644
--- a/apps/dokploy/server/queues/deployments-queue.ts
+++ b/apps/dokploy/server/queues/deployments-queue.ts
@@ -2,13 +2,8 @@ import {
deployApplication,
deployCompose,
deployPreviewApplication,
- deployRemoteApplication,
- deployRemoteCompose,
- deployRemotePreviewApplication,
rebuildApplication,
rebuildCompose,
- rebuildRemoteApplication,
- rebuildRemoteCompose,
updateApplicationStatus,
updateCompose,
updatePreviewDeployment,
@@ -24,91 +19,48 @@ export const deploymentWorker = new Worker(
if (job.data.applicationType === "application") {
await updateApplicationStatus(job.data.applicationId, "running");
- 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,
- });
- }
+ 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.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,
- });
- }
+ 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,
+ });
}
} else if (job.data.applicationType === "application-preview") {
await updatePreviewDeployment(job.data.previewDeploymentId, {
previewStatus: "running",
});
- if (job.data.server) {
- if (job.data.type === "deploy") {
- await deployRemotePreviewApplication({
- applicationId: job.data.applicationId,
- titleLog: job.data.titleLog,
- descriptionLog: job.data.descriptionLog,
- previewDeploymentId: job.data.previewDeploymentId,
- });
- }
- } else {
- if (job.data.type === "deploy") {
- await deployPreviewApplication({
- applicationId: job.data.applicationId,
- titleLog: job.data.titleLog,
- descriptionLog: job.data.descriptionLog,
- previewDeploymentId: job.data.previewDeploymentId,
- });
- }
+
+ if (job.data.type === "deploy") {
+ await deployPreviewApplication({
+ applicationId: job.data.applicationId,
+ titleLog: job.data.titleLog,
+ descriptionLog: job.data.descriptionLog,
+ previewDeploymentId: job.data.previewDeploymentId,
+ });
}
}
} catch (error) {
diff --git a/apps/dokploy/server/queues/queueSetup.ts b/apps/dokploy/server/queues/queueSetup.ts
index 1577273c8..351f5d1c0 100644
--- a/apps/dokploy/server/queues/queueSetup.ts
+++ b/apps/dokploy/server/queues/queueSetup.ts
@@ -1,3 +1,7 @@
+import {
+ execAsync,
+ execAsyncRemote,
+} from "@dokploy/server/utils/process/execAsync";
import { Queue } from "bullmq";
import { redisConfig } from "./redis-connection";
@@ -41,4 +45,31 @@ export const cleanQueuesByCompose = async (composeId: string) => {
}
};
+export const killDockerBuild = async (
+ type: "application" | "compose",
+ serverId: string | null,
+) => {
+ try {
+ if (type === "application") {
+ const command = `pkill -2 -f "docker build"`;
+
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command);
+ }
+ } else if (type === "compose") {
+ const command = `pkill -2 -f "docker compose"`;
+
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command);
+ }
+ }
+ } catch (error) {
+ console.error(error);
+ }
+};
+
export { myQueue };
diff --git a/apps/dokploy/server/wss/docker-stats.ts b/apps/dokploy/server/wss/docker-stats.ts
index 99e993dce..bd740e976 100644
--- a/apps/dokploy/server/wss/docker-stats.ts
+++ b/apps/dokploy/server/wss/docker-stats.ts
@@ -2,6 +2,7 @@ import type http from "node:http";
import {
docker,
execAsync,
+ getHostSystemStats,
getLastAdvancedStatsFile,
recordAdvancedStats,
validateRequest,
@@ -49,6 +50,21 @@ export const setupDockerStatsMonitoringSocketServer = (
}
const intervalId = setInterval(async () => {
try {
+ // Special case: when monitoring "dokploy", get host system stats instead of container stats
+ if (appName === "dokploy") {
+ const stat = await getHostSystemStats();
+
+ await recordAdvancedStats(stat, appName);
+ const data = await getLastAdvancedStatsFile(appName);
+
+ ws.send(
+ JSON.stringify({
+ data,
+ }),
+ );
+ return;
+ }
+
const filter = {
status: ["running"],
...(appType === "application" && {
diff --git a/apps/dokploy/setup.ts b/apps/dokploy/setup.ts
index 55e1da87c..e0ccb86d8 100644
--- a/apps/dokploy/setup.ts
+++ b/apps/dokploy/setup.ts
@@ -22,7 +22,7 @@ import {
await initializeNetwork();
createDefaultTraefikConfig();
createDefaultServerTraefikConfig();
- await execAsync("docker pull traefik:v3.5.0");
+ await execAsync("docker pull traefik:v3.6.1");
await initializeStandaloneTraefik();
await initializeRedis();
await initializePostgres();
diff --git a/packages/server/package.json b/packages/server/package.json
index dcdc57e79..e23fa6d8b 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -62,7 +62,7 @@
"lodash": "4.17.21",
"micromatch": "4.0.8",
"nanoid": "3.3.11",
- "node-os-utils": "1.3.7",
+ "node-os-utils": "2.0.1",
"node-pty": "1.0.0",
"node-schedule": "2.1.1",
"nodemailer": "6.9.14",
@@ -76,6 +76,7 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"rotating-file-stream": "3.2.3",
+ "shell-quote": "^1.8.1",
"slugify": "^1.6.6",
"ssh2": "1.15.0",
"toml": "3.0.0",
@@ -89,12 +90,12 @@
"@types/lodash": "4.17.4",
"@types/micromatch": "4.0.9",
"@types/node": "^18.19.104",
- "@types/node-os-utils": "1.3.4",
"@types/node-schedule": "2.1.6",
"@types/nodemailer": "^6.4.17",
"@types/qrcode": "^1.5.5",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
+ "@types/shell-quote": "^1.7.5",
"@types/ssh2": "1.15.1",
"@types/ws": "8.5.10",
"drizzle-kit": "^0.30.6",
diff --git a/packages/server/src/emails/emails/build-success.tsx b/packages/server/src/emails/emails/build-success.tsx
index d9e500ab9..e5e1d1bb4 100644
--- a/packages/server/src/emails/emails/build-success.tsx
+++ b/packages/server/src/emails/emails/build-success.tsx
@@ -19,6 +19,7 @@ export type TemplateProps = {
applicationType: string;
buildLink: string;
date: string;
+ environmentName: string;
};
export const BuildSuccessEmail = ({
@@ -27,6 +28,7 @@ export const BuildSuccessEmail = ({
applicationType = "application",
buildLink = "https://dokploy.com/projects/dokploy-test/applications/dokploy-test",
date = "2023-05-01T00:00:00.000Z",
+ environmentName = "production",
}: TemplateProps) => {
const previewText = `Build success for ${applicationName}`;
return (
@@ -74,6 +76,9 @@ export const BuildSuccessEmail = ({
Application Name: {applicationName}
+
+ Environment: {environmentName}
+
Application Type: {applicationType}
diff --git a/packages/server/src/monitoring/utils.ts b/packages/server/src/monitoring/utils.ts
index 11ebb6169..2c42b99a6 100644
--- a/packages/server/src/monitoring/utils.ts
+++ b/packages/server/src/monitoring/utils.ts
@@ -1,5 +1,5 @@
import { promises } from "node:fs";
-import osUtils from "node-os-utils";
+import { OSUtils } from "node-os-utils";
import { paths } from "../constants";
export interface Container {
@@ -38,22 +38,122 @@ export const recordAdvancedStats = async (
});
if (appName === "dokploy") {
- const disk = await osUtils.drive.info("/");
+ const osutils = new OSUtils();
+ const diskResult = await osutils.disk.usageByMountPoint("/");
- const diskUsage = disk.usedGb;
- const diskTotal = disk.totalGb;
- const diskUsedPercentage = disk.usedPercentage;
- const diskFree = disk.freeGb;
+ if (diskResult.success && diskResult.data) {
+ const disk = diskResult.data;
+ const diskUsage = disk.used.toGB().toFixed(2);
+ const diskTotal = disk.total.toGB().toFixed(2);
+ const diskUsedPercentage = disk.usagePercentage;
+ const diskFree = disk.available.toGB().toFixed(2);
- await updateStatsFile(appName, "disk", {
- diskTotal: +diskTotal,
- diskUsedPercentage: +diskUsedPercentage,
- diskUsage: +diskUsage,
- diskFree: +diskFree,
- });
+ await updateStatsFile(appName, "disk", {
+ diskTotal: +diskTotal,
+ diskUsedPercentage: +diskUsedPercentage,
+ diskUsage: +diskUsage,
+ diskFree: +diskFree,
+ });
+ }
}
};
+/**
+ * Get host system statistics using node-os-utils
+ * This is used when monitoring "dokploy" to show host stats instead of container stats
+ */
+export const getHostSystemStats = async (): Promise
=> {
+ const osutils = new OSUtils({
+ disk: {
+ includeStats: true, // Enable disk I/O statistics
+ },
+ });
+
+ // Get CPU usage
+ const cpuResult = await osutils.cpu.usage();
+ const cpuUsage = cpuResult.success ? cpuResult.data : 0;
+
+ // Get memory info
+ const memResult = await osutils.memory.info();
+ let memUsedGB = 0;
+ let memTotalGB = 0;
+ let memUsedPercent = 0;
+ if (memResult.success) {
+ memTotalGB = memResult.data.total.toGB();
+ memUsedGB = memResult.data.used.toGB();
+ memUsedPercent = memResult.data.usagePercentage;
+ }
+
+ // Get network stats from network.overview()
+ let netInputBytes = 0;
+ let netOutputBytes = 0;
+ const networkOverview = await osutils.network.overview();
+ if (networkOverview.success) {
+ netInputBytes = networkOverview.data.totalRxBytes.toBytes();
+ netOutputBytes = networkOverview.data.totalTxBytes.toBytes();
+ }
+
+ // Get Block I/O from disk.stats()
+ let blockReadBytes = 0;
+ let blockWriteBytes = 0;
+ const diskStats = await osutils.disk.stats();
+ if (diskStats.success && diskStats.data.length > 0) {
+ // Filter out virtual devices (loop, ram, sr, etc.) - only include real disk devices
+ const excludePatterns = [/^loop/, /^ram/, /^sr\d+$/, /^fd\d+$/];
+ for (const stat of diskStats.data) {
+ // Skip virtual devices
+ if (
+ stat.device &&
+ excludePatterns.some((pattern) => pattern.test(stat.device))
+ ) {
+ continue;
+ }
+ // readBytes and writeBytes are DataSize objects with .toBytes() method
+ blockReadBytes += stat.readBytes.toBytes();
+ blockWriteBytes += stat.writeBytes.toBytes();
+ }
+ }
+
+ // Format values similar to docker stats
+ const formatBytes = (bytes: number): string => {
+ if (bytes >= 1024 * 1024 * 1024) {
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)}GiB`;
+ }
+ if (bytes >= 1024 * 1024) {
+ return `${(bytes / (1024 * 1024)).toFixed(2)}MiB`;
+ }
+ if (bytes >= 1024) {
+ return `${(bytes / 1024).toFixed(2)}KiB`;
+ }
+ return `${bytes}B`;
+ };
+
+ // Format memory usage similar to docker stats format: "used / total"
+ const memUsedFormatted = `${memUsedGB.toFixed(2)}GiB`;
+ const memTotalFormatted = `${memTotalGB.toFixed(2)}GiB`;
+ const memUsageFormatted = `${memUsedFormatted} / ${memTotalFormatted}`;
+
+ // Format network I/O
+ const netInputMb = netInputBytes / (1024 * 1024);
+ const netOutputMb = netOutputBytes / (1024 * 1024);
+ const netIOFormatted = `${netInputMb.toFixed(2)}MB / ${netOutputMb.toFixed(2)}MB`;
+
+ // Format Block I/O
+ const blockIOFormatted = `${formatBytes(blockReadBytes)} / ${formatBytes(blockWriteBytes)}`;
+
+ // Create a stat object compatible with recordAdvancedStats
+ return {
+ CPUPerc: `${cpuUsage.toFixed(2)}%`,
+ MemPerc: `${memUsedPercent.toFixed(2)}%`,
+ MemUsage: memUsageFormatted,
+ BlockIO: blockIOFormatted,
+ NetIO: netIOFormatted,
+ Container: "dokploy",
+ ID: "host-system",
+ Name: "dokploy",
+ };
+};
+
export const getAdvancedStats = async (appName: string) => {
return {
cpu: await readStatsFile(appName, "cpu"),
diff --git a/packages/server/src/services/admin.ts b/packages/server/src/services/admin.ts
index 55ee5caee..0cbb20785 100644
--- a/packages/server/src/services/admin.ts
+++ b/packages/server/src/services/admin.ts
@@ -110,7 +110,8 @@ export const getDokployUrl = async () => {
const admin = await findAdmin();
if (admin.user.host) {
- return `https://${admin.user.host}`;
+ const protocol = admin.user.https ? "https" : "http";
+ return `${protocol}://${admin.user.host}`;
}
return `http://${admin.user.serverIp}:${process.env.PORT}`;
};
diff --git a/packages/server/src/services/application.ts b/packages/server/src/services/application.ts
index 1891f9b6b..6ad90a93b 100644
--- a/packages/server/src/services/application.ts
+++ b/packages/server/src/services/application.ts
@@ -7,37 +7,25 @@ import {
} from "@dokploy/server/db/schema";
import { getAdvancedStats } from "@dokploy/server/monitoring/utils";
import {
- buildApplication,
getBuildCommand,
mechanizeDockerContainer,
} from "@dokploy/server/utils/builders";
import { sendBuildErrorNotifications } from "@dokploy/server/utils/notifications/build-error";
import { sendBuildSuccessNotifications } from "@dokploy/server/utils/notifications/build-success";
-import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync";
import {
- cloneBitbucketRepository,
- getBitbucketCloneCommand,
-} from "@dokploy/server/utils/providers/bitbucket";
-import {
- buildDocker,
- buildRemoteDocker,
-} from "@dokploy/server/utils/providers/docker";
+ ExecError,
+ execAsync,
+ execAsyncRemote,
+} from "@dokploy/server/utils/process/execAsync";
+import { cloneBitbucketRepository } from "@dokploy/server/utils/providers/bitbucket";
+import { buildRemoteDocker } from "@dokploy/server/utils/providers/docker";
import {
cloneGitRepository,
- getCustomGitCloneCommand,
+ getGitCommitInfo,
} from "@dokploy/server/utils/providers/git";
-import {
- cloneGiteaRepository,
- getGiteaCloneCommand,
-} from "@dokploy/server/utils/providers/gitea";
-import {
- cloneGithubRepository,
- getGithubCloneCommand,
-} from "@dokploy/server/utils/providers/github";
-import {
- cloneGitlabRepository,
- getGitlabCloneCommand,
-} from "@dokploy/server/utils/providers/gitlab";
+import { cloneGiteaRepository } from "@dokploy/server/utils/providers/gitea";
+import { cloneGithubRepository } from "@dokploy/server/utils/providers/github";
+import { cloneGitlabRepository } from "@dokploy/server/utils/providers/gitlab";
import { createTraefikConfig } from "@dokploy/server/utils/traefik/application";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
@@ -46,6 +34,7 @@ import { getDokployUrl } from "./admin";
import {
createDeployment,
createDeploymentPreview,
+ updateDeployment,
updateDeploymentStatus,
} from "./deployment";
import { type Domain, getDomainHost } from "./domain";
@@ -192,30 +181,31 @@ export const deployApplication = async ({
});
try {
+ let command = "set -e;";
if (application.sourceType === "github") {
- await cloneGithubRepository({
- ...application,
- logPath: deployment.logPath,
- });
- await buildApplication(application, deployment.logPath);
+ command += await cloneGithubRepository(application);
} else if (application.sourceType === "gitlab") {
- await cloneGitlabRepository(application, deployment.logPath);
- await buildApplication(application, deployment.logPath);
+ command += await cloneGitlabRepository(application);
} else if (application.sourceType === "gitea") {
- await cloneGiteaRepository(application, deployment.logPath);
- await buildApplication(application, deployment.logPath);
+ command += await cloneGiteaRepository(application);
} else if (application.sourceType === "bitbucket") {
- await cloneBitbucketRepository(application, deployment.logPath);
- await buildApplication(application, deployment.logPath);
- } else if (application.sourceType === "docker") {
- await buildDocker(application, deployment.logPath);
+ command += await cloneBitbucketRepository(application);
} else if (application.sourceType === "git") {
- await cloneGitRepository(application, deployment.logPath);
- await buildApplication(application, deployment.logPath);
- } else if (application.sourceType === "drop") {
- await buildApplication(application, deployment.logPath);
+ command += await cloneGitRepository(application);
+ } else if (application.sourceType === "docker") {
+ command += await buildRemoteDocker(application);
}
+ command += getBuildCommand(application);
+
+ const commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`;
+ if (application.serverId) {
+ await execAsyncRemote(application.serverId, commandWithLog);
+ } else {
+ await execAsync(commandWithLog);
+ }
+
+ await mechanizeDockerContainer(application);
await updateDeploymentStatus(deployment.deploymentId, "done");
await updateApplicationStatus(applicationId, "done");
@@ -237,8 +227,24 @@ export const deployApplication = async ({
buildLink,
organizationId: application.environment.project.organizationId,
domains: application.domains,
+ environmentName: application.environment.name,
});
} catch (error) {
+ let command = "";
+
+ // Only log details for non-ExecError errors
+ if (!(error instanceof ExecError)) {
+ const message = error instanceof Error ? error.message : String(error);
+ const encodedMessage = encodeBase64(message);
+ command += `echo "${encodedMessage}" | base64 -d >> "${deployment.logPath}";`;
+ }
+
+ command += `echo "\nError occurred β, check the logs for details." >> ${deployment.logPath};`;
+ if (application.serverId) {
+ await execAsyncRemote(application.serverId, command);
+ } else {
+ await execAsync(command);
+ }
await updateDeploymentStatus(deployment.deploymentId, "error");
await updateApplicationStatus(applicationId, "error");
@@ -253,8 +259,19 @@ export const deployApplication = async ({
});
throw error;
- }
+ } finally {
+ // Only extract commit info for non-docker sources
+ if (application.sourceType !== "docker") {
+ const commitInfo = await getGitCommitInfo(application);
+ if (commitInfo) {
+ await updateDeployment(deployment.deploymentId, {
+ title: commitInfo.message,
+ description: `Commit: ${commitInfo.hash}`,
+ });
+ }
+ }
+ }
return true;
};
@@ -268,50 +285,8 @@ export const rebuildApplication = async ({
descriptionLog: string;
}) => {
const application = await findApplicationById(applicationId);
-
- const deployment = await createDeployment({
- applicationId: applicationId,
- title: titleLog,
- 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.environment.projectId}/environment/${application.environmentId}/services/application/${application.applicationId}?tab=deployments`;
+
const deployment = await createDeployment({
applicationId: applicationId,
title: titleLog,
@@ -319,39 +294,16 @@ export const deployRemoteApplication = async ({
});
try {
+ let command = "set -e;";
+ // Check case for docker only
+ command += getBuildCommand(application);
+ const commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`;
if (application.serverId) {
- let command = "set -e;";
- if (application.sourceType === "github") {
- command += await getGithubCloneCommand({
- ...application,
- serverId: application.serverId,
- logPath: 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 === "gitea") {
- command += await getGiteaCloneCommand(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);
- }
-
- if (application.sourceType !== "docker") {
- command += getBuildCommand(application, deployment.logPath);
- }
- await execAsyncRemote(application.serverId, command);
- await mechanizeDockerContainer(application);
+ await execAsyncRemote(application.serverId, commandWithLog);
+ } else {
+ await execAsync(commandWithLog);
}
-
+ await mechanizeDockerContainer(application);
await updateDeploymentStatus(deployment.deploymentId, "done");
await updateApplicationStatus(applicationId, "done");
@@ -373,32 +325,26 @@ export const deployRemoteApplication = async ({
buildLink,
organizationId: application.environment.project.organizationId,
domains: application.domains,
+ environmentName: application.environment.name,
});
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
+ let command = "";
- const encodedContent = encodeBase64(errorMessage);
-
- await execAsyncRemote(
- application.serverId,
- `
- echo "\n\n===================================EXTRA LOGS============================================" >> ${deployment.logPath};
- echo "Error occurred β, check the logs for details." >> ${deployment.logPath};
- echo "${encodedContent}" | base64 -d >> "${deployment.logPath}";`,
- );
+ // Only log details for non-ExecError errors
+ if (!(error instanceof ExecError)) {
+ const message = error instanceof Error ? error.message : String(error);
+ const encodedMessage = encodeBase64(message);
+ command += `echo "${encodedMessage}" | base64 -d >> "${deployment.logPath}";`;
+ }
+ command += `echo "\nError occurred β, check the logs for details." >> ${deployment.logPath};`;
+ if (application.serverId) {
+ await execAsyncRemote(application.serverId, command);
+ } else {
+ await execAsync(command);
+ }
await updateDeploymentStatus(deployment.deploymentId, "error");
await updateApplicationStatus(applicationId, "error");
-
- await sendBuildErrorNotifications({
- projectName: application.environment.project.name,
- applicationName: application.name,
- applicationType: "application",
- errorMessage: `Please check the logs for details: ${errorMessage}`,
- buildLink,
- organizationId: application.environment.project.organizationId,
- });
-
throw error;
}
@@ -475,14 +421,22 @@ export const deployPreviewApplication = async ({
application.buildArgs = `${application.previewBuildArgs}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain?.host}`;
application.buildSecrets = `${application.previewBuildSecrets}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain?.host}`;
+ let command = "set -e;";
if (application.sourceType === "github") {
- await cloneGithubRepository({
+ command += await cloneGithubRepository({
...application,
appName: previewDeployment.appName,
branch: previewDeployment.branch,
- logPath: deployment.logPath,
});
- await buildApplication(application, deployment.logPath);
+ command += getBuildCommand(application);
+
+ const commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`;
+ if (application.serverId) {
+ await execAsyncRemote(application.serverId, commandWithLog);
+ } else {
+ await execAsync(commandWithLog);
+ }
+ await mechanizeDockerContainer(application);
}
const successComment = getIssueComment(
application.name,
@@ -513,170 +467,10 @@ export const deployPreviewApplication = async ({
return true;
};
-export const deployRemotePreviewApplication = async ({
- applicationId,
- titleLog = "Preview Deployment",
- descriptionLog = "",
- previewDeploymentId,
-}: {
- applicationId: string;
- titleLog: string;
- descriptionLog: string;
- previewDeploymentId: string;
-}) => {
- const application = await findApplicationById(applicationId);
-
- const deployment = await createDeploymentPreview({
- title: titleLog,
- description: descriptionLog,
- previewDeploymentId: previewDeploymentId,
- });
-
- const previewDeployment =
- await findPreviewDeploymentById(previewDeploymentId);
-
- await updatePreviewDeployment(previewDeploymentId, {
- createdAt: new Date().toISOString(),
- });
-
- const previewDomain = getDomainHost(previewDeployment?.domain as Domain);
- const issueParams = {
- owner: application?.owner || "",
- repository: application?.repository || "",
- issue_number: previewDeployment.pullRequestNumber,
- comment_id: Number.parseInt(previewDeployment.pullRequestCommentId),
- githubId: application?.githubId || "",
- };
- try {
- const commentExists = await issueCommentExists({
- ...issueParams,
- });
- if (!commentExists) {
- const result = await createPreviewDeploymentComment({
- ...issueParams,
- previewDomain,
- appName: previewDeployment.appName,
- githubId: application?.githubId || "",
- previewDeploymentId,
- });
-
- if (!result) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Pull request comment not found",
- });
- }
-
- issueParams.comment_id = Number.parseInt(result?.pullRequestCommentId);
- }
- const buildingComment = getIssueComment(
- application.name,
- "running",
- previewDomain,
- );
- await updateIssueComment({
- ...issueParams,
- body: `### Dokploy Preview Deployment\n\n${buildingComment}`,
- });
- application.appName = previewDeployment.appName;
- application.env = `${application.previewEnv}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain?.host}`;
- application.buildArgs = `${application.previewBuildArgs}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain?.host}`;
- application.buildSecrets = `${application.previewBuildSecrets}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain?.host}`;
-
- if (application.serverId) {
- let command = "set -e;";
- if (application.sourceType === "github") {
- command += await getGithubCloneCommand({
- ...application,
- appName: previewDeployment.appName,
- branch: previewDeployment.branch,
- serverId: application.serverId,
- logPath: deployment.logPath,
- });
- }
-
- command += getBuildCommand(application, deployment.logPath);
- await execAsyncRemote(application.serverId, command);
- await mechanizeDockerContainer(application);
- }
-
- const successComment = getIssueComment(
- application.name,
- "success",
- previewDomain,
- );
- await updateIssueComment({
- ...issueParams,
- body: `### Dokploy Preview Deployment\n\n${successComment}`,
- });
- await updateDeploymentStatus(deployment.deploymentId, "done");
- await updatePreviewDeployment(previewDeploymentId, {
- previewStatus: "done",
- });
- } catch (error) {
- const comment = getIssueComment(application.name, "error", previewDomain);
- await updateIssueComment({
- ...issueParams,
- body: `### Dokploy Preview Deployment\n\n${comment}`,
- });
- await updateDeploymentStatus(deployment.deploymentId, "error");
- await updatePreviewDeployment(previewDeploymentId, {
- previewStatus: "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");
- } catch (error) {
- // @ts-ignore
- const encodedContent = encodeBase64(error?.message);
-
- await execAsyncRemote(
- application.serverId,
- `
- echo "\n\n===================================EXTRA LOGS============================================" >> ${deployment.logPath};
- echo "Error occurred β, check the logs for details." >> ${deployment.logPath};
- echo "${encodedContent}" | base64 -d >> "${deployment.logPath}";`,
- );
- await updateDeploymentStatus(deployment.deploymentId, "error");
- await updateApplicationStatus(applicationId, "error");
- throw error;
- }
-
- return true;
-};
-
export const getApplicationStats = async (appName: string) => {
+ if (appName === "dokploy") {
+ return await getAdvancedStats(appName);
+ }
const filter = {
status: ["running"],
label: [`com.docker.swarm.service.name=${appName}`],
diff --git a/packages/server/src/services/compose.ts b/packages/server/src/services/compose.ts
index 1436c52cc..89a12a156 100644
--- a/packages/server/src/services/compose.ts
+++ b/packages/server/src/services/compose.ts
@@ -7,14 +7,10 @@ import {
cleanAppName,
compose,
} from "@dokploy/server/db/schema";
-import {
- buildCompose,
- getBuildComposeCommand,
-} from "@dokploy/server/utils/builders/compose";
+import { getBuildComposeCommand } from "@dokploy/server/utils/builders/compose";
import { randomizeSpecificationFile } from "@dokploy/server/utils/docker/compose";
import {
cloneCompose,
- cloneComposeRemote,
loadDockerCompose,
loadDockerComposeRemote,
} from "@dokploy/server/utils/docker/domain";
@@ -22,38 +18,28 @@ import type { ComposeSpecification } from "@dokploy/server/utils/docker/types";
import { sendBuildErrorNotifications } from "@dokploy/server/utils/notifications/build-error";
import { sendBuildSuccessNotifications } from "@dokploy/server/utils/notifications/build-success";
import {
+ ExecError,
execAsync,
execAsyncRemote,
} from "@dokploy/server/utils/process/execAsync";
-import {
- cloneBitbucketRepository,
- getBitbucketCloneCommand,
-} from "@dokploy/server/utils/providers/bitbucket";
+import { cloneBitbucketRepository } from "@dokploy/server/utils/providers/bitbucket";
import {
cloneGitRepository,
- getCustomGitCloneCommand,
+ getGitCommitInfo,
} from "@dokploy/server/utils/providers/git";
-import {
- cloneGiteaRepository,
- getGiteaCloneCommand,
-} from "@dokploy/server/utils/providers/gitea";
-import {
- cloneGithubRepository,
- getGithubCloneCommand,
-} from "@dokploy/server/utils/providers/github";
-import {
- cloneGitlabRepository,
- getGitlabCloneCommand,
-} from "@dokploy/server/utils/providers/gitlab";
-import {
- createComposeFile,
- getCreateComposeFileCommand,
-} from "@dokploy/server/utils/providers/raw";
+import { cloneGiteaRepository } from "@dokploy/server/utils/providers/gitea";
+import { cloneGithubRepository } from "@dokploy/server/utils/providers/github";
+import { cloneGitlabRepository } from "@dokploy/server/utils/providers/gitlab";
+import { getCreateComposeFileCommand } from "@dokploy/server/utils/providers/raw";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { encodeBase64 } from "../utils/docker/utils";
import { getDokployUrl } from "./admin";
-import { createDeploymentCompose, updateDeploymentStatus } from "./deployment";
+import {
+ createDeploymentCompose,
+ updateDeployment,
+ updateDeploymentStatus,
+} from "./deployment";
import { validUniqueServerAppName } from "./project";
export type Compose = typeof compose.$inferSelect;
@@ -163,10 +149,11 @@ export const loadServices = async (
const compose = await findComposeById(composeId);
if (type === "fetch") {
+ const command = await cloneCompose(compose);
if (compose.serverId) {
- await cloneComposeRemote(compose);
+ await execAsyncRemote(compose.serverId, command);
} else {
- await cloneCompose(compose);
+ await execAsync(command);
}
}
@@ -235,24 +222,41 @@ export const deployCompose = async ({
});
try {
+ const entity = {
+ ...compose,
+ type: "compose" as const,
+ };
+ let command = "set -e;";
if (compose.sourceType === "github") {
- await cloneGithubRepository({
- ...compose,
- logPath: deployment.logPath,
- type: "compose",
- });
+ command += await cloneGithubRepository(entity);
} else if (compose.sourceType === "gitlab") {
- await cloneGitlabRepository(compose, deployment.logPath, true);
+ command += await cloneGitlabRepository(entity);
} else if (compose.sourceType === "bitbucket") {
- await cloneBitbucketRepository(compose, deployment.logPath, true);
+ command += await cloneBitbucketRepository(entity);
} else if (compose.sourceType === "git") {
- await cloneGitRepository(compose, deployment.logPath, true);
+ command += await cloneGitRepository(entity);
} else if (compose.sourceType === "gitea") {
- await cloneGiteaRepository(compose, deployment.logPath, true);
+ command += await cloneGiteaRepository(entity);
} else if (compose.sourceType === "raw") {
- await createComposeFile(compose, deployment.logPath);
+ command += getCreateComposeFileCommand(entity);
}
- await buildCompose(compose, deployment.logPath);
+
+ let commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`;
+ if (compose.serverId) {
+ await execAsyncRemote(compose.serverId, commandWithLog);
+ } else {
+ await execAsync(commandWithLog);
+ }
+
+ command = "set -e;";
+ command += await getBuildComposeCommand(entity);
+ commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`;
+ if (compose.serverId) {
+ await execAsyncRemote(compose.serverId, commandWithLog);
+ } else {
+ await execAsync(commandWithLog);
+ }
+
await updateDeploymentStatus(deployment.deploymentId, "done");
await updateCompose(composeId, {
composeStatus: "done",
@@ -265,8 +269,24 @@ export const deployCompose = async ({
buildLink,
organizationId: compose.environment.project.organizationId,
domains: compose.domains,
+ environmentName: compose.environment.name,
});
} catch (error) {
+ let command = "";
+
+ // Only log details for non-ExecError errors
+ if (!(error instanceof ExecError)) {
+ const message = error instanceof Error ? error.message : String(error);
+ const encodedMessage = encodeBase64(message);
+ command += `echo "${encodedMessage}" | base64 -d >> "${deployment.logPath}";`;
+ }
+
+ command += `echo "\nError occurred β, check the logs for details." >> ${deployment.logPath};`;
+ if (compose.serverId) {
+ await execAsyncRemote(compose.serverId, command);
+ } else {
+ await execAsync(command);
+ }
await updateDeploymentStatus(deployment.deploymentId, "error");
await updateCompose(composeId, {
composeStatus: "error",
@@ -281,6 +301,19 @@ export const deployCompose = async ({
organizationId: compose.environment.project.organizationId,
});
throw error;
+ } finally {
+ if (compose.sourceType !== "raw") {
+ const commitInfo = await getGitCommitInfo({
+ ...compose,
+ type: "compose",
+ });
+ if (commitInfo) {
+ await updateDeployment(deployment.deploymentId, {
+ title: commitInfo.message,
+ description: `Commit: ${commitInfo.hash}`,
+ });
+ }
+ }
}
};
@@ -302,154 +335,23 @@ export const rebuildCompose = async ({
});
try {
+ let command = "set -e;";
if (compose.sourceType === "raw") {
- await createComposeFile(compose, deployment.logPath);
+ command += getCreateComposeFileCommand(compose);
}
- await buildCompose(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 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.environment.projectId
- }/environment/${compose.environmentId}/services/compose/${compose.composeId}?tab=deployments`;
- const deployment = await createDeploymentCompose({
- composeId: composeId,
- title: titleLog,
- description: descriptionLog,
- });
- try {
+ let commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`;
if (compose.serverId) {
- let command = "set -e;";
-
- if (compose.sourceType === "github") {
- command += await getGithubCloneCommand({
- ...compose,
- logPath: deployment.logPath,
- type: "compose",
- serverId: compose.serverId,
- });
- } 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,
- );
- console.log(command);
- } else if (compose.sourceType === "raw") {
- command += getCreateComposeFileCommand(compose, deployment.logPath);
- } else if (compose.sourceType === "gitea") {
- command += await getGiteaCloneCommand(
- compose,
- deployment.logPath,
- true,
- );
- }
-
- await execAsyncRemote(compose.serverId, command);
- await getBuildComposeCommand(compose, deployment.logPath);
- }
-
- await updateDeploymentStatus(deployment.deploymentId, "done");
- await updateCompose(composeId, {
- composeStatus: "done",
- });
-
- await sendBuildSuccessNotifications({
- projectName: compose.environment.project.name,
- applicationName: compose.name,
- applicationType: "compose",
- buildLink,
- organizationId: compose.environment.project.organizationId,
- domains: compose.domains,
- });
- } catch (error) {
- // @ts-ignore
- const encodedContent = encodeBase64(error?.message);
-
- await execAsyncRemote(
- compose.serverId,
- `
- echo "\n\n===================================EXTRA LOGS============================================" >> ${deployment.logPath};
- echo "Error occurred β, check the logs for details." >> ${deployment.logPath};
- echo "${encodedContent}" | base64 -d >> "${deployment.logPath}";`,
- );
- await updateDeploymentStatus(deployment.deploymentId, "error");
- await updateCompose(composeId, {
- composeStatus: "error",
- });
- await sendBuildErrorNotifications({
- projectName: compose.environment.project.name,
- applicationName: compose.name,
- applicationType: "compose",
- // @ts-ignore
- errorMessage: error?.message || "Error building",
- buildLink,
- organizationId: compose.environment.project.organizationId,
- });
- 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.sourceType === "raw") {
- const command = getCreateComposeFileCommand(compose, deployment.logPath);
- await execAsyncRemote(compose.serverId, command);
+ await execAsyncRemote(compose.serverId, commandWithLog);
+ } else {
+ await execAsync(commandWithLog);
}
+ command += await getBuildComposeCommand(compose);
+ commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`;
if (compose.serverId) {
- await getBuildComposeCommand(compose, deployment.logPath);
+ await execAsyncRemote(compose.serverId, commandWithLog);
+ } else {
+ await execAsync(commandWithLog);
}
await updateDeploymentStatus(deployment.deploymentId, "done");
@@ -457,16 +359,21 @@ export const rebuildRemoteCompose = async ({
composeStatus: "done",
});
} catch (error) {
- // @ts-ignore
- const encodedContent = encodeBase64(error?.message);
+ let command = "";
- await execAsyncRemote(
- compose.serverId,
- `
- echo "\n\n===================================EXTRA LOGS============================================" >> ${deployment.logPath};
- echo "Error occurred β, check the logs for details." >> ${deployment.logPath};
- echo "${encodedContent}" | base64 -d >> "${deployment.logPath}";`,
- );
+ // Only log details for non-ExecError errors
+ if (!(error instanceof ExecError)) {
+ const message = error instanceof Error ? error.message : String(error);
+ const encodedMessage = encodeBase64(message);
+ command += `echo "${encodedMessage}" | base64 -d >> "${deployment.logPath}";`;
+ }
+
+ command += `echo "\nError occurred β, check the logs for details." >> ${deployment.logPath};`;
+ if (compose.serverId) {
+ await execAsyncRemote(compose.serverId, command);
+ } else {
+ await execAsync(command);
+ }
await updateDeploymentStatus(deployment.deploymentId, "error");
await updateCompose(composeId, {
composeStatus: "error",
@@ -501,7 +408,7 @@ export const removeCompose = async (
} else {
const command = `
docker network disconnect ${compose.appName} dokploy-traefik;
- cd ${projectPath} && docker compose -p ${compose.appName} down ${
+ cd ${projectPath} && env -i PATH="$PATH" docker compose -p ${compose.appName} down ${
deleteVolumes ? "--volumes" : ""
} && rm -rf ${projectPath}`;
@@ -528,7 +435,7 @@ export const startCompose = async (composeId: string) => {
const projectPath = join(COMPOSE_PATH, compose.appName, "code");
const path =
compose.sourceType === "raw" ? "docker-compose.yml" : compose.composePath;
- const baseCommand = `docker compose -p ${compose.appName} -f ${path} up -d`;
+ const baseCommand = `env -i PATH="$PATH" docker compose -p ${compose.appName} -f ${path} up -d`;
if (compose.composeType === "docker-compose") {
if (compose.serverId) {
await execAsyncRemote(
@@ -563,14 +470,17 @@ export const stopCompose = async (composeId: string) => {
if (compose.serverId) {
await execAsyncRemote(
compose.serverId,
- `cd ${join(COMPOSE_PATH, compose.appName)} && docker compose -p ${
+ `cd ${join(COMPOSE_PATH, compose.appName)} && env -i PATH="$PATH" docker compose -p ${
compose.appName
} stop`,
);
} else {
- await execAsync(`docker compose -p ${compose.appName} stop`, {
- cwd: join(COMPOSE_PATH, compose.appName),
- });
+ await execAsync(
+ `env -i PATH="$PATH" docker compose -p ${compose.appName} stop`,
+ {
+ cwd: join(COMPOSE_PATH, compose.appName),
+ },
+ );
}
}
diff --git a/packages/server/src/services/deployment.ts b/packages/server/src/services/deployment.ts
index ed03b32fc..41274bac9 100644
--- a/packages/server/src/services/deployment.ts
+++ b/packages/server/src/services/deployment.ts
@@ -74,20 +74,21 @@ export const createDeployment = async (
>,
) => {
const application = await findApplicationById(deployment.applicationId);
-
try {
await removeLastTenDeployments(
deployment.applicationId,
"application",
application.serverId,
);
- const { LOGS_PATH } = paths(!!application.serverId);
+ const serverId = application.serverId;
+
+ const { LOGS_PATH } = paths(!!serverId);
const formattedDateTime = format(new Date(), "yyyy-MM-dd:HH:mm:ss");
const fileName = `${application.appName}-${formattedDateTime}.log`;
const logFilePath = path.join(LOGS_PATH, application.appName, fileName);
- if (application.serverId) {
- const server = await findServerById(application.serverId);
+ if (serverId) {
+ const server = await findServerById(serverId);
const command = `
mkdir -p ${LOGS_PATH}/${application.appName};
@@ -99,7 +100,7 @@ export const createDeployment = async (
await fsPromises.mkdir(path.join(LOGS_PATH, application.appName), {
recursive: true,
});
- await fsPromises.writeFile(logFilePath, "Initializing deployment");
+ await fsPromises.writeFile(logFilePath, "Initializing deployment\n");
}
const deploymentCreate = await db
@@ -249,7 +250,7 @@ export const createDeploymentCompose = async (
const command = `
mkdir -p ${LOGS_PATH}/${compose.appName};
-echo "Initializing deployment" >> ${logFilePath};
+echo "Initializing deployment\n" >> ${logFilePath};
`;
await execAsyncRemote(server.serverId, command);
@@ -257,7 +258,7 @@ echo "Initializing deployment" >> ${logFilePath};
await fsPromises.mkdir(path.join(LOGS_PATH, compose.appName), {
recursive: true,
});
- await fsPromises.writeFile(logFilePath, "Initializing deployment");
+ await fsPromises.writeFile(logFilePath, "Initializing deployment\n");
}
const deploymentCreate = await db
diff --git a/packages/server/src/services/environment.ts b/packages/server/src/services/environment.ts
index 1d77510be..fb1952818 100644
--- a/packages/server/src/services/environment.ts
+++ b/packages/server/src/services/environment.ts
@@ -34,13 +34,43 @@ export const findEnvironmentById = async (environmentId: string) => {
const environment = await db.query.environments.findFirst({
where: eq(environments.environmentId, environmentId),
with: {
- applications: true,
- mariadb: true,
- mongo: true,
- mysql: true,
- postgres: true,
- redis: true,
- compose: true,
+ applications: {
+ with: {
+ deployments: true,
+ server: true,
+ },
+ },
+ mariadb: {
+ with: {
+ server: true,
+ },
+ },
+ mongo: {
+ with: {
+ server: true,
+ },
+ },
+ mysql: {
+ with: {
+ server: true,
+ },
+ },
+ postgres: {
+ with: {
+ server: true,
+ },
+ },
+ redis: {
+ with: {
+ server: true,
+ },
+ },
+ compose: {
+ with: {
+ deployments: true,
+ server: true,
+ },
+ },
project: true,
},
});
diff --git a/packages/server/src/services/postgres.ts b/packages/server/src/services/postgres.ts
index 0d900443e..d7016038a 100644
--- a/packages/server/src/services/postgres.ts
+++ b/packages/server/src/services/postgres.ts
@@ -13,6 +13,18 @@ import { TRPCError } from "@trpc/server";
import { eq, getTableColumns } from "drizzle-orm";
import { validUniqueServerAppName } from "./project";
+export function getMountPath(dockerImage: string): string {
+ const versionMatch = dockerImage.match(/postgres:(\d+)/);
+
+ if (versionMatch?.[1]) {
+ const version = Number.parseInt(versionMatch[1], 10);
+ if (version >= 18) {
+ return `/var/lib/postgresql/${version}/data`;
+ }
+ }
+ return "/var/lib/postgresql/data";
+}
+
export type Postgres = typeof postgres.$inferSelect;
export const createPostgres = async (input: typeof apiCreatePostgres._type) => {
diff --git a/packages/server/src/services/settings.ts b/packages/server/src/services/settings.ts
index 301573cb4..23d11b09b 100644
--- a/packages/server/src/services/settings.ts
+++ b/packages/server/src/services/settings.ts
@@ -59,10 +59,8 @@ export const getUpdateData = async (): Promise => {
let currentDigest: string;
try {
currentDigest = await getServiceImageDigest();
- } catch {
- // Docker service might not exist locally
- // You can run the # Installation command for docker service create mentioned in the below docs to test it locally:
- // https://docs.dokploy.com/docs/core/manual-installation
+ } catch (error) {
+ // TODO: Docker versions 29.0.0 change the way to get the service image digest, so we need to update this in the future we upgrade to that version.
return DEFAULT_UPDATE_DATA;
}
diff --git a/packages/server/src/setup/postgres-setup.ts b/packages/server/src/setup/postgres-setup.ts
index cf162f1ed..377a84952 100644
--- a/packages/server/src/setup/postgres-setup.ts
+++ b/packages/server/src/setup/postgres-setup.ts
@@ -17,7 +17,7 @@ export const initializePostgres = async () => {
Mounts: [
{
Type: "volume",
- Source: "dokploy-postgres-database",
+ Source: "dokploy-postgres",
Target: "/var/lib/postgresql/data",
},
],
diff --git a/packages/server/src/setup/redis-setup.ts b/packages/server/src/setup/redis-setup.ts
index 7366546da..894b3427d 100644
--- a/packages/server/src/setup/redis-setup.ts
+++ b/packages/server/src/setup/redis-setup.ts
@@ -14,7 +14,7 @@ export const initializeRedis = async () => {
Mounts: [
{
Type: "volume",
- Source: "redis-data-volume",
+ Source: "dokploy-redis",
Target: "/data",
},
],
diff --git a/packages/server/src/setup/traefik-setup.ts b/packages/server/src/setup/traefik-setup.ts
index fa9bf78d0..73cff0b1c 100644
--- a/packages/server/src/setup/traefik-setup.ts
+++ b/packages/server/src/setup/traefik-setup.ts
@@ -20,7 +20,7 @@ export const TRAEFIK_PORT =
Number.parseInt(process.env.TRAEFIK_PORT!, 10) || 80;
export const TRAEFIK_HTTP3_PORT =
Number.parseInt(process.env.TRAEFIK_HTTP3_PORT!, 10) || 443;
-export const TRAEFIK_VERSION = process.env.TRAEFIK_VERSION || "3.5.0";
+export const TRAEFIK_VERSION = process.env.TRAEFIK_VERSION || "3.6.1";
export interface TraefikOptions {
env?: string[];
diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts
index 667b46b74..fe5417ea5 100644
--- a/packages/server/src/utils/builders/compose.ts
+++ b/packages/server/src/utils/builders/compose.ts
@@ -1,117 +1,29 @@
-import {
- createWriteStream,
- existsSync,
- mkdirSync,
- 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,
-} from "../docker/domain";
+import { quote } from "shell-quote";
+import { writeDomainsToCompose } from "../docker/domain";
import {
encodeBase64,
getEnviromentVariablesObject,
prepareEnvironmentVariables,
} from "../docker/utils";
-import { execAsync, execAsyncRemote } from "../process/execAsync";
-import { spawnAsync } from "../process/spawnAsync";
export type ComposeNested = InferResultType<
"compose",
{ environment: { with: { 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);
- if (compose.isolatedDeployment) {
- await execAsync(
- `docker network inspect ${compose.appName} >/dev/null 2>&1 || docker network create ${composeType === "stack" ? "--driver overlay" : ""} --attachable ${compose.appName}`,
- );
- }
-
- 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");
-
- 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.environment.project.env,
- ),
- }),
- },
- },
- );
-
- if (compose.isolatedDeployment) {
- await execAsync(
- `docker network connect ${compose.appName} $(docker ps --filter "name=dokploy-traefik" -q) >/dev/null 2>&1`,
- ).catch(() => {});
- }
-
- 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,
-) => {
- const { COMPOSE_PATH } = paths(true);
+export const getBuildComposeCommand = async (compose: ComposeNested) => {
+ const { COMPOSE_PATH } = paths(!!compose.serverId);
const { sourceType, appName, mounts, composeType, domains } = 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 newCompose = await writeDomainsToCompose(compose, domains);
const logContent = `
App Name: ${appName}
Build Compose π³
@@ -133,7 +45,7 @@ Compose Type: ${composeType} β
`;
const bashCommand = `
set -e
{
- echo "${logBox}" >> "${logPath}"
+ echo "${logBox}";
${newCompose}
@@ -141,19 +53,18 @@ Compose Type: ${composeType} β
`;
cd "${projectPath}";
- ${exportEnvCommand}
${compose.isolatedDeployment ? `docker network inspect ${compose.appName} >/dev/null 2>&1 || docker network create --attachable ${compose.appName}` : ""}
- docker ${command.split(" ").join(" ")} >> "${logPath}" 2>&1 || { echo "Error: β Docker command failed" >> "${logPath}"; exit 1; }
+ env -i PATH="$PATH" ${exportEnvCommand} docker ${command.split(" ").join(" ")} 2>&1 || { echo "Error: β Docker command failed"; exit 1; }
${compose.isolatedDeployment ? `docker network connect ${compose.appName} $(docker ps --filter "name=dokploy-traefik" -q) >/dev/null 2>&1` : ""}
- echo "Docker Compose Deployed: β
" >> "${logPath}"
+ echo "Docker Compose Deployed: β
";
} || {
- echo "Error: β Script execution failed" >> "${logPath}"
+ echo "Error: β Script execution failed";
exit 1
}
`;
- return await execAsyncRemote(compose.serverId, bashCommand);
+ return bashCommand;
};
const sanitizeCommand = (command: string) => {
@@ -185,38 +96,8 @@ export const createCommand = (compose: ComposeNested) => {
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 envFilePath = join(dirname(composeFilePath), ".env");
- let envContent = `APP_NAME=${appName}\n`;
- envContent += env || "";
- if (!envContent.includes("DOCKER_CONFIG")) {
- envContent += "\nDOCKER_CONFIG=/root/.docker";
- }
-
- if (compose.randomize) {
- envContent += `\nCOMPOSE_PREFIX=${compose.suffix}`;
- }
-
- const envFileContent = prepareEnvironmentVariables(
- envContent,
- compose.environment.project.env,
- compose.environment.env,
- ).join("\n");
-
- if (!existsSync(dirname(envFilePath))) {
- mkdirSync(dirname(envFilePath), { recursive: true });
- }
- writeFileSync(envFilePath, envFileContent);
-};
-
export const getCreateEnvFileCommand = (compose: ComposeNested) => {
- const { COMPOSE_PATH } = paths(true);
+ const { COMPOSE_PATH } = paths(!!compose.serverId);
const { env, composePath, appName } = compose;
const composeFilePath =
join(COMPOSE_PATH, appName, "code", composePath) ||
@@ -255,8 +136,8 @@ const getExportEnvCommand = (compose: ComposeNested) => {
compose.environment.project.env,
);
const exports = Object.entries(envVars)
- .map(([key, value]) => `export ${key}=${JSON.stringify(value)}`)
- .join("\n");
+ .map(([key, value]) => `${key}=${quote([value])}`)
+ .join(" ");
- return exports ? `\n# Export environment variables\n${exports}\n` : "";
+ return exports ? `${exports}` : "";
};
diff --git a/packages/server/src/utils/builders/docker-file.ts b/packages/server/src/utils/builders/docker-file.ts
index b5c2b59c3..8ca99ccf2 100644
--- a/packages/server/src/utils/builders/docker-file.ts
+++ b/packages/server/src/utils/builders/docker-file.ts
@@ -1,109 +1,16 @@
-import type { WriteStream } from "node:fs";
import {
getEnviromentVariablesObject,
- prepareEnvironmentVariables,
+ prepareEnvironmentVariablesForShell,
} from "@dokploy/server/utils/docker/utils";
+import { quote } from "shell-quote";
import {
getBuildAppDirectory,
getDockerContextPath,
} from "../filesystem/directory";
-import { spawnAsync } from "../process/spawnAsync";
import type { ApplicationNested } from ".";
-import { createEnvFile, createEnvFileCommand } from "./utils";
+import { createEnvFileCommand } from "./utils";
-export const buildCustomDocker = async (
- application: ApplicationNested,
- writeStream: WriteStream,
-) => {
- const {
- appName,
- env,
- publishDirectory,
- buildArgs,
- buildSecrets,
- dockerBuildStage,
- cleanCache,
- } = application;
- const dockerFilePath = getBuildAppDirectory(application);
- try {
- const image = `${appName}`;
-
- const defaultContextPath =
- dockerFilePath.substring(0, dockerFilePath.lastIndexOf("/") + 1) || ".";
-
- const dockerContextPath = getDockerContextPath(application);
-
- const commandArgs = ["build", "-t", image, "-f", dockerFilePath, "."];
-
- if (cleanCache) {
- commandArgs.push("--no-cache");
- }
-
- if (dockerBuildStage) {
- commandArgs.push("--target", dockerBuildStage);
- }
-
- const args = prepareEnvironmentVariables(
- buildArgs,
- application.environment.project.env,
- application.environment.env,
- );
-
- for (const arg of args) {
- commandArgs.push("--build-arg", arg);
- }
-
- const secrets = getEnviromentVariablesObject(
- buildSecrets,
- application.environment.project.env,
- application.environment.env,
- );
-
- for (const key in secrets) {
- // Although buildx is smart enough to know we may be referring to an environment variable name,
- // we still make sure it doesn't fall back to type=file.
- // See: https://docs.docker.com/reference/cli/docker/buildx/build/#secret
- commandArgs.push("--secret", `type=env,id=${key}`);
- }
-
- /*
- Do not generate an environment file when publishDirectory is specified,
- as it could be publicly exposed.
- */
- if (!publishDirectory) {
- createEnvFile(
- dockerFilePath,
- env,
- application.environment.project.env,
- application.environment.env,
- );
- }
-
- await spawnAsync(
- "docker",
- commandArgs,
- (data) => {
- if (writeStream.writable) {
- writeStream.write(data);
- }
- },
- {
- cwd: dockerContextPath || defaultContextPath,
- env: {
- ...process.env,
- ...secrets,
- },
- },
- );
- } catch (error) {
- throw error;
- }
-};
-
-export const getDockerCommand = (
- application: ApplicationNested,
- logPath: string,
-) => {
+export const getDockerCommand = (application: ApplicationNested) => {
const {
appName,
env,
@@ -134,14 +41,14 @@ export const getDockerCommand = (
commandArgs.push("--no-cache");
}
- const args = prepareEnvironmentVariables(
+ const args = prepareEnvironmentVariablesForShell(
buildArgs,
application.environment.project.env,
application.environment.env,
);
for (const arg of args) {
- commandArgs.push("--build-arg", `'${arg}'`);
+ commandArgs.push("--build-arg", arg);
}
const secrets = getEnviromentVariablesObject(
@@ -151,7 +58,7 @@ export const getDockerCommand = (
);
const joinedSecrets = Object.entries(secrets)
- .map(([key, value]) => `${key}='${value.replace(/'/g, "'\"'\"'")}'`)
+ .map(([key, value]) => `${key}=${quote([value])}`)
.join(" ");
for (const key in secrets) {
@@ -176,17 +83,17 @@ export const getDockerCommand = (
}
command += `
-echo "Building ${appName}" >> ${logPath};
-cd ${dockerContextPath} >> ${logPath} 2>> ${logPath} || {
- echo "β The path ${dockerContextPath} does not exist" >> ${logPath};
+echo "Building ${appName}" ;
+cd ${dockerContextPath} || {
+ echo "β The path ${dockerContextPath} does not exist" ;
exit 1;
}
-${joinedSecrets} docker ${commandArgs.join(" ")} >> ${logPath} 2>> ${logPath} || {
- echo "β Docker build failed" >> ${logPath};
+${joinedSecrets} docker ${commandArgs.join(" ")} || {
+ echo "β Docker build failed" ;
exit 1;
}
-echo "β
Docker build completed." >> ${logPath};
+echo "β
Docker build completed." ;
`;
return command;
diff --git a/packages/server/src/utils/builders/heroku.ts b/packages/server/src/utils/builders/heroku.ts
index 3306f2fc2..8b38c694d 100644
--- a/packages/server/src/utils/builders/heroku.ts
+++ b/packages/server/src/utils/builders/heroku.ts
@@ -1,58 +1,12 @@
-import type { WriteStream } from "node:fs";
-import { prepareEnvironmentVariables } from "../docker/utils";
+import { prepareEnvironmentVariablesForShell } from "../docker/utils";
import { getBuildAppDirectory } from "../filesystem/directory";
-import { spawnAsync } from "../process/spawnAsync";
import type { ApplicationNested } from ".";
-// TODO: integrate in the vps sudo chown -R $(whoami) ~/.docker
-export const buildHeroku = async (
- application: ApplicationNested,
- writeStream: WriteStream,
-) => {
- const { env, appName, cleanCache } = application;
- const buildAppDirectory = getBuildAppDirectory(application);
- const envVariables = prepareEnvironmentVariables(
- env,
- application.environment.project.env,
- application.environment.env,
- );
- try {
- const args = [
- "build",
- appName,
- "--path",
- buildAppDirectory,
- "--builder",
- `heroku/builder:${application.herokuVersion || "24"}`,
- ];
-
- for (const env of envVariables) {
- args.push("--env", env);
- }
-
- if (cleanCache) {
- args.push("--clear-cache");
- }
-
- await spawnAsync("pack", args, (data) => {
- if (writeStream.writable) {
- writeStream.write(data);
- }
- });
- return true;
- } catch (e) {
- throw e;
- }
-};
-
-export const getHerokuCommand = (
- application: ApplicationNested,
- logPath: string,
-) => {
+export const getHerokuCommand = (application: ApplicationNested) => {
const { env, appName, cleanCache } = application;
const buildAppDirectory = getBuildAppDirectory(application);
- const envVariables = prepareEnvironmentVariables(
+ const envVariables = prepareEnvironmentVariablesForShell(
env,
application.environment.project.env,
application.environment.env,
@@ -72,17 +26,17 @@ export const getHerokuCommand = (
}
for (const env of envVariables) {
- args.push("--env", `'${env}'`);
+ args.push("--env", env);
}
const command = `pack ${args.join(" ")}`;
const bashCommand = `
-echo "Starting heroku build..." >> ${logPath};
-${command} >> ${logPath} 2>> ${logPath} || {
- echo "β Heroku build failed" >> ${logPath};
+echo "Starting heroku build..." ;
+${command} || {
+ echo "β Heroku build failed" ;
exit 1;
}
-echo "β
Heroku build completed." >> ${logPath};
+echo "β
Heroku build completed." ;
`;
return bashCommand;
diff --git a/packages/server/src/utils/builders/index.ts b/packages/server/src/utils/builders/index.ts
index 5ae0704c5..bfc3e894a 100644
--- a/packages/server/src/utils/builders/index.ts
+++ b/packages/server/src/utils/builders/index.ts
@@ -1,7 +1,6 @@
-import { createWriteStream } from "node:fs";
import type { InferResultType } from "@dokploy/server/types/with";
import type { CreateServiceOptions } from "dockerode";
-import { uploadImage, uploadImageRemoteCommand } from "../cluster/upload";
+import { uploadImageRemoteCommand } from "../cluster/upload";
import {
calculateResources,
generateBindMounts,
@@ -11,12 +10,12 @@ import {
prepareEnvironmentVariables,
} from "../docker/utils";
import { getRemoteDocker } from "../servers/remote-docker";
-import { buildCustomDocker, getDockerCommand } from "./docker-file";
-import { buildHeroku, getHerokuCommand } from "./heroku";
-import { buildNixpacks, getNixpacksCommand } from "./nixpacks";
-import { buildPaketo, getPaketoCommand } from "./paketo";
-import { buildRailpack, getRailpackCommand } from "./railpack";
-import { buildStatic, getStaticCommand } from "./static";
+import { getDockerCommand } from "./docker-file";
+import { getHerokuCommand } from "./heroku";
+import { getNixpacksCommand } from "./nixpacks";
+import { getPaketoCommand } from "./paketo";
+import { getRailpackCommand } from "./railpack";
+import { getStaticCommand } from "./static";
// NIXPACKS codeDirectory = where is the path of the code directory
// HEROKU codeDirectory = where is the path of the code directory
@@ -34,76 +33,35 @@ export type ApplicationNested = InferResultType<
}
>;
-export const buildApplication = async (
- application: ApplicationNested,
- logPath: string,
-) => {
- const writeStream = createWriteStream(logPath, { flags: "a" });
- const { buildType, sourceType } = application;
- try {
- writeStream.write(
- `\nBuild ${buildType}: β
\nSource Type: ${sourceType}: β
\n`,
- );
- console.log(`Build ${buildType}: β
`);
- if (buildType === "nixpacks") {
- await buildNixpacks(application, writeStream);
- } else if (buildType === "heroku_buildpacks") {
- await buildHeroku(application, writeStream);
- } else if (buildType === "paketo_buildpacks") {
- await buildPaketo(application, writeStream);
- } else if (buildType === "dockerfile") {
- await buildCustomDocker(application, writeStream);
- } else if (buildType === "static") {
- await buildStatic(application, writeStream);
- } else if (buildType === "railpack") {
- await buildRailpack(application, writeStream);
- }
-
- if (application.registryId) {
- await uploadImage(application, writeStream);
- }
- await mechanizeDockerContainer(application);
- writeStream.write("Docker Deployed: β
");
- } catch (error) {
- if (error instanceof Error) {
- writeStream.write(`Error β\n${error?.message}`);
- } else {
- writeStream.write("Error β");
- }
- throw error;
- } finally {
- writeStream.end();
- }
-};
-
-export const getBuildCommand = (
- application: ApplicationNested,
- logPath: string,
-) => {
+export const getBuildCommand = (application: ApplicationNested) => {
let command = "";
const { buildType, registry } = application;
+
+ if (application.sourceType === "docker") {
+ return "";
+ }
switch (buildType) {
case "nixpacks":
- command = getNixpacksCommand(application, logPath);
+ command = getNixpacksCommand(application);
break;
case "heroku_buildpacks":
- command = getHerokuCommand(application, logPath);
+ command = getHerokuCommand(application);
break;
case "paketo_buildpacks":
- command = getPaketoCommand(application, logPath);
+ command = getPaketoCommand(application);
break;
case "static":
- command = getStaticCommand(application, logPath);
+ command = getStaticCommand(application);
break;
case "dockerfile":
- command = getDockerCommand(application, logPath);
+ command = getDockerCommand(application);
break;
case "railpack":
- command = getRailpackCommand(application, logPath);
+ command = getRailpackCommand(application);
break;
}
if (registry) {
- command += uploadImageRemoteCommand(application, logPath);
+ command += uploadImageRemoteCommand(application);
}
return command;
diff --git a/packages/server/src/utils/builders/nixpacks.ts b/packages/server/src/utils/builders/nixpacks.ts
index 76905d0e7..b7134ea65 100644
--- a/packages/server/src/utils/builders/nixpacks.ts
+++ b/packages/server/src/utils/builders/nixpacks.ts
@@ -1,106 +1,16 @@
-import { existsSync, mkdirSync, type WriteStream } from "node:fs";
import path from "node:path";
-import {
- buildStatic,
- getStaticCommand,
-} from "@dokploy/server/utils/builders/static";
+import { getStaticCommand } from "@dokploy/server/utils/builders/static";
import { nanoid } from "nanoid";
-import { prepareEnvironmentVariables } from "../docker/utils";
+import { prepareEnvironmentVariablesForShell } from "../docker/utils";
import { getBuildAppDirectory } from "../filesystem/directory";
-import { spawnAsync } from "../process/spawnAsync";
import type { ApplicationNested } from ".";
-export const buildNixpacks = async (
- application: ApplicationNested,
- writeStream: WriteStream,
-) => {
+export const getNixpacksCommand = (application: ApplicationNested) => {
const { env, appName, publishDirectory, cleanCache } = application;
const buildAppDirectory = getBuildAppDirectory(application);
const buildContainerId = `${appName}-${nanoid(10)}`;
- const envVariables = prepareEnvironmentVariables(
- env,
- application.environment.project.env,
- application.environment.env,
- );
-
- const writeToStream = (data: string) => {
- if (writeStream.writable) {
- writeStream.write(data);
- }
- };
-
- try {
- const args = ["build", buildAppDirectory, "--name", appName];
-
- if (cleanCache) {
- args.push("--no-cache");
- }
-
- 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");
- }
-
- await spawnAsync("nixpacks", args, writeToStream);
-
- /*
- 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) {
- await spawnAsync(
- "docker",
- ["create", "--name", buildContainerId, appName],
- writeToStream,
- );
-
- const localPath = path.join(buildAppDirectory, publishDirectory);
-
- if (!existsSync(path.dirname(localPath))) {
- mkdirSync(path.dirname(localPath), { recursive: true });
- }
-
- // https://docs.docker.com/reference/cli/docker/container/cp/
- const isDirectory =
- publishDirectory.endsWith("/") || !path.extname(publishDirectory);
-
- await spawnAsync(
- "docker",
- [
- "cp",
- `${buildContainerId}:/app/${publishDirectory}${isDirectory ? "/." : ""}`,
- localPath,
- ],
- writeToStream,
- );
-
- await spawnAsync("docker", ["rm", buildContainerId], writeToStream);
-
- await buildStatic(application, writeStream);
- }
- return true;
- } catch (e) {
- await spawnAsync("docker", ["rm", buildContainerId], writeToStream);
-
- throw e;
- }
-};
-
-export const getNixpacksCommand = (
- application: ApplicationNested,
- logPath: string,
-) => {
- const { env, appName, publishDirectory, cleanCache } = application;
-
- const buildAppDirectory = getBuildAppDirectory(application);
- const buildContainerId = `${appName}-${nanoid(10)}`;
- const envVariables = prepareEnvironmentVariables(
+ const envVariables = prepareEnvironmentVariablesForShell(
env,
application.environment.project.env,
application.environment.env,
@@ -113,7 +23,7 @@ export const getNixpacksCommand = (
}
for (const env of envVariables) {
- args.push("--env", `'${env}'`);
+ args.push("--env", env);
}
if (publishDirectory) {
@@ -122,12 +32,12 @@ export const getNixpacksCommand = (
}
const command = `nixpacks ${args.join(" ")}`;
let bashCommand = `
-echo "Starting nixpacks build..." >> ${logPath};
-${command} >> ${logPath} 2>> ${logPath} || {
- echo "β Nixpacks build failed" >> ${logPath};
- exit 1;
-}
-echo "β
Nixpacks build completed." >> ${logPath};
+ echo "Starting nixpacks build..." ;
+ ${command} || {
+ echo "β Nixpacks build failed" ;
+ exit 1;
+ }
+ echo "β
Nixpacks build completed." ;
`;
/*
@@ -141,16 +51,16 @@ echo "β
Nixpacks build completed." >> ${logPath};
publishDirectory.endsWith("/") || !path.extname(publishDirectory);
bashCommand += `
-docker create --name ${buildContainerId} ${appName}
-mkdir -p ${localPath}
-docker cp ${buildContainerId}:/app/${publishDirectory}${isDirectory ? "/." : ""} ${path.join(buildAppDirectory, publishDirectory)} >> ${logPath} 2>> ${logPath} || {
+ docker create --name ${buildContainerId} ${appName}
+ mkdir -p ${localPath}
+ docker cp ${buildContainerId}:/app/${publishDirectory}${isDirectory ? "/." : ""} ${path.join(buildAppDirectory, publishDirectory)} || {
+ docker rm ${buildContainerId}
+ echo "β Copying ${publishDirectory} to ${path.join(buildAppDirectory, publishDirectory)} failed" ;
+ exit 1;
+ }
docker rm ${buildContainerId}
- echo "β Copying ${publishDirectory} to ${path.join(buildAppDirectory, publishDirectory)} failed" >> ${logPath};
- exit 1;
-}
-docker rm ${buildContainerId}
-${getStaticCommand(application, logPath)}
- `;
+ ${getStaticCommand(application)}
+ `;
}
return bashCommand;
diff --git a/packages/server/src/utils/builders/paketo.ts b/packages/server/src/utils/builders/paketo.ts
index b95a1bb31..bb4f8c8a4 100644
--- a/packages/server/src/utils/builders/paketo.ts
+++ b/packages/server/src/utils/builders/paketo.ts
@@ -1,57 +1,12 @@
-import type { WriteStream } from "node:fs";
-import { prepareEnvironmentVariables } from "../docker/utils";
+import { prepareEnvironmentVariablesForShell } from "../docker/utils";
import { getBuildAppDirectory } from "../filesystem/directory";
-import { spawnAsync } from "../process/spawnAsync";
import type { ApplicationNested } from ".";
-export const buildPaketo = async (
- application: ApplicationNested,
- writeStream: WriteStream,
-) => {
- const { env, appName, cleanCache } = application;
- const buildAppDirectory = getBuildAppDirectory(application);
- const envVariables = prepareEnvironmentVariables(
- env,
- application.environment.project.env,
- application.environment.env,
- );
- try {
- const args = [
- "build",
- appName,
- "--path",
- buildAppDirectory,
- "--builder",
- "paketobuildpacks/builder-jammy-full",
- ];
-
- if (cleanCache) {
- args.push("--clear-cache");
- }
-
- for (const env of envVariables) {
- args.push("--env", env);
- }
-
- await spawnAsync("pack", args, (data) => {
- if (writeStream.writable) {
- writeStream.write(data);
- }
- });
- return true;
- } catch (e) {
- throw e;
- }
-};
-
-export const getPaketoCommand = (
- application: ApplicationNested,
- logPath: string,
-) => {
+export const getPaketoCommand = (application: ApplicationNested) => {
const { env, appName, cleanCache } = application;
const buildAppDirectory = getBuildAppDirectory(application);
- const envVariables = prepareEnvironmentVariables(
+ const envVariables = prepareEnvironmentVariablesForShell(
env,
application.environment.project.env,
application.environment.env,
@@ -71,17 +26,17 @@ export const getPaketoCommand = (
}
for (const env of envVariables) {
- args.push("--env", `'${env}'`);
+ args.push("--env", env);
}
const command = `pack ${args.join(" ")}`;
const bashCommand = `
-echo "Starting Paketo build..." >> ${logPath};
-${command} >> ${logPath} 2>> ${logPath} || {
- echo "β Paketo build failed" >> ${logPath};
+echo "Starting Paketo build..." ;
+${command} || {
+ echo "β Paketo build failed" ;
exit 1;
}
-echo "β
Paketo build completed." >> ${logPath};
+echo "β
Paketo build completed." ;
`;
return bashCommand;
diff --git a/packages/server/src/utils/builders/railpack.ts b/packages/server/src/utils/builders/railpack.ts
index 4adc9ca1c..62fa9f975 100644
--- a/packages/server/src/utils/builders/railpack.ts
+++ b/packages/server/src/utils/builders/railpack.ts
@@ -1,13 +1,12 @@
import { createHash } from "node:crypto";
-import type { WriteStream } from "node:fs";
import { nanoid } from "nanoid";
+import { quote } from "shell-quote";
import {
parseEnvironmentKeyValuePair,
prepareEnvironmentVariables,
+ prepareEnvironmentVariablesForShell,
} from "../docker/utils";
import { getBuildAppDirectory } from "../filesystem/directory";
-import { execAsync } from "../process/execAsync";
-import { spawnAsync } from "../process/spawnAsync";
import type { ApplicationNested } from ".";
const calculateSecretsHash = (envVariables: string[]): string => {
@@ -18,111 +17,10 @@ const calculateSecretsHash = (envVariables: string[]): string => {
return hash.digest("hex");
};
-export const buildRailpack = async (
- application: ApplicationNested,
- writeStream: WriteStream,
-) => {
+export const getRailpackCommand = (application: ApplicationNested) => {
const { env, appName, cleanCache } = application;
const buildAppDirectory = getBuildAppDirectory(application);
- const envVariables = prepareEnvironmentVariables(
- env,
- application.environment.project.env,
- application.environment.env,
- );
-
- try {
- await execAsync(
- "docker buildx create --use --name builder-containerd --driver docker-container || true",
- );
-
- await execAsync("docker buildx use builder-containerd");
-
- // First prepare the build plan and info
- const prepareArgs = [
- "prepare",
- buildAppDirectory,
- "--plan-out",
- `${buildAppDirectory}/railpack-plan.json`,
- "--info-out",
- `${buildAppDirectory}/railpack-info.json`,
- ];
-
- // Add environment variables to prepare command
- for (const env of envVariables) {
- prepareArgs.push("--env", env);
- }
-
- // Run prepare command
- await spawnAsync("railpack", prepareArgs, (data) => {
- if (writeStream.writable) {
- writeStream.write(data);
- }
- });
-
- // Calculate secrets hash for layer invalidation
- const secretsHash = calculateSecretsHash(envVariables);
-
- // Build with BuildKit using the Railpack frontend
- const cacheKey = cleanCache ? nanoid(10) : undefined;
- const buildArgs = [
- "buildx",
- "build",
- ...(cacheKey
- ? [
- "--build-arg",
- `secrets-hash=${secretsHash}`,
- "--build-arg",
- `cache-key=${cacheKey}`,
- ]
- : []),
- "--build-arg",
- `BUILDKIT_SYNTAX=ghcr.io/railwayapp/railpack-frontend:v${application.railpackVersion}`,
- "-f",
- `${buildAppDirectory}/railpack-plan.json`,
- "--output",
- `type=docker,name=${appName}`,
- ];
-
- // Add secrets properly formatted
- const env: { [key: string]: string } = {};
- for (const pair of envVariables) {
- const [key, value] = parseEnvironmentKeyValuePair(pair);
- if (key && value) {
- buildArgs.push("--secret", `id=${key},env=${key}`);
- env[key] = value;
- }
- }
-
- buildArgs.push(buildAppDirectory);
-
- await spawnAsync(
- "docker",
- buildArgs,
- (data) => {
- if (writeStream.writable) {
- writeStream.write(data);
- }
- },
- {
- env: { ...process.env, ...env },
- },
- );
-
- return true;
- } catch (e) {
- throw e;
- } finally {
- await execAsync("docker buildx rm builder-containerd");
- }
-};
-
-export const getRailpackCommand = (
- application: ApplicationNested,
- logPath: string,
-) => {
- const { env, appName, cleanCache } = application;
- const buildAppDirectory = getBuildAppDirectory(application);
- const envVariables = prepareEnvironmentVariables(
+ const envVariables = prepareEnvironmentVariablesForShell(
env,
application.environment.project.env,
application.environment.env,
@@ -139,7 +37,7 @@ export const getRailpackCommand = (
];
for (const env of envVariables) {
- prepareArgs.push("--env", `'${env}'`);
+ prepareArgs.push("--env", env);
}
// Calculate secrets hash for layer invalidation
@@ -167,37 +65,49 @@ export const getRailpackCommand = (
];
// Add secrets properly formatted
+ // Use prepareEnvironmentVariables (without ForShell) to get raw values for parsing
+ const rawEnvVariables = prepareEnvironmentVariables(
+ env,
+ application.environment.project.env,
+ application.environment.env,
+ );
const exportEnvs = [];
- for (const pair of envVariables) {
+ for (const pair of rawEnvVariables) {
const [key, value] = parseEnvironmentKeyValuePair(pair);
if (key && value) {
buildArgs.push("--secret", `id=${key},env=${key}`);
- exportEnvs.push(`export ${key}='${value}'`);
+ exportEnvs.push(`export ${key}=${quote([value])}`);
}
}
buildArgs.push(buildAppDirectory);
const bashCommand = `
+
# Ensure we have a builder with containerd
+
+export RAILPACK_VERSION=${application.railpackVersion}
+bash -c "$(curl -fsSL https://railpack.com/install.sh)"
docker buildx create --use --name builder-containerd --driver docker-container || true
docker buildx use builder-containerd
-echo "Preparing Railpack build plan..." >> "${logPath}";
-railpack ${prepareArgs.join(" ")} >> ${logPath} 2>> ${logPath} || {
- echo "β Railpack prepare failed" >> ${logPath};
+echo "Preparing Railpack build plan..." ;
+railpack ${prepareArgs.join(" ")} || {
+ echo "β Railpack prepare failed" ;
+ docker buildx rm builder-containerd || true
exit 1;
}
-echo "β
Railpack prepare completed." >> ${logPath};
+echo "β
Railpack prepare completed." ;
-echo "Building with Railpack frontend..." >> "${logPath}";
+echo "Building with Railpack frontend..." ;
# Export environment variables for secrets
${exportEnvs.join("\n")}
-docker ${buildArgs.join(" ")} >> ${logPath} 2>> ${logPath} || {
- echo "β Railpack build failed" >> ${logPath};
+docker ${buildArgs.join(" ")} || {
+ echo "β Railpack build failed" ;
+ docker buildx rm builder-containerd || true
exit 1;
}
-echo "β
Railpack build completed." >> ${logPath};
+echo "β
Railpack build completed." ;
docker buildx rm builder-containerd
`;
diff --git a/packages/server/src/utils/builders/static.ts b/packages/server/src/utils/builders/static.ts
index e59faa711..99fa25285 100644
--- a/packages/server/src/utils/builders/static.ts
+++ b/packages/server/src/utils/builders/static.ts
@@ -1,9 +1,5 @@
-import type { WriteStream } from "node:fs";
-import {
- buildCustomDocker,
- getDockerCommand,
-} from "@dokploy/server/utils/builders/docker-file";
-import { createFile, getCreateFileCommand } from "../docker/utils";
+import { getDockerCommand } from "@dokploy/server/utils/builders/docker-file";
+import { getCreateFileCommand } from "../docker/utils";
import { getBuildAppDirectory } from "../filesystem/directory";
import type { ApplicationNested } from ".";
@@ -32,81 +28,40 @@ http {
}
`;
-export const buildStatic = async (
- application: ApplicationNested,
- writeStream: WriteStream,
-) => {
+export const getStaticCommand = (application: ApplicationNested) => {
const { publishDirectory, isStaticSpa } = application;
const buildAppDirectory = getBuildAppDirectory(application);
-
- try {
- if (isStaticSpa) {
- createFile(buildAppDirectory, "nginx.conf", nginxSpaConfig);
- }
-
- createFile(
+ let command = "";
+ if (isStaticSpa) {
+ command += getCreateFileCommand(
buildAppDirectory,
- ".dockerignore",
- [".git", ".env", "Dockerfile", ".dockerignore"].join("\n"),
+ "nginx.conf",
+ nginxSpaConfig,
);
-
- createFile(
- buildAppDirectory,
- "Dockerfile",
- [
- "FROM nginx:alpine",
- "WORKDIR /usr/share/nginx/html/",
- isStaticSpa ? "COPY nginx.conf /etc/nginx/nginx.conf" : "",
- `COPY ${publishDirectory || "."} .`,
- 'CMD ["nginx", "-g", "daemon off;"]',
- ].join("\n"),
- );
-
- createFile(
- buildAppDirectory,
- ".dockerignore",
- [".git", ".env", "Dockerfile", ".dockerignore"].join("\n"),
- );
-
- await buildCustomDocker(
- {
- ...application,
- buildType: "dockerfile",
- dockerfile: "Dockerfile",
- },
- writeStream,
- );
-
- return true;
- } catch (e) {
- throw e;
}
-};
-export const getStaticCommand = (
- application: ApplicationNested,
- logPath: string,
-) => {
- const { publishDirectory } = application;
- const buildAppDirectory = getBuildAppDirectory(application);
+ command += getCreateFileCommand(
+ buildAppDirectory,
+ ".dockerignore",
+ [".git", ".env", "Dockerfile", ".dockerignore"].join("\n"),
+ );
- let command = getCreateFileCommand(
+ command += getCreateFileCommand(
buildAppDirectory,
"Dockerfile",
[
"FROM nginx:alpine",
"WORKDIR /usr/share/nginx/html/",
+ isStaticSpa ? "COPY nginx.conf /etc/nginx/nginx.conf" : "",
`COPY ${publishDirectory || "."} .`,
+ 'CMD ["nginx", "-g", "daemon off;"]',
].join("\n"),
);
- command += getDockerCommand(
- {
- ...application,
- buildType: "dockerfile",
- dockerfile: "Dockerfile",
- },
- logPath,
- );
+ command += getDockerCommand({
+ ...application,
+ buildType: "dockerfile",
+ dockerfile: "Dockerfile",
+ });
return command;
};
diff --git a/packages/server/src/utils/cluster/upload.ts b/packages/server/src/utils/cluster/upload.ts
index c13a2701c..33be287e1 100644
--- a/packages/server/src/utils/cluster/upload.ts
+++ b/packages/server/src/utils/cluster/upload.ts
@@ -1,11 +1,6 @@
-import type { WriteStream } from "node:fs";
import type { ApplicationNested } from "../builders";
-import { spawnAsync } from "../process/spawnAsync";
-export const uploadImage = async (
- application: ApplicationNested,
- writeStream: WriteStream,
-) => {
+export const uploadImageRemoteCommand = (application: ApplicationNested) => {
const registry = application.registry;
if (!registry) {
@@ -19,85 +14,28 @@ export const uploadImage = async (
const finalURL = registryUrl;
// Build registry tag in correct format: registry.com/owner/image:tag
- // For ghcr.io: ghcr.io/username/image:tag
- // For docker.io: docker.io/username/image:tag
const registryTag = imagePrefix
? `${registryUrl ? `${registryUrl}/` : ""}${imagePrefix}/${imageName}`
: `${registryUrl ? `${registryUrl}/` : ""}${username}/${imageName}`;
- try {
- writeStream.write(
- `π¦ [Enabled Registry] Uploading image to ${registry.registryType} | ${imageName} | ${finalURL} | ${registryTag}\n`,
- );
- const loginCommand = spawnAsync(
- "docker",
- ["login", finalURL, "-u", registry.username, "--password-stdin"],
- (data) => {
- if (writeStream.writable) {
- writeStream.write(data);
- }
- },
- );
- loginCommand.child?.stdin?.write(registry.password);
- loginCommand.child?.stdin?.end();
- await loginCommand;
-
- await spawnAsync("docker", ["tag", imageName, registryTag], (data) => {
- if (writeStream.writable) {
- writeStream.write(data);
- }
- });
-
- await spawnAsync("docker", ["push", registryTag], (data) => {
- if (writeStream.writable) {
- writeStream.write(data);
- }
- });
- } catch (error) {
- console.log(error);
- throw error;
- }
-};
-
-export const uploadImageRemoteCommand = (
- application: ApplicationNested,
- logPath: string,
-) => {
- const registry = application.registry;
-
- if (!registry) {
- throw new Error("Registry not found");
- }
-
- const { registryUrl, imagePrefix, username } = registry;
- const { appName } = application;
- const imageName = `${appName}:latest`;
-
- const finalURL = registryUrl;
-
- // Build registry tag in correct format: registry.com/owner/image:tag
- const registryTag = imagePrefix
- ? `${registryUrl}/${imagePrefix}/${imageName}`
- : `${registryUrl}/${username}/${imageName}`;
-
try {
const command = `
- echo "π¦ [Enabled Registry] Uploading image to '${registry.registryType}' | '${registryTag}'" >> ${logPath};
- echo "${registry.password}" | docker login ${finalURL} -u ${registry.username} --password-stdin >> ${logPath} 2>> ${logPath} || {
- echo "β DockerHub Failed" >> ${logPath};
+ echo "π¦ [Enabled Registry] Uploading image to '${registry.registryType}' | '${registryTag}'" ;
+ echo "${registry.password}" | docker login ${finalURL} -u ${registry.username} --password-stdin || {
+ echo "β DockerHub Failed" ;
exit 1;
}
- echo "β
Registry Login Success" >> ${logPath};
- docker tag ${imageName} ${registryTag} >> ${logPath} 2>> ${logPath} || {
- echo "β Error tagging image" >> ${logPath};
+ echo "β
Registry Login Success" ;
+ docker tag ${imageName} ${registryTag} || {
+ echo "β Error tagging image" ;
exit 1;
}
- echo "β
Image Tagged" >> ${logPath};
- docker push ${registryTag} 2>> ${logPath} || {
- echo "β Error pushing image" >> ${logPath};
+ echo "β
Image Tagged" ;
+ docker push ${registryTag} || {
+ echo "β Error pushing image" ;
exit 1;
}
- echo "β
Image Pushed" >> ${logPath};
+ echo "β
Image Pushed" ;
`;
return command;
} catch (error) {
diff --git a/packages/server/src/utils/docker/collision.ts b/packages/server/src/utils/docker/collision.ts
index 9752100ca..88d20d4d8 100644
--- a/packages/server/src/utils/docker/collision.ts
+++ b/packages/server/src/utils/docker/collision.ts
@@ -1,11 +1,11 @@
import { findComposeById } from "@dokploy/server/services/compose";
import { stringify } from "yaml";
+import { execAsync, execAsyncRemote } from "../process/execAsync";
import { addAppNameToAllServiceNames } from "./collision/root-network";
import { generateRandomHash } from "./compose";
import { addSuffixToAllVolumes } from "./compose/volume";
import {
cloneCompose,
- cloneComposeRemote,
loadDockerCompose,
loadDockerComposeRemote,
} from "./domain";
@@ -31,10 +31,11 @@ export const randomizeIsolatedDeploymentComposeFile = async (
) => {
const compose = await findComposeById(composeId);
+ const command = await cloneCompose(compose);
if (compose.serverId) {
- await cloneComposeRemote(compose);
+ await execAsyncRemote(compose.serverId, command);
} else {
- await cloneCompose(compose);
+ await execAsync(command);
}
let composeData: ComposeSpecification | null;
diff --git a/packages/server/src/utils/docker/domain.ts b/packages/server/src/utils/docker/domain.ts
index 7a9521d1d..2272f364e 100644
--- a/packages/server/src/utils/docker/domain.ts
+++ b/packages/server/src/utils/docker/domain.ts
@@ -1,35 +1,16 @@
import fs, { existsSync, readFileSync } from "node:fs";
-import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { paths } from "@dokploy/server/constants";
import type { Compose } from "@dokploy/server/services/compose";
import type { Domain } from "@dokploy/server/services/domain";
import { parse, stringify } from "yaml";
import { execAsyncRemote } from "../process/execAsync";
-import {
- cloneRawBitbucketRepository,
- cloneRawBitbucketRepositoryRemote,
-} from "../providers/bitbucket";
-import {
- cloneGitRawRepository,
- cloneRawGitRepositoryRemote,
-} from "../providers/git";
-import {
- cloneRawGiteaRepository,
- cloneRawGiteaRepositoryRemote,
-} from "../providers/gitea";
-import {
- cloneRawGithubRepository,
- cloneRawGithubRepositoryRemote,
-} from "../providers/github";
-import {
- cloneRawGitlabRepository,
- cloneRawGitlabRepositoryRemote,
-} from "../providers/gitlab";
-import {
- createComposeFileRaw,
- createComposeFileRawRemote,
-} from "../providers/raw";
+import { cloneBitbucketRepository } from "../providers/bitbucket";
+import { cloneGitRepository } from "../providers/git";
+import { cloneGiteaRepository } from "../providers/gitea";
+import { cloneGithubRepository } from "../providers/github";
+import { cloneGitlabRepository } from "../providers/gitlab";
+import { getCreateComposeFileCommand } from "../providers/raw";
import { randomizeDeployableSpecificationFile } from "./collision";
import { randomizeSpecificationFile } from "./compose";
import type {
@@ -40,35 +21,25 @@ import type {
import { encodeBase64 } from "./utils";
export const cloneCompose = async (compose: Compose) => {
+ let command = "set -e;";
+ const entity = {
+ ...compose,
+ type: "compose" as const,
+ };
if (compose.sourceType === "github") {
- await cloneRawGithubRepository(compose);
+ command += await cloneGithubRepository(entity);
} else if (compose.sourceType === "gitlab") {
- await cloneRawGitlabRepository(compose);
+ command += await cloneGitlabRepository(entity);
} else if (compose.sourceType === "bitbucket") {
- await cloneRawBitbucketRepository(compose);
+ command += await cloneBitbucketRepository(entity);
} else if (compose.sourceType === "git") {
- await cloneGitRawRepository(compose);
+ command += await cloneGitRepository(entity);
} else if (compose.sourceType === "gitea") {
- await cloneRawGiteaRepository(compose);
+ command += await cloneGiteaRepository(entity);
} 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 === "gitea") {
- await cloneRawGiteaRepositoryRemote(compose);
- } else if (compose.sourceType === "raw") {
- await createComposeFileRawRemote(compose);
+ command += getCreateComposeFileCommand(compose);
}
+ return command;
};
export const getComposePath = (compose: Compose) => {
@@ -134,25 +105,6 @@ export const readComposeFile = async (compose: Compose) => {
export const writeDomainsToCompose = async (
compose: Compose,
domains: Domain[],
-) => {
- if (!domains.length) {
- return;
- }
- const composeConverted = await addDomainToCompose(compose, domains);
-
- const path = getComposePath(compose);
- const composeString = stringify(composeConverted, { lineWidth: 1000 });
- try {
- await writeFile(path, composeString, "utf8");
- } catch (error) {
- throw error;
- }
-};
-
-export const writeDomainsToComposeRemote = async (
- compose: Compose,
- domains: Domain[],
- logPath: string,
) => {
if (!domains.length) {
return "";
@@ -164,23 +116,21 @@ export const writeDomainsToComposeRemote = async (
if (!composeConverted) {
return `
-echo "β Error: Compose file not found" >> ${logPath};
+echo "β Error: Compose file not found";
exit 1;
`;
}
- if (compose.serverId) {
- const composeString = stringify(composeConverted, { lineWidth: 1000 });
- const encodedContent = encodeBase64(composeString);
- return `echo "${encodedContent}" | base64 -d > "${path}";`;
- }
+
+ const composeString = stringify(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};
+ return `echo "β Has occurred an error: ${error?.message || error}";
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[],
@@ -190,7 +140,7 @@ export const addDomainToCompose = async (
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
+ result = await loadDockerComposeRemote(compose);
} else {
result = await loadDockerCompose(compose);
}
diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts
index 6d00aa0df..4258cfbbe 100644
--- a/packages/server/src/utils/docker/utils.ts
+++ b/packages/server/src/utils/docker/utils.ts
@@ -5,6 +5,7 @@ import { docker, paths } from "@dokploy/server/constants";
import type { Compose } from "@dokploy/server/services/compose";
import type { ContainerInfo, ResourceRequirements } from "dockerode";
import { parse } from "dotenv";
+import { quote } from "shell-quote";
import type { ApplicationNested } from "../builders";
import type { MariadbNested } from "../databases/mariadb";
import type { MongoNested } from "../databases/mongo";
@@ -310,6 +311,21 @@ export const prepareEnvironmentVariables = (
return resolvedVars;
};
+export const prepareEnvironmentVariablesForShell = (
+ serviceEnv: string | null,
+ projectEnv?: string | null,
+ environmentEnv?: string | null,
+): string[] => {
+ const envVars = prepareEnvironmentVariables(
+ serviceEnv,
+ projectEnv,
+ environmentEnv,
+ );
+ // Using shell-quote library to properly escape shell arguments
+ // This is the standard way to handle special characters in shell commands
+ return envVars.map((env) => quote([env]));
+};
+
export const parseEnvironmentKeyValuePair = (
pair: string,
): [string, string] => {
diff --git a/packages/server/src/utils/notifications/build-error.ts b/packages/server/src/utils/notifications/build-error.ts
index f1746a890..f05fa8134 100644
--- a/packages/server/src/utils/notifications/build-error.ts
+++ b/packages/server/src/utils/notifications/build-error.ts
@@ -54,301 +54,303 @@ export const sendBuildErrorNotifications = async ({
for (const notification of notificationList) {
const { email, discord, telegram, slack, gotify, ntfy, custom, lark } =
notification;
- if (email) {
- const template = await renderAsync(
- BuildFailedEmail({
+ try {
+ if (email) {
+ const template = await renderAsync(
+ BuildFailedEmail({
+ projectName,
+ applicationName,
+ applicationType,
+ errorMessage: errorMessage,
+ buildLink,
+ date: date.toLocaleString(),
+ }),
+ ).catch();
+ await sendEmailNotification(
+ email,
+ "Build failed for dokploy",
+ template,
+ );
+ }
+
+ if (discord) {
+ const decorate = (decoration: string, text: string) =>
+ `${discord.decoration ? decoration : ""} ${text}`.trim();
+
+ const limitCharacter = 800;
+ const truncatedErrorMessage = errorMessage.substring(0, limitCharacter);
+ await sendDiscordNotification(discord, {
+ title: decorate(">", "`β οΈ` Build Failed"),
+ color: 0xed4245,
+ fields: [
+ {
+ name: decorate("`π οΈ`", "Project"),
+ value: projectName,
+ inline: true,
+ },
+ {
+ name: decorate("`βοΈ`", "Application"),
+ value: applicationName,
+ inline: true,
+ },
+ {
+ name: decorate("`β`", "Type"),
+ value: applicationType,
+ inline: true,
+ },
+ {
+ name: decorate("`π
`", "Date"),
+ value: ``,
+ inline: true,
+ },
+ {
+ name: decorate("`β`", "Time"),
+ value: ``,
+ inline: true,
+ },
+ {
+ name: decorate("`β`", "Type"),
+ value: "Failed",
+ inline: true,
+ },
+ {
+ name: decorate("`β οΈ`", "Error Message"),
+ value: `\`\`\`${truncatedErrorMessage}\`\`\``,
+ },
+ {
+ name: decorate("`π§·`", "Build Link"),
+ value: `[Click here to access build link](${buildLink})`,
+ },
+ ],
+ timestamp: date.toISOString(),
+ footer: {
+ text: "Dokploy Build Notification",
+ },
+ });
+ }
+
+ if (gotify) {
+ const decorate = (decoration: string, text: string) =>
+ `${gotify.decoration ? decoration : ""} ${text}\n`;
+ await sendGotifyNotification(
+ gotify,
+ decorate("β οΈ", "Build Failed"),
+ `${decorate("π οΈ", `Project: ${projectName}`)}` +
+ `${decorate("βοΈ", `Application: ${applicationName}`)}` +
+ `${decorate("β", `Type: ${applicationType}`)}` +
+ `${decorate("π", `Date: ${date.toLocaleString()}`)}` +
+ `${decorate("β οΈ", `Error:\n${errorMessage}`)}` +
+ `${decorate("π", `Build details:\n${buildLink}`)}`,
+ );
+ }
+
+ if (ntfy) {
+ await sendNtfyNotification(
+ ntfy,
+ "Build Failed",
+ "warning",
+ `view, Build details, ${buildLink}, clear=true;`,
+ `π οΈProject: ${projectName}\n` +
+ `βοΈApplication: ${applicationName}\n` +
+ `βType: ${applicationType}\n` +
+ `πDate: ${date.toLocaleString()}\n` +
+ `β οΈError:\n${errorMessage}`,
+ );
+ }
+
+ if (telegram) {
+ const inlineButton = [
+ [
+ {
+ text: "Deployment Logs",
+ url: buildLink,
+ },
+ ],
+ ];
+
+ await sendTelegramNotification(
+ telegram,
+ `β οΈ Build Failed\n\nProject: ${projectName}\nApplication: ${applicationName}\nType: ${applicationType}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}\n\nError:\n${errorMessage}`,
+ inlineButton,
+ );
+ }
+
+ if (slack) {
+ const { channel } = slack;
+ await sendSlackNotification(slack, {
+ channel: channel,
+ attachments: [
+ {
+ color: "#FF0000",
+ pretext: ":warning: *Build Failed*",
+ fields: [
+ {
+ title: "Project",
+ value: projectName,
+ short: true,
+ },
+ {
+ title: "Application",
+ value: applicationName,
+ short: true,
+ },
+ {
+ title: "Type",
+ value: applicationType,
+ short: true,
+ },
+ {
+ title: "Time",
+ value: date.toLocaleString(),
+ short: true,
+ },
+ {
+ title: "Error",
+ value: `\`\`\`${errorMessage}\`\`\``,
+ short: false,
+ },
+ ],
+ actions: [
+ {
+ type: "button",
+ text: "View Build Details",
+ url: buildLink,
+ },
+ ],
+ },
+ ],
+ });
+ }
+
+ if (custom) {
+ await sendCustomNotification(custom, {
+ title: "Build Error",
+ message: "Build failed with errors",
projectName,
applicationName,
applicationType,
- errorMessage: errorMessage,
+ errorMessage,
buildLink,
+ timestamp: date.toISOString(),
date: date.toLocaleString(),
- }),
- ).catch();
- await sendEmailNotification(email, "Build failed for dokploy", template);
- }
+ status: "error",
+ type: "build",
+ });
+ }
- if (discord) {
- const decorate = (decoration: string, text: string) =>
- `${discord.decoration ? decoration : ""} ${text}`.trim();
-
- const limitCharacter = 800;
- const truncatedErrorMessage = errorMessage.substring(0, limitCharacter);
- await sendDiscordNotification(discord, {
- title: decorate(">", "`β οΈ` Build Failed"),
- color: 0xed4245,
- fields: [
- {
- name: decorate("`π οΈ`", "Project"),
- value: projectName,
- inline: true,
- },
- {
- name: decorate("`βοΈ`", "Application"),
- value: applicationName,
- inline: true,
- },
- {
- name: decorate("`β`", "Type"),
- value: applicationType,
- inline: true,
- },
- {
- name: decorate("`π
`", "Date"),
- value: ``,
- inline: true,
- },
- {
- name: decorate("`β`", "Time"),
- value: ``,
- inline: true,
- },
- {
- name: decorate("`β`", "Type"),
- value: "Failed",
- inline: true,
- },
- {
- name: decorate("`β οΈ`", "Error Message"),
- value: `\`\`\`${truncatedErrorMessage}\`\`\``,
- },
- {
- name: decorate("`π§·`", "Build Link"),
- value: `[Click here to access build link](${buildLink})`,
- },
- ],
- timestamp: date.toISOString(),
- footer: {
- text: "Dokploy Build Notification",
- },
- });
- }
-
- if (gotify) {
- const decorate = (decoration: string, text: string) =>
- `${gotify.decoration ? decoration : ""} ${text}\n`;
- await sendGotifyNotification(
- gotify,
- decorate("β οΈ", "Build Failed"),
- `${decorate("π οΈ", `Project: ${projectName}`)}` +
- `${decorate("βοΈ", `Application: ${applicationName}`)}` +
- `${decorate("β", `Type: ${applicationType}`)}` +
- `${decorate("π", `Date: ${date.toLocaleString()}`)}` +
- `${decorate("β οΈ", `Error:\n${errorMessage}`)}` +
- `${decorate("π", `Build details:\n${buildLink}`)}`,
- );
- }
-
- if (ntfy) {
- await sendNtfyNotification(
- ntfy,
- "Build Failed",
- "warning",
- `view, Build details, ${buildLink}, clear=true;`,
- `π οΈProject: ${projectName}\n` +
- `βοΈApplication: ${applicationName}\n` +
- `βType: ${applicationType}\n` +
- `πDate: ${date.toLocaleString()}\n` +
- `β οΈError:\n${errorMessage}`,
- );
- }
-
- if (telegram) {
- const inlineButton = [
- [
- {
- text: "Deployment Logs",
- url: buildLink,
- },
- ],
- ];
-
- await sendTelegramNotification(
- telegram,
- `β οΈ Build Failed\n\nProject: ${projectName}\nApplication: ${applicationName}\nType: ${applicationType}\nDate: ${format(
- date,
- "PP",
- )}\nTime: ${format(
- date,
- "pp",
- )}\n\nError:\n${errorMessage}`,
- inlineButton,
- );
- }
-
- if (slack) {
- const { channel } = slack;
- await sendSlackNotification(slack, {
- channel: channel,
- attachments: [
- {
- color: "#FF0000",
- pretext: ":warning: *Build Failed*",
- fields: [
- {
- title: "Project",
- value: projectName,
- short: true,
- },
- {
- title: "Application",
- value: applicationName,
- short: true,
- },
- {
- title: "Type",
- value: applicationType,
- short: true,
- },
- {
- title: "Time",
- value: date.toLocaleString(),
- short: true,
- },
- {
- title: "Error",
- value: `\`\`\`${errorMessage}\`\`\``,
- short: false,
- },
- ],
- actions: [
- {
- type: "button",
- text: "View Build Details",
- url: buildLink,
- },
- ],
- },
- ],
- });
- }
-
- if (custom) {
- await sendCustomNotification(custom, {
- title: "Build Error",
- message: "Build failed with errors",
- projectName,
- applicationName,
- applicationType,
- errorMessage,
- buildLink,
- timestamp: date.toISOString(),
- date: date.toLocaleString(),
- status: "error",
- type: "build",
- });
- }
-
- if (lark) {
- const limitCharacter = 800;
- const truncatedErrorMessage = errorMessage.substring(0, limitCharacter);
- await sendLarkNotification(lark, {
- msg_type: "interactive",
- card: {
- schema: "2.0",
- config: {
- update_multi: true,
- style: {
- text_size: {
- normal_v2: {
- default: "normal",
- pc: "normal",
- mobile: "heading",
+ if (lark) {
+ const limitCharacter = 800;
+ const truncatedErrorMessage = errorMessage.substring(0, limitCharacter);
+ await sendLarkNotification(lark, {
+ msg_type: "interactive",
+ card: {
+ schema: "2.0",
+ config: {
+ update_multi: true,
+ style: {
+ text_size: {
+ normal_v2: {
+ default: "normal",
+ pc: "normal",
+ mobile: "heading",
+ },
},
},
},
- },
- header: {
- title: {
- tag: "plain_text",
- content: "β οΈ Build Failed",
- },
- subtitle: {
- tag: "plain_text",
- content: "",
- },
- template: "red",
- padding: "12px 12px 12px 12px",
- },
- body: {
- direction: "vertical",
- padding: "12px 12px 12px 12px",
- elements: [
- {
- tag: "column_set",
- columns: [
- {
- tag: "column",
- width: "weighted",
- elements: [
- {
- tag: "markdown",
- content: `**Project:**\n${projectName}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- {
- tag: "markdown",
- content: `**Type:**\n${applicationType}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- {
- tag: "markdown",
- content: `**Error Message:**\n\`\`\`\n${truncatedErrorMessage}\n\`\`\``,
- text_align: "left",
- text_size: "normal_v2",
- },
- ],
- vertical_align: "top",
- weight: 1,
- },
- {
- tag: "column",
- width: "weighted",
- elements: [
- {
- tag: "markdown",
- content: `**Application:**\n${applicationName}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- {
- tag: "markdown",
- content: `**Date:**\n${format(date, "PP pp")}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- ],
- vertical_align: "top",
- weight: 1,
- },
- ],
+ header: {
+ title: {
+ tag: "plain_text",
+ content: "β οΈ Build Failed",
},
- {
- tag: "button",
- text: {
- tag: "plain_text",
- content: "View Build Details",
+ subtitle: {
+ tag: "plain_text",
+ content: "",
+ },
+ template: "red",
+ padding: "12px 12px 12px 12px",
+ },
+ body: {
+ direction: "vertical",
+ padding: "12px 12px 12px 12px",
+ elements: [
+ {
+ tag: "column_set",
+ columns: [
+ {
+ tag: "column",
+ width: "weighted",
+ elements: [
+ {
+ tag: "markdown",
+ content: `**Project:**\n${projectName}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ {
+ tag: "markdown",
+ content: `**Type:**\n${applicationType}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ {
+ tag: "markdown",
+ content: `**Error Message:**\n\`\`\`\n${truncatedErrorMessage}\n\`\`\``,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ ],
+ vertical_align: "top",
+ weight: 1,
+ },
+ {
+ tag: "column",
+ width: "weighted",
+ elements: [
+ {
+ tag: "markdown",
+ content: `**Application:**\n${applicationName}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ {
+ tag: "markdown",
+ content: `**Date:**\n${format(date, "PP pp")}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ ],
+ vertical_align: "top",
+ weight: 1,
+ },
+ ],
},
- type: "danger",
- width: "default",
- size: "medium",
- behaviors: [
- {
- type: "open_url",
- default_url: buildLink,
- pc_url: "",
- ios_url: "",
- android_url: "",
+ {
+ tag: "button",
+ text: {
+ tag: "plain_text",
+ content: "View Build Details",
},
- ],
- margin: "0px 0px 0px 0px",
- },
- ],
+ type: "danger",
+ width: "default",
+ size: "medium",
+ behaviors: [
+ {
+ type: "open_url",
+ default_url: buildLink,
+ pc_url: "",
+ ios_url: "",
+ android_url: "",
+ },
+ ],
+ margin: "0px 0px 0px 0px",
+ },
+ ],
+ },
},
- },
- });
+ });
+ }
+ } catch (error) {
+ console.log(error);
}
}
};
diff --git a/packages/server/src/utils/notifications/build-success.ts b/packages/server/src/utils/notifications/build-success.ts
index ab84a5fde..f3350d72b 100644
--- a/packages/server/src/utils/notifications/build-success.ts
+++ b/packages/server/src/utils/notifications/build-success.ts
@@ -6,337 +6,365 @@ import { renderAsync } from "@react-email/components";
import { format } from "date-fns";
import { and, eq } from "drizzle-orm";
import {
- sendCustomNotification,
- sendDiscordNotification,
- sendEmailNotification,
- sendGotifyNotification,
- sendLarkNotification,
- sendNtfyNotification,
- sendSlackNotification,
- sendTelegramNotification,
+ sendCustomNotification,
+ sendDiscordNotification,
+ sendEmailNotification,
+ sendGotifyNotification,
+ sendLarkNotification,
+ sendNtfyNotification,
+ sendSlackNotification,
+ sendTelegramNotification,
} from "./utils";
interface Props {
- projectName: string;
- applicationName: string;
- applicationType: string;
- buildLink: string;
- organizationId: string;
- domains: Domain[];
+ projectName: string;
+ applicationName: string;
+ applicationType: string;
+ buildLink: string;
+ organizationId: string;
+ domains: Domain[];
+ environmentName: string;
}
export const sendBuildSuccessNotifications = async ({
- projectName,
- applicationName,
- applicationType,
- buildLink,
- organizationId,
- domains,
+ projectName,
+ applicationName,
+ applicationType,
+ buildLink,
+ organizationId,
+ domains,
+ environmentName,
}: Props) => {
- const date = new Date();
- const unixDate = ~~(Number(date) / 1000);
- const notificationList = await db.query.notifications.findMany({
- where: and(
- eq(notifications.appDeploy, true),
- eq(notifications.organizationId, organizationId),
- ),
- with: {
- email: true,
- discord: true,
- telegram: true,
- slack: true,
- gotify: true,
- ntfy: true,
- custom: true,
- lark: true,
- },
- });
+ const date = new Date();
+ const unixDate = ~~(Number(date) / 1000);
+ const notificationList = await db.query.notifications.findMany({
+ where: and(
+ eq(notifications.appDeploy, true),
+ eq(notifications.organizationId, organizationId)
+ ),
+ with: {
+ email: true,
+ discord: true,
+ telegram: true,
+ slack: true,
+ gotify: true,
+ ntfy: true,
+ custom: true,
+ lark: true,
+ },
+ });
- for (const notification of notificationList) {
- const { email, discord, telegram, slack, gotify, ntfy, custom, lark } =
- notification;
+ for (const notification of notificationList) {
+ const { email, discord, telegram, slack, gotify, ntfy, custom, lark } =
+ notification;
+ try {
+ if (email) {
+ const template = await renderAsync(
+ BuildSuccessEmail({
+ projectName,
+ applicationName,
+ applicationType,
+ buildLink,
+ date: date.toLocaleString(),
+ environmentName,
+ })
+ ).catch();
+ await sendEmailNotification(
+ email,
+ "Build success for dokploy",
+ template
+ );
+ }
- if (email) {
- const template = await renderAsync(
- BuildSuccessEmail({
- projectName,
- applicationName,
- applicationType,
- buildLink,
- date: date.toLocaleString(),
- }),
- ).catch();
- await sendEmailNotification(email, "Build success for dokploy", template);
- }
+ if (discord) {
+ const decorate = (decoration: string, text: string) =>
+ `${discord.decoration ? decoration : ""} ${text}`.trim();
- if (discord) {
- const decorate = (decoration: string, text: string) =>
- `${discord.decoration ? decoration : ""} ${text}`.trim();
+ await sendDiscordNotification(discord, {
+ title: decorate(">", "`β
` Build Successes"),
+ color: 0x57f287,
+ fields: [
+ {
+ name: decorate("`π οΈ`", "Project"),
+ value: projectName,
+ inline: true,
+ },
+ {
+ name: decorate("`βοΈ`", "Application"),
+ value: applicationName,
+ inline: true,
+ },
+ {
+ name: decorate("`π`", "Environment"),
+ value: environmentName,
+ inline: true,
+ },
+ {
+ name: decorate("`β`", "Type"),
+ value: applicationType,
+ inline: true,
+ },
+ {
+ name: decorate("`π
`", "Date"),
+ value: ``,
+ inline: true,
+ },
+ {
+ name: decorate("`β`", "Time"),
+ value: ``,
+ inline: true,
+ },
+ {
+ name: decorate("`β`", "Type"),
+ value: "Successful",
+ inline: true,
+ },
+ {
+ name: decorate("`π§·`", "Build Link"),
+ value: `[Click here to access build link](${buildLink})`,
+ },
+ ],
+ timestamp: date.toISOString(),
+ footer: {
+ text: "Dokploy Build Notification",
+ },
+ });
+ }
- await sendDiscordNotification(discord, {
- title: decorate(">", "`β
` Build Success"),
- color: 0x57f287,
- fields: [
- {
- name: decorate("`π οΈ`", "Project"),
- value: projectName,
- inline: true,
- },
- {
- name: decorate("`βοΈ`", "Application"),
- value: applicationName,
- inline: true,
- },
- {
- name: decorate("`β`", "Type"),
- value: applicationType,
- inline: true,
- },
- {
- name: decorate("`π
`", "Date"),
- value: ``,
- inline: true,
- },
- {
- name: decorate("`β`", "Time"),
- value: ``,
- inline: true,
- },
- {
- name: decorate("`β`", "Type"),
- value: "Successful",
- inline: true,
- },
- {
- name: decorate("`π§·`", "Build Link"),
- value: `[Click here to access build link](${buildLink})`,
- },
- ],
- timestamp: date.toISOString(),
- footer: {
- text: "Dokploy Build Notification",
- },
- });
- }
+ if (gotify) {
+ const decorate = (decoration: string, text: string) =>
+ `${gotify.decoration ? decoration : ""} ${text}\n`;
+ await sendGotifyNotification(
+ gotify,
+ decorate("β
", "Build Success"),
+ `${decorate("π οΈ", `Project: ${projectName}`)}` +
+ `${decorate("βοΈ", `Application: ${applicationName}`)}` +
+ `${decorate("π", `Environment: ${environmentName}`)}` +
+ `${decorate("β", `Type: ${applicationType}`)}` +
+ `${decorate("π", `Date: ${date.toLocaleString()}`)}` +
+ `${decorate("π", `Build details:\n${buildLink}`)}`
+ );
+ }
- if (gotify) {
- const decorate = (decoration: string, text: string) =>
- `${gotify.decoration ? decoration : ""} ${text}\n`;
- await sendGotifyNotification(
- gotify,
- decorate("β
", "Build Success"),
- `${decorate("π οΈ", `Project: ${projectName}`)}` +
- `${decorate("βοΈ", `Application: ${applicationName}`)}` +
- `${decorate("β", `Type: ${applicationType}`)}` +
- `${decorate("π", `Date: ${date.toLocaleString()}`)}` +
- `${decorate("π", `Build details:\n${buildLink}`)}`,
- );
- }
+ if (ntfy) {
+ await sendNtfyNotification(
+ ntfy,
+ "Build Success",
+ "white_check_mark",
+ `view, Build details, ${buildLink}, clear=true;`,
+ `π Project: ${projectName}\n` +
+ `βοΈApplication: ${applicationName}\n` +
+ `πEnvironment: ${environmentName}\n` +
+ `βType: ${applicationType}\n` +
+ `πDate: ${date.toLocaleString()}`
+ );
+ }
- if (ntfy) {
- await sendNtfyNotification(
- ntfy,
- "Build Success",
- "white_check_mark",
- `view, Build details, ${buildLink}, clear=true;`,
- `π Project: ${projectName}\n` +
- `βοΈApplication: ${applicationName}\n` +
- `βType: ${applicationType}\n` +
- `πDate: ${date.toLocaleString()}`,
- );
- }
+ if (telegram) {
+ const chunkArray = (array: T[], chunkSize: number): T[][] =>
+ Array.from({ length: Math.ceil(array.length / chunkSize) }, (_, i) =>
+ array.slice(i * chunkSize, i * chunkSize + chunkSize)
+ );
- if (telegram) {
- const chunkArray = (array: T[], chunkSize: number): T[][] =>
- Array.from({ length: Math.ceil(array.length / chunkSize) }, (_, i) =>
- array.slice(i * chunkSize, i * chunkSize + chunkSize),
- );
+ const inlineButton = [
+ [
+ {
+ text: "Deployment Logs",
+ url: buildLink,
+ },
+ ],
+ ...chunkArray(domains, 2).map((chunk) =>
+ chunk.map((data) => ({
+ text: data.host,
+ url: `${data.https ? "https" : "http"}://${data.host}`,
+ }))
+ ),
+ ];
- const inlineButton = [
- [
- {
- text: "Deployment Logs",
- url: buildLink,
- },
- ],
- ...chunkArray(domains, 2).map((chunk) =>
- chunk.map((data) => ({
- text: data.host,
- url: `${data.https ? "https" : "http"}://${data.host}`,
- })),
- ),
- ];
+ await sendTelegramNotification(
+ telegram,
+ `β
Build Success\n\nProject: ${projectName}\nApplication: ${applicationName}\nEnvironment: ${environmentName}\nType: ${applicationType}\nDate: ${format(
+ date,
+ "PP"
+ )}\nTime: ${format(date, "pp")}`,
+ inlineButton
+ );
+ }
- await sendTelegramNotification(
- telegram,
- `β
Build Success\n\nProject: ${projectName}\nApplication: ${applicationName}\nType: ${applicationType}\nDate: ${format(
- date,
- "PP",
- )}\nTime: ${format(date, "pp")}`,
- inlineButton,
- );
- }
+ if (slack) {
+ const { channel } = slack;
+ await sendSlackNotification(slack, {
+ channel: channel,
+ attachments: [
+ {
+ color: "#00FF00",
+ pretext: ":white_check_mark: *Build Success*",
+ fields: [
+ {
+ title: "Project",
+ value: projectName,
+ short: true,
+ },
+ {
+ title: "Application",
+ value: applicationName,
+ short: true,
+ },
+ {
+ title: "Environment",
+ value: environmentName,
+ short: true,
+ },
+ {
+ title: "Type",
+ value: applicationType,
+ short: true,
+ },
+ {
+ title: "Time",
+ value: date.toLocaleString(),
+ short: true,
+ },
+ ],
+ actions: [
+ {
+ type: "button",
+ text: "View Build Details",
+ url: buildLink,
+ },
+ ],
+ },
+ ],
+ });
+ }
- if (slack) {
- const { channel } = slack;
- await sendSlackNotification(slack, {
- channel: channel,
- attachments: [
- {
- color: "#00FF00",
- pretext: ":white_check_mark: *Build Success*",
- fields: [
- {
- title: "Project",
- value: projectName,
- short: true,
- },
- {
- title: "Application",
- value: applicationName,
- short: true,
- },
- {
- title: "Type",
- value: applicationType,
- short: true,
- },
- {
- title: "Time",
- value: date.toLocaleString(),
- short: true,
- },
- ],
- actions: [
- {
- type: "button",
- text: "View Build Details",
- url: buildLink,
- },
- ],
- },
- ],
- });
- }
+ if (custom) {
+ await sendCustomNotification(custom, {
+ title: "Build Success",
+ message: "Build completed successfully",
+ projectName,
+ applicationName,
+ applicationType,
+ buildLink,
+ timestamp: date.toISOString(),
+ date: date.toLocaleString(),
+ domains: domains.map((domain) => domain.host).join(", "),
+ status: "success",
+ type: "build",
+ });
+ }
- if (custom) {
- await sendCustomNotification(custom, {
- title: "Build Success",
- message: "Build completed successfully",
- projectName,
- applicationName,
- applicationType,
- buildLink,
- timestamp: date.toISOString(),
- date: date.toLocaleString(),
- domains: domains.map((domain) => domain.host).join(", "),
- status: "success",
- type: "build",
- });
- }
-
- if (lark) {
- await sendLarkNotification(lark, {
- msg_type: "interactive",
- card: {
- schema: "2.0",
- config: {
- update_multi: true,
- style: {
- text_size: {
- normal_v2: {
- default: "normal",
- pc: "normal",
- mobile: "heading",
- },
- },
- },
- },
- header: {
- title: {
- tag: "plain_text",
- content: "β
Build Success",
- },
- subtitle: {
- tag: "plain_text",
- content: "",
- },
- template: "green",
- padding: "12px 12px 12px 12px",
- },
- body: {
- direction: "vertical",
- padding: "12px 12px 12px 12px",
- elements: [
- {
- tag: "column_set",
- columns: [
- {
- tag: "column",
- width: "weighted",
- elements: [
- {
- tag: "markdown",
- content: `**Project:**\n${projectName}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- {
- tag: "markdown",
- content: `**Type:**\n${applicationType}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- ],
- vertical_align: "top",
- weight: 1,
- },
- {
- tag: "column",
- width: "weighted",
- elements: [
- {
- tag: "markdown",
- content: `**Application:**\n${applicationName}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- {
- tag: "markdown",
- content: `**Date:**\n${format(date, "PP pp")}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- ],
- vertical_align: "top",
- weight: 1,
- },
- ],
- },
- {
- tag: "button",
- text: {
- tag: "plain_text",
- content: "View Build Details",
- },
- type: "primary",
- width: "default",
- size: "medium",
- behaviors: [
- {
- type: "open_url",
- default_url: buildLink,
- pc_url: "",
- ios_url: "",
- android_url: "",
- },
- ],
- margin: "0px 0px 0px 0px",
- },
- ],
- },
- },
- });
- }
- }
+ if (lark) {
+ await sendLarkNotification(lark, {
+ msg_type: "interactive",
+ card: {
+ schema: "2.0",
+ config: {
+ update_multi: true,
+ style: {
+ text_size: {
+ normal_v2: {
+ default: "normal",
+ pc: "normal",
+ mobile: "heading",
+ },
+ },
+ },
+ },
+ header: {
+ title: {
+ tag: "plain_text",
+ content: "β
Build Success",
+ },
+ subtitle: {
+ tag: "plain_text",
+ content: "",
+ },
+ template: "green",
+ padding: "12px 12px 12px 12px",
+ },
+ body: {
+ direction: "vertical",
+ padding: "12px 12px 12px 12px",
+ elements: [
+ {
+ tag: "column_set",
+ columns: [
+ {
+ tag: "column",
+ width: "weighted",
+ elements: [
+ {
+ tag: "markdown",
+ content: `**Project:**\n${projectName}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ {
+ tag: "markdown",
+ content: `**Environment:**\n${environmentName}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ {
+ tag: "markdown",
+ content: `**Type:**\n${applicationType}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ ],
+ vertical_align: "top",
+ weight: 1,
+ },
+ {
+ tag: "column",
+ width: "weighted",
+ elements: [
+ {
+ tag: "markdown",
+ content: `**Application:**\n${applicationName}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ {
+ tag: "markdown",
+ content: `**Date:**\n${format(date, "PP pp")}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ ],
+ vertical_align: "top",
+ weight: 1,
+ },
+ ],
+ },
+ {
+ tag: "button",
+ text: {
+ tag: "plain_text",
+ content: "View Build Details",
+ },
+ type: "primary",
+ width: "default",
+ size: "medium",
+ behaviors: [
+ {
+ type: "open_url",
+ default_url: buildLink,
+ pc_url: "",
+ ios_url: "",
+ android_url: "",
+ },
+ ],
+ margin: "0px 0px 0px 0px",
+ },
+ ],
+ },
+ },
+ });
+ }
+ } catch (error) {
+ console.log(error);
+ }
+ }
};
diff --git a/packages/server/src/utils/notifications/database-backup.ts b/packages/server/src/utils/notifications/database-backup.ts
index fa4e3b253..e0754b715 100644
--- a/packages/server/src/utils/notifications/database-backup.ts
+++ b/packages/server/src/utils/notifications/database-backup.ts
@@ -54,328 +54,331 @@ export const sendDatabaseBackupNotifications = async ({
for (const notification of notificationList) {
const { email, discord, telegram, slack, gotify, ntfy, custom, lark } =
notification;
+ try {
+ if (email) {
+ const template = await renderAsync(
+ DatabaseBackupEmail({
+ projectName,
+ applicationName,
+ databaseType,
+ type,
+ errorMessage,
+ date: date.toLocaleString(),
+ }),
+ ).catch();
+ await sendEmailNotification(
+ email,
+ "Database backup for dokploy",
+ template,
+ );
+ }
- if (email) {
- const template = await renderAsync(
- DatabaseBackupEmail({
+ if (discord) {
+ const decorate = (decoration: string, text: string) =>
+ `${discord.decoration ? decoration : ""} ${text}`.trim();
+
+ await sendDiscordNotification(discord, {
+ title:
+ type === "success"
+ ? decorate(">", "`β
` Database Backup Successful")
+ : decorate(">", "`β` Database Backup Failed"),
+ color: type === "success" ? 0x57f287 : 0xed4245,
+ fields: [
+ {
+ name: decorate("`π οΈ`", "Project"),
+ value: projectName,
+ inline: true,
+ },
+ {
+ name: decorate("`βοΈ`", "Application"),
+ value: applicationName,
+ inline: true,
+ },
+ {
+ name: decorate("`β`", "Database"),
+ value: databaseType,
+ inline: true,
+ },
+ {
+ name: decorate("`π`", "Database Name"),
+ value: databaseName,
+ inline: true,
+ },
+ {
+ name: decorate("`π
`", "Date"),
+ value: ``,
+ inline: true,
+ },
+ {
+ name: decorate("`β`", "Time"),
+ value: ``,
+ inline: true,
+ },
+ {
+ name: decorate("`β`", "Type"),
+ value: type
+ .replace("error", "Failed")
+ .replace("success", "Successful"),
+ inline: true,
+ },
+ ...(type === "error" && errorMessage
+ ? [
+ {
+ name: decorate("`β οΈ`", "Error Message"),
+ value: `\`\`\`${errorMessage}\`\`\``,
+ },
+ ]
+ : []),
+ ],
+ timestamp: date.toISOString(),
+ footer: {
+ text: "Dokploy Database Backup Notification",
+ },
+ });
+ }
+
+ if (gotify) {
+ const decorate = (decoration: string, text: string) =>
+ `${gotify.decoration ? decoration : ""} ${text}\n`;
+
+ await sendGotifyNotification(
+ gotify,
+ decorate(
+ type === "success" ? "β
" : "β",
+ `Database Backup ${type === "success" ? "Successful" : "Failed"}`,
+ ),
+ `${decorate("π οΈ", `Project: ${projectName}`)}` +
+ `${decorate("βοΈ", `Application: ${applicationName}`)}` +
+ `${decorate("β", `Type: ${databaseType}`)}` +
+ `${decorate("π", `Database Name: ${databaseName}`)}` +
+ `${decorate("π", `Date: ${date.toLocaleString()}`)}` +
+ `${type === "error" && errorMessage ? decorate("β", `Error:\n${errorMessage}`) : ""}`,
+ );
+ }
+
+ if (ntfy) {
+ await sendNtfyNotification(
+ ntfy,
+ `Database Backup ${type === "success" ? "Successful" : "Failed"}`,
+ `${type === "success" ? "white_check_mark" : "x"}`,
+ "",
+ `π Project: ${projectName}\n` +
+ `βοΈApplication: ${applicationName}\n` +
+ `βType: ${databaseType}\n` +
+ `πDatabase Name: ${databaseName}` +
+ `πDate: ${date.toLocaleString()}\n` +
+ `${type === "error" && errorMessage ? `βError:\n${errorMessage}` : ""}`,
+ );
+ }
+
+ if (telegram) {
+ const isError = type === "error" && errorMessage;
+
+ const statusEmoji = type === "success" ? "β
" : "β";
+ const typeStatus = type === "success" ? "Successful" : "Failed";
+ const errorMsg = isError
+ ? `\n\nError:\n${errorMessage}`
+ : "";
+
+ const messageText = `${statusEmoji} Database Backup ${typeStatus}\n\nProject: ${projectName}\nApplication: ${applicationName}\nType: ${databaseType}\nDatabase Name: ${databaseName}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}${isError ? errorMsg : ""}`;
+
+ await sendTelegramNotification(telegram, messageText);
+ }
+
+ if (slack) {
+ const { channel } = slack;
+ await sendSlackNotification(slack, {
+ channel: channel,
+ attachments: [
+ {
+ color: type === "success" ? "#00FF00" : "#FF0000",
+ pretext:
+ type === "success"
+ ? ":white_check_mark: *Database Backup Successful*"
+ : ":x: *Database Backup Failed*",
+ fields: [
+ ...(type === "error" && errorMessage
+ ? [
+ {
+ title: "Error Message",
+ value: errorMessage,
+ short: false,
+ },
+ ]
+ : []),
+ {
+ title: "Project",
+ value: projectName,
+ short: true,
+ },
+ {
+ title: "Application",
+ value: applicationName,
+ short: true,
+ },
+ {
+ title: "Type",
+ value: databaseType,
+ short: true,
+ },
+ {
+ title: "Database Name",
+ value: databaseName,
+ },
+ {
+ title: "Time",
+ value: date.toLocaleString(),
+ short: true,
+ },
+ {
+ title: "Type",
+ value: type,
+ },
+ {
+ title: "Status",
+ value: type === "success" ? "Successful" : "Failed",
+ },
+ ],
+ },
+ ],
+ });
+ }
+
+ if (custom) {
+ await sendCustomNotification(custom, {
+ title: `Database Backup ${type === "success" ? "Successful" : "Failed"}`,
+ message:
+ type === "success"
+ ? "Database backup completed successfully"
+ : "Database backup failed",
projectName,
applicationName,
databaseType,
+ databaseName,
type,
- errorMessage,
+ errorMessage: errorMessage || "",
+ timestamp: date.toISOString(),
date: date.toLocaleString(),
- }),
- ).catch();
- await sendEmailNotification(
- email,
- "Database backup for dokploy",
- template,
- );
- }
+ status: type,
+ });
+ }
- if (discord) {
- const decorate = (decoration: string, text: string) =>
- `${discord.decoration ? decoration : ""} ${text}`.trim();
+ if (lark) {
+ const limitCharacter = 800;
+ const truncatedErrorMessage =
+ errorMessage && errorMessage.length > limitCharacter
+ ? errorMessage.substring(0, limitCharacter)
+ : errorMessage;
- await sendDiscordNotification(discord, {
- title:
- type === "success"
- ? decorate(">", "`β
` Database Backup Successful")
- : decorate(">", "`β` Database Backup Failed"),
- color: type === "success" ? 0x57f287 : 0xed4245,
- fields: [
- {
- name: decorate("`π οΈ`", "Project"),
- value: projectName,
- inline: true,
- },
- {
- name: decorate("`βοΈ`", "Application"),
- value: applicationName,
- inline: true,
- },
- {
- name: decorate("`β`", "Database"),
- value: databaseType,
- inline: true,
- },
- {
- name: decorate("`π`", "Database Name"),
- value: databaseName,
- inline: true,
- },
- {
- name: decorate("`π
`", "Date"),
- value: ``,
- inline: true,
- },
- {
- name: decorate("`β`", "Time"),
- value: ``,
- inline: true,
- },
- {
- name: decorate("`β`", "Type"),
- value: type
- .replace("error", "Failed")
- .replace("success", "Successful"),
- inline: true,
- },
- ...(type === "error" && errorMessage
- ? [
+ await sendLarkNotification(lark, {
+ msg_type: "interactive",
+ card: {
+ schema: "2.0",
+ config: {
+ update_multi: true,
+ style: {
+ text_size: {
+ normal_v2: {
+ default: "normal",
+ pc: "normal",
+ mobile: "heading",
+ },
+ },
+ },
+ },
+ header: {
+ title: {
+ tag: "plain_text",
+ content:
+ type === "success"
+ ? "β
Database Backup Successful"
+ : "β Database Backup Failed",
+ },
+ subtitle: {
+ tag: "plain_text",
+ content: "",
+ },
+ template: type === "success" ? "green" : "red",
+ padding: "12px 12px 12px 12px",
+ },
+ body: {
+ direction: "vertical",
+ padding: "12px 12px 12px 12px",
+ elements: [
{
- name: decorate("`β οΈ`", "Error Message"),
- value: `\`\`\`${errorMessage}\`\`\``,
- },
- ]
- : []),
- ],
- timestamp: date.toISOString(),
- footer: {
- text: "Dokploy Database Backup Notification",
- },
- });
- }
-
- if (gotify) {
- const decorate = (decoration: string, text: string) =>
- `${gotify.decoration ? decoration : ""} ${text}\n`;
-
- await sendGotifyNotification(
- gotify,
- decorate(
- type === "success" ? "β
" : "β",
- `Database Backup ${type === "success" ? "Successful" : "Failed"}`,
- ),
- `${decorate("π οΈ", `Project: ${projectName}`)}` +
- `${decorate("βοΈ", `Application: ${applicationName}`)}` +
- `${decorate("β", `Type: ${databaseType}`)}` +
- `${decorate("π", `Database Name: ${databaseName}`)}` +
- `${decorate("π", `Date: ${date.toLocaleString()}`)}` +
- `${type === "error" && errorMessage ? decorate("β", `Error:\n${errorMessage}`) : ""}`,
- );
- }
-
- if (ntfy) {
- await sendNtfyNotification(
- ntfy,
- `Database Backup ${type === "success" ? "Successful" : "Failed"}`,
- `${type === "success" ? "white_check_mark" : "x"}`,
- "",
- `π Project: ${projectName}\n` +
- `βοΈApplication: ${applicationName}\n` +
- `βType: ${databaseType}\n` +
- `πDatabase Name: ${databaseName}` +
- `πDate: ${date.toLocaleString()}\n` +
- `${type === "error" && errorMessage ? `βError:\n${errorMessage}` : ""}`,
- );
- }
-
- if (telegram) {
- const isError = type === "error" && errorMessage;
-
- const statusEmoji = type === "success" ? "β
" : "β";
- const typeStatus = type === "success" ? "Successful" : "Failed";
- const errorMsg = isError
- ? `\n\nError:\n${errorMessage}`
- : "";
-
- const messageText = `${statusEmoji} Database Backup ${typeStatus}\n\nProject: ${projectName}\nApplication: ${applicationName}\nType: ${databaseType}\nDatabase Name: ${databaseName}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}${isError ? errorMsg : ""}`;
-
- await sendTelegramNotification(telegram, messageText);
- }
-
- if (slack) {
- const { channel } = slack;
- await sendSlackNotification(slack, {
- channel: channel,
- attachments: [
- {
- color: type === "success" ? "#00FF00" : "#FF0000",
- pretext:
- type === "success"
- ? ":white_check_mark: *Database Backup Successful*"
- : ":x: *Database Backup Failed*",
- fields: [
- ...(type === "error" && errorMessage
- ? [
+ tag: "column_set",
+ columns: [
{
- title: "Error Message",
- value: errorMessage,
- short: false,
+ tag: "column",
+ width: "weighted",
+ elements: [
+ {
+ tag: "markdown",
+ content: `**Project:**\n${projectName}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ {
+ tag: "markdown",
+ content: `**Database Type:**\n${databaseType}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ {
+ tag: "markdown",
+ content: `**Status:**\n${type === "success" ? "Successful" : "Failed"}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ ],
+ vertical_align: "top",
+ weight: 1,
},
- ]
- : []),
- {
- title: "Project",
- value: projectName,
- short: true,
- },
- {
- title: "Application",
- value: applicationName,
- short: true,
- },
- {
- title: "Type",
- value: databaseType,
- short: true,
- },
- {
- title: "Database Name",
- value: databaseName,
- },
- {
- title: "Time",
- value: date.toLocaleString(),
- short: true,
- },
- {
- title: "Type",
- value: type,
- },
- {
- title: "Status",
- value: type === "success" ? "Successful" : "Failed",
- },
- ],
- },
- ],
- });
- }
-
- if (custom) {
- await sendCustomNotification(custom, {
- title: `Database Backup ${type === "success" ? "Successful" : "Failed"}`,
- message:
- type === "success"
- ? "Database backup completed successfully"
- : "Database backup failed",
- projectName,
- applicationName,
- databaseType,
- databaseName,
- type,
- errorMessage: errorMessage || "",
- timestamp: date.toISOString(),
- date: date.toLocaleString(),
- status: type,
- });
- }
-
- if (lark) {
- const limitCharacter = 800;
- const truncatedErrorMessage =
- errorMessage && errorMessage.length > limitCharacter
- ? errorMessage.substring(0, limitCharacter)
- : errorMessage;
-
- await sendLarkNotification(lark, {
- msg_type: "interactive",
- card: {
- schema: "2.0",
- config: {
- update_multi: true,
- style: {
- text_size: {
- normal_v2: {
- default: "normal",
- pc: "normal",
- mobile: "heading",
- },
- },
- },
- },
- header: {
- title: {
- tag: "plain_text",
- content:
- type === "success"
- ? "β
Database Backup Successful"
- : "β Database Backup Failed",
- },
- subtitle: {
- tag: "plain_text",
- content: "",
- },
- template: type === "success" ? "green" : "red",
- padding: "12px 12px 12px 12px",
- },
- body: {
- direction: "vertical",
- padding: "12px 12px 12px 12px",
- elements: [
- {
- tag: "column_set",
- columns: [
- {
- tag: "column",
- width: "weighted",
- elements: [
- {
- tag: "markdown",
- content: `**Project:**\n${projectName}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- {
- tag: "markdown",
- content: `**Database Type:**\n${databaseType}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- {
- tag: "markdown",
- content: `**Status:**\n${type === "success" ? "Successful" : "Failed"}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- ],
- vertical_align: "top",
- weight: 1,
- },
- {
- tag: "column",
- width: "weighted",
- elements: [
- {
- tag: "markdown",
- content: `**Application:**\n${applicationName}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- {
- tag: "markdown",
- content: `**Database Name:**\n${databaseName}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- {
- tag: "markdown",
- content: `**Date:**\n${format(date, "PP pp")}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- ],
- vertical_align: "top",
- weight: 1,
- },
- ],
- },
- ...(type === "error" && truncatedErrorMessage
- ? [
{
- tag: "markdown",
- content: `**Error Message:**\n\`\`\`\n${truncatedErrorMessage}\n\`\`\``,
- text_align: "left",
- text_size: "normal_v2",
+ tag: "column",
+ width: "weighted",
+ elements: [
+ {
+ tag: "markdown",
+ content: `**Application:**\n${applicationName}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ {
+ tag: "markdown",
+ content: `**Database Name:**\n${databaseName}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ {
+ tag: "markdown",
+ content: `**Date:**\n${format(date, "PP pp")}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ ],
+ vertical_align: "top",
+ weight: 1,
},
- ]
- : []),
- ],
+ ],
+ },
+ ...(type === "error" && truncatedErrorMessage
+ ? [
+ {
+ tag: "markdown",
+ content: `**Error Message:**\n\`\`\`\n${truncatedErrorMessage}\n\`\`\``,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ ]
+ : []),
+ ],
+ },
},
- },
- });
+ });
+ }
+ } catch (error) {
+ console.log(error);
}
}
};
diff --git a/packages/server/src/utils/notifications/docker-cleanup.ts b/packages/server/src/utils/notifications/docker-cleanup.ts
index cd25bdd4c..061f892ff 100644
--- a/packages/server/src/utils/notifications/docker-cleanup.ts
+++ b/packages/server/src/utils/notifications/docker-cleanup.ts
@@ -41,194 +41,197 @@ export const sendDockerCleanupNotifications = async (
for (const notification of notificationList) {
const { email, discord, telegram, slack, gotify, ntfy, custom, lark } =
notification;
+ try {
+ if (email) {
+ const template = await renderAsync(
+ DockerCleanupEmail({ message, date: date.toLocaleString() }),
+ ).catch();
- if (email) {
- const template = await renderAsync(
- DockerCleanupEmail({ message, date: date.toLocaleString() }),
- ).catch();
+ await sendEmailNotification(
+ email,
+ "Docker cleanup for dokploy",
+ template,
+ );
+ }
- await sendEmailNotification(
- email,
- "Docker cleanup for dokploy",
- template,
- );
- }
+ if (discord) {
+ const decorate = (decoration: string, text: string) =>
+ `${discord.decoration ? decoration : ""} ${text}`.trim();
- if (discord) {
- const decorate = (decoration: string, text: string) =>
- `${discord.decoration ? decoration : ""} ${text}`.trim();
-
- await sendDiscordNotification(discord, {
- title: decorate(">", "`β
` Docker Cleanup"),
- color: 0x57f287,
- fields: [
- {
- name: decorate("`π
`", "Date"),
- value: ``,
- inline: true,
+ await sendDiscordNotification(discord, {
+ title: decorate(">", "`β
` Docker Cleanup"),
+ color: 0x57f287,
+ fields: [
+ {
+ name: decorate("`π
`", "Date"),
+ value: ``,
+ inline: true,
+ },
+ {
+ name: decorate("`β`", "Time"),
+ value: ``,
+ inline: true,
+ },
+ {
+ name: decorate("`β`", "Type"),
+ value: "Successful",
+ inline: true,
+ },
+ {
+ name: decorate("`π`", "Message"),
+ value: `\`\`\`${message}\`\`\``,
+ },
+ ],
+ timestamp: date.toISOString(),
+ footer: {
+ text: "Dokploy Docker Cleanup Notification",
},
- {
- name: decorate("`β`", "Time"),
- value: ``,
- inline: true,
- },
- {
- name: decorate("`β`", "Type"),
- value: "Successful",
- inline: true,
- },
- {
- name: decorate("`π`", "Message"),
- value: `\`\`\`${message}\`\`\``,
- },
- ],
- timestamp: date.toISOString(),
- footer: {
- text: "Dokploy Docker Cleanup Notification",
- },
- });
- }
+ });
+ }
- if (gotify) {
- const decorate = (decoration: string, text: string) =>
- `${gotify.decoration ? decoration : ""} ${text}\n`;
- await sendGotifyNotification(
- gotify,
- decorate("β
", "Docker Cleanup"),
- `${decorate("π", `Date: ${date.toLocaleString()}`)}` +
- `${decorate("π", `Message:\n${message}`)}`,
- );
- }
+ if (gotify) {
+ const decorate = (decoration: string, text: string) =>
+ `${gotify.decoration ? decoration : ""} ${text}\n`;
+ await sendGotifyNotification(
+ gotify,
+ decorate("β
", "Docker Cleanup"),
+ `${decorate("π", `Date: ${date.toLocaleString()}`)}` +
+ `${decorate("π", `Message:\n${message}`)}`,
+ );
+ }
- if (ntfy) {
- await sendNtfyNotification(
- ntfy,
- "Docker Cleanup",
- "white_check_mark",
- "",
- `πDate: ${date.toLocaleString()}\n` + `πMessage:\n${message}`,
- );
- }
+ if (ntfy) {
+ await sendNtfyNotification(
+ ntfy,
+ "Docker Cleanup",
+ "white_check_mark",
+ "",
+ `πDate: ${date.toLocaleString()}\n` + `πMessage:\n${message}`,
+ );
+ }
- if (telegram) {
- await sendTelegramNotification(
- telegram,
- `β
Docker Cleanup\n\nMessage: ${message}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}`,
- );
- }
+ if (telegram) {
+ await sendTelegramNotification(
+ telegram,
+ `β
Docker Cleanup\n\nMessage: ${message}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}`,
+ );
+ }
- if (slack) {
- const { channel } = slack;
- await sendSlackNotification(slack, {
- channel: channel,
- attachments: [
- {
- color: "#00FF00",
- pretext: ":white_check_mark: *Docker Cleanup*",
- fields: [
- {
- title: "Message",
- value: message,
- },
- {
- title: "Time",
- value: date.toLocaleString(),
- short: true,
- },
- ],
- },
- ],
- });
- }
+ if (slack) {
+ const { channel } = slack;
+ await sendSlackNotification(slack, {
+ channel: channel,
+ attachments: [
+ {
+ color: "#00FF00",
+ pretext: ":white_check_mark: *Docker Cleanup*",
+ fields: [
+ {
+ title: "Message",
+ value: message,
+ },
+ {
+ title: "Time",
+ value: date.toLocaleString(),
+ short: true,
+ },
+ ],
+ },
+ ],
+ });
+ }
- if (custom) {
- await sendCustomNotification(custom, {
- title: "Docker Cleanup",
- message: "Docker cleanup completed successfully",
- cleanupMessage: message,
- timestamp: date.toISOString(),
- date: date.toLocaleString(),
- status: "success",
- type: "docker-cleanup",
- });
- }
+ if (custom) {
+ await sendCustomNotification(custom, {
+ title: "Docker Cleanup",
+ message: "Docker cleanup completed successfully",
+ cleanupMessage: message,
+ timestamp: date.toISOString(),
+ date: date.toLocaleString(),
+ status: "success",
+ type: "docker-cleanup",
+ });
+ }
- if (lark) {
- await sendLarkNotification(lark, {
- msg_type: "interactive",
- card: {
- schema: "2.0",
- config: {
- update_multi: true,
- style: {
- text_size: {
- normal_v2: {
- default: "normal",
- pc: "normal",
- mobile: "heading",
+ if (lark) {
+ await sendLarkNotification(lark, {
+ msg_type: "interactive",
+ card: {
+ schema: "2.0",
+ config: {
+ update_multi: true,
+ style: {
+ text_size: {
+ normal_v2: {
+ default: "normal",
+ pc: "normal",
+ mobile: "heading",
+ },
},
},
},
- },
- header: {
- title: {
- tag: "plain_text",
- content: "β
Docker Cleanup",
- },
- subtitle: {
- tag: "plain_text",
- content: "",
- },
- template: "green",
- padding: "12px 12px 12px 12px",
- },
- body: {
- direction: "vertical",
- padding: "12px 12px 12px 12px",
- elements: [
- {
- tag: "column_set",
- columns: [
- {
- tag: "column",
- width: "weighted",
- elements: [
- {
- tag: "markdown",
- content: "**Status:**\nSuccessful",
- text_align: "left",
- text_size: "normal_v2",
- },
- {
- tag: "markdown",
- content: `**Cleanup Details:**\n${message}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- ],
- vertical_align: "top",
- weight: 1,
- },
- {
- tag: "column",
- width: "weighted",
- elements: [
- {
- tag: "markdown",
- content: `**Date:**\n${format(date, "PP pp")}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- ],
- vertical_align: "top",
- weight: 1,
- },
- ],
+ header: {
+ title: {
+ tag: "plain_text",
+ content: "β
Docker Cleanup",
},
- ],
+ subtitle: {
+ tag: "plain_text",
+ content: "",
+ },
+ template: "green",
+ padding: "12px 12px 12px 12px",
+ },
+ body: {
+ direction: "vertical",
+ padding: "12px 12px 12px 12px",
+ elements: [
+ {
+ tag: "column_set",
+ columns: [
+ {
+ tag: "column",
+ width: "weighted",
+ elements: [
+ {
+ tag: "markdown",
+ content: "**Status:**\nSuccessful",
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ {
+ tag: "markdown",
+ content: `**Cleanup Details:**\n${message}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ ],
+ vertical_align: "top",
+ weight: 1,
+ },
+ {
+ tag: "column",
+ width: "weighted",
+ elements: [
+ {
+ tag: "markdown",
+ content: `**Date:**\n${format(date, "PP pp")}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ ],
+ vertical_align: "top",
+ weight: 1,
+ },
+ ],
+ },
+ ],
+ },
},
- },
- });
+ });
+ }
+ } catch (error) {
+ console.log(error);
}
}
};
diff --git a/packages/server/src/utils/notifications/dokploy-restart.ts b/packages/server/src/utils/notifications/dokploy-restart.ts
index 97ea3cbc3..edc83c513 100644
--- a/packages/server/src/utils/notifications/dokploy-restart.ts
+++ b/packages/server/src/utils/notifications/dokploy-restart.ts
@@ -5,231 +5,222 @@ import { renderAsync } from "@react-email/components";
import { format } from "date-fns";
import { eq } from "drizzle-orm";
import {
- sendCustomNotification,
- sendDiscordNotification,
- sendEmailNotification,
- sendGotifyNotification,
- sendLarkNotification,
- sendNtfyNotification,
- sendSlackNotification,
- sendTelegramNotification,
+ sendCustomNotification,
+ sendDiscordNotification,
+ sendEmailNotification,
+ sendGotifyNotification,
+ sendLarkNotification,
+ sendNtfyNotification,
+ sendSlackNotification,
+ sendTelegramNotification,
} from "./utils";
export const sendDokployRestartNotifications = async () => {
- const date = new Date();
- const unixDate = ~~(Number(date) / 1000);
- const notificationList = await db.query.notifications.findMany({
- where: eq(notifications.dokployRestart, true),
- with: {
- email: true,
- discord: true,
- telegram: true,
- slack: true,
- gotify: true,
- ntfy: true,
- custom: true,
- lark: true,
- },
- });
+ const date = new Date();
+ const unixDate = ~~(Number(date) / 1000);
+ const notificationList = await db.query.notifications.findMany({
+ where: eq(notifications.dokployRestart, true),
+ with: {
+ email: true,
+ discord: true,
+ telegram: true,
+ slack: true,
+ gotify: true,
+ ntfy: true,
+ custom: true,
+ lark: true,
+ },
+ });
- for (const notification of notificationList) {
- const { email, discord, telegram, slack, gotify, ntfy, custom, lark } =
- notification;
+ for (const notification of notificationList) {
+ const { email, discord, telegram, slack, gotify, ntfy, custom, lark } =
+ notification;
- if (email) {
- const template = await renderAsync(
- DokployRestartEmail({ date: date.toLocaleString() }),
- ).catch();
- await sendEmailNotification(email, "Dokploy Server Restarted", template);
- }
+ try {
+ if (email) {
+ const template = await renderAsync(
+ DokployRestartEmail({ date: date.toLocaleString() })
+ ).catch();
- if (discord) {
- const decorate = (decoration: string, text: string) =>
- `${discord.decoration ? decoration : ""} ${text}`.trim();
+ await sendEmailNotification(
+ email,
+ "Dokploy Server Restarted",
+ template
+ );
+ }
- try {
- await sendDiscordNotification(discord, {
- title: decorate(">", "`β
` Dokploy Server Restarted"),
- color: 0x57f287,
- fields: [
- {
- name: decorate("`π
`", "Date"),
- value: ``,
- inline: true,
- },
- {
- name: decorate("`β`", "Time"),
- value: ``,
- inline: true,
- },
- {
- name: decorate("`β`", "Type"),
- value: "Successful",
- inline: true,
- },
- ],
- timestamp: date.toISOString(),
- footer: {
- text: "Dokploy Restart Notification",
- },
- });
- } catch (error) {
- console.log(error);
- }
- }
+ if (discord) {
+ const decorate = (decoration: string, text: string) =>
+ `${discord.decoration ? decoration : ""} ${text}`.trim();
- if (gotify) {
- const decorate = (decoration: string, text: string) =>
- `${gotify.decoration ? decoration : ""} ${text}\n`;
- try {
- await sendGotifyNotification(
- gotify,
- decorate("β
", "Dokploy Server Restarted"),
- `${decorate("π", `Date: ${date.toLocaleString()}`)}`,
- );
- } catch (error) {
- console.log(error);
- }
- }
+ await sendDiscordNotification(discord, {
+ title: decorate(">", "`β
` Dokploy Server Restarted"),
+ color: 0x57f287,
+ fields: [
+ {
+ name: decorate("`π
`", "Date"),
+ value: ``,
+ inline: true,
+ },
+ {
+ name: decorate("`β`", "Time"),
+ value: ``,
+ inline: true,
+ },
+ {
+ name: decorate("`β`", "Type"),
+ value: "Successful",
+ inline: true,
+ },
+ ],
+ timestamp: date.toISOString(),
+ footer: {
+ text: "Dokploy Restart Notification",
+ },
+ });
+ }
- if (ntfy) {
- try {
- await sendNtfyNotification(
- ntfy,
- "Dokploy Server Restarted",
- "white_check_mark",
- "",
- `πDate: ${date.toLocaleString()}`,
- );
- } catch (error) {
- console.log(error);
- }
- }
+ if (gotify) {
+ const decorate = (decoration: string, text: string) =>
+ `${gotify.decoration ? decoration : ""} ${text}\n`;
+ await sendGotifyNotification(
+ gotify,
+ decorate("β
", "Dokploy Server Restarted"),
+ `${decorate("π", `Date: ${date.toLocaleString()}`)}`
+ );
+ }
- if (telegram) {
- try {
- await sendTelegramNotification(
- telegram,
- `β
Dokploy Server Restarted\n\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}`,
- );
- } catch (error) {
- console.log(error);
- }
- }
+ if (ntfy) {
+ await sendNtfyNotification(
+ ntfy,
+ "Dokploy Server Restarted",
+ "white_check_mark",
+ "",
+ `πDate: ${date.toLocaleString()}`
+ );
+ }
- if (slack) {
- const { channel } = slack;
- try {
- await sendSlackNotification(slack, {
- channel: channel,
- attachments: [
- {
- color: "#00FF00",
- pretext: ":white_check_mark: *Dokploy Server Restarted*",
- fields: [
- {
- title: "Time",
- value: date.toLocaleString(),
- short: true,
- },
- ],
- },
- ],
- });
- } catch (error) {
- console.log(error);
- }
- }
+ if (telegram) {
+ await sendTelegramNotification(
+ telegram,
+ `β
Dokploy Server Restarted\n\nDate: ${format(
+ date,
+ "PP"
+ )}\nTime: ${format(date, "pp")}`
+ );
+ }
- if (custom) {
- try {
- await sendCustomNotification(custom, {
- title: "Dokploy Server Restarted",
- message: "Dokploy server has been restarted successfully",
- timestamp: date.toISOString(),
- date: date.toLocaleString(),
- status: "success",
- type: "dokploy-restart",
- });
- } catch (error) {
- console.log(error);
- }
- }
+ if (slack) {
+ const { channel } = slack;
+ await sendSlackNotification(slack, {
+ channel: channel,
+ attachments: [
+ {
+ color: "#00FF00",
+ pretext: ":white_check_mark: *Dokploy Server Restarted*",
+ fields: [
+ {
+ title: "Time",
+ value: date.toLocaleString(),
+ short: true,
+ },
+ ],
+ },
+ ],
+ });
+ }
- if (lark) {
- try {
- await sendLarkNotification(lark, {
- msg_type: "interactive",
- card: {
- schema: "2.0",
- config: {
- update_multi: true,
- style: {
- text_size: {
- normal_v2: {
- default: "normal",
- pc: "normal",
- mobile: "heading",
- },
- },
- },
- },
- header: {
- title: {
- tag: "plain_text",
- content: "β
Dokploy Server Restarted",
- },
- subtitle: {
- tag: "plain_text",
- content: "",
- },
- template: "green",
- padding: "12px 12px 12px 12px",
- },
- body: {
- direction: "vertical",
- padding: "12px 12px 12px 12px",
- elements: [
- {
- tag: "column_set",
- columns: [
- {
- tag: "column",
- width: "weighted",
- elements: [
- {
- tag: "markdown",
- content: "**Status:**\nSuccessful",
- text_align: "left",
- text_size: "normal_v2",
- },
- ],
- vertical_align: "top",
- weight: 1,
- },
- {
- tag: "column",
- width: "weighted",
- elements: [
- {
- tag: "markdown",
- content: `**Restart Time:**\n${format(date, "PP pp")}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- ],
- vertical_align: "top",
- weight: 1,
- },
- ],
- },
- ],
- },
- },
- });
- } catch (error) {
- console.log(error);
- }
- }
- }
+ if (custom) {
+ try {
+ await sendCustomNotification(custom, {
+ title: "Dokploy Server Restarted",
+ message: "Dokploy server has been restarted successfully",
+ timestamp: date.toISOString(),
+ date: date.toLocaleString(),
+ status: "success",
+ type: "dokploy-restart",
+ });
+ } catch (error) {
+ console.log(error);
+ }
+ }
+
+ if (lark) {
+ await sendLarkNotification(lark, {
+ msg_type: "interactive",
+ card: {
+ schema: "2.0",
+ config: {
+ update_multi: true,
+ style: {
+ text_size: {
+ normal_v2: {
+ default: "normal",
+ pc: "normal",
+ mobile: "heading",
+ },
+ },
+ },
+ },
+ header: {
+ title: {
+ tag: "plain_text",
+ content: "β
Dokploy Server Restarted",
+ },
+ subtitle: {
+ tag: "plain_text",
+ content: "",
+ },
+ template: "green",
+ padding: "12px 12px 12px 12px",
+ },
+ body: {
+ direction: "vertical",
+ padding: "12px 12px 12px 12px",
+ elements: [
+ {
+ tag: "column_set",
+ columns: [
+ {
+ tag: "column",
+ width: "weighted",
+ elements: [
+ {
+ tag: "markdown",
+ content: "**Status:**\nSuccessful",
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ ],
+ vertical_align: "top",
+ weight: 1,
+ },
+ {
+ tag: "column",
+ width: "weighted",
+ elements: [
+ {
+ tag: "markdown",
+ content: `**Restart Time:**\n${format(
+ date,
+ "PP pp"
+ )}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ ],
+ vertical_align: "top",
+ weight: 1,
+ },
+ ],
+ },
+ ],
+ },
+ },
+ });
+ }
+ } catch (error) {
+ console.log(error);
+ }
+ }
};
diff --git a/packages/server/src/utils/notifications/utils.ts b/packages/server/src/utils/notifications/utils.ts
index 23d976489..f20196ccc 100644
--- a/packages/server/src/utils/notifications/utils.ts
+++ b/packages/server/src/utils/notifications/utils.ts
@@ -39,6 +39,9 @@ export const sendEmailNotification = async (
});
} catch (err) {
console.log(err);
+ throw new Error(
+ `Failed to send email notification ${err instanceof Error ? err.message : "Unknown error"}`,
+ );
}
};
@@ -46,15 +49,23 @@ export const sendDiscordNotification = async (
connection: typeof discord.$inferInsert,
embed: any,
) => {
- // try {
- await fetch(connection.webhookUrl, {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ embeds: [embed] }),
- });
- // } catch (err) {
- // console.log(err);
- // }
+ try {
+ const response = await fetch(connection.webhookUrl, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ embeds: [embed] }),
+ });
+ if (!response.ok) {
+ throw new Error(
+ `Failed to send discord notification ${response.statusText}`,
+ );
+ }
+ } catch (err) {
+ console.log("error", err);
+ throw new Error(
+ `Failed to send discord notification ${err instanceof Error ? err.message : "Unknown error"}`,
+ );
+ }
};
export const sendTelegramNotification = async (
@@ -91,13 +102,21 @@ export const sendSlackNotification = async (
message: any,
) => {
try {
- await fetch(connection.webhookUrl, {
+ const response = await fetch(connection.webhookUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(message),
});
+ if (!response.ok) {
+ throw new Error(
+ `Failed to send slack notification ${response.statusText}`,
+ );
+ }
} catch (err) {
- console.log(err);
+ console.log("error", err);
+ throw new Error(
+ `Failed to send slack notification ${err instanceof Error ? err.message : "Unknown error"}`,
+ );
}
};
diff --git a/packages/server/src/utils/process/ExecError.ts b/packages/server/src/utils/process/ExecError.ts
new file mode 100644
index 000000000..773968b5c
--- /dev/null
+++ b/packages/server/src/utils/process/ExecError.ts
@@ -0,0 +1,55 @@
+export interface ExecErrorDetails {
+ command: string;
+ stdout?: string;
+ stderr?: string;
+ exitCode?: number;
+ originalError?: Error;
+ serverId?: string | null;
+}
+
+export class ExecError extends Error {
+ public readonly command: string;
+ public readonly stdout?: string;
+ public readonly stderr?: string;
+ public readonly exitCode?: number;
+ public readonly originalError?: Error;
+ public readonly serverId?: string | null;
+
+ constructor(message: string, details: ExecErrorDetails) {
+ super(message);
+ this.name = "ExecError";
+ this.command = details.command;
+ this.stdout = details.stdout;
+ this.stderr = details.stderr;
+ this.exitCode = details.exitCode;
+ this.originalError = details.originalError;
+ this.serverId = details.serverId;
+
+ // Maintains proper stack trace for where our error was thrown (only available on V8)
+ if (Error.captureStackTrace) {
+ Error.captureStackTrace(this, ExecError);
+ }
+ }
+
+ /**
+ * Get a formatted error message with all details
+ */
+ getDetailedMessage(): string {
+ const parts = [
+ `Command: ${this.command}`,
+ this.exitCode !== undefined ? `Exit Code: ${this.exitCode}` : null,
+ this.serverId ? `Server ID: ${this.serverId}` : "Location: Local",
+ this.stderr ? `Stderr: ${this.stderr}` : null,
+ this.stdout ? `Stdout: ${this.stdout}` : null,
+ ].filter(Boolean);
+
+ return `${this.message}\n${parts.join("\n")}`;
+ }
+
+ /**
+ * Check if this error is from a remote execution
+ */
+ isRemote(): boolean {
+ return !!this.serverId;
+ }
+}
diff --git a/packages/server/src/utils/process/execAsync.ts b/packages/server/src/utils/process/execAsync.ts
index 84f0701d9..cd0249000 100644
--- a/packages/server/src/utils/process/execAsync.ts
+++ b/packages/server/src/utils/process/execAsync.ts
@@ -2,8 +2,43 @@ import { exec, execFile } from "node:child_process";
import util from "node:util";
import { findServerById } from "@dokploy/server/services/server";
import { Client } from "ssh2";
+import { ExecError } from "./ExecError";
-export const execAsync = util.promisify(exec);
+// Re-export ExecError for easier imports
+export { ExecError } from "./ExecError";
+
+const execAsyncBase = util.promisify(exec);
+
+export const execAsync = async (
+ command: string,
+ options?: { cwd?: string; env?: NodeJS.ProcessEnv; shell?: string },
+): Promise<{ stdout: string; stderr: string }> => {
+ try {
+ const result = await execAsyncBase(command, options);
+ return {
+ stdout: result.stdout.toString(),
+ stderr: result.stderr.toString(),
+ };
+ } catch (error) {
+ if (error instanceof Error) {
+ // @ts-ignore - exec error has these properties
+ const exitCode = error.code;
+ // @ts-ignore
+ const stdout = error.stdout?.toString() || "";
+ // @ts-ignore
+ const stderr = error.stderr?.toString() || "";
+
+ throw new ExecError(`Command execution failed: ${error.message}`, {
+ command,
+ stdout,
+ stderr,
+ exitCode,
+ originalError: error,
+ });
+ }
+ throw error;
+ }
+};
interface ExecOptions {
cwd?: string;
@@ -21,7 +56,16 @@ export const execAsyncStream = (
const childProcess = exec(command, options, (error) => {
if (error) {
- reject(error);
+ reject(
+ new ExecError(`Command execution failed: ${error.message}`, {
+ command,
+ stdout: stdoutComplete,
+ stderr: stderrComplete,
+ // @ts-ignore
+ exitCode: error.code,
+ originalError: error,
+ }),
+ );
return;
}
resolve({ stdout: stdoutComplete, stderr: stderrComplete });
@@ -45,7 +89,14 @@ export const execAsyncStream = (
childProcess.on("error", (error) => {
console.log(error);
- reject(error);
+ reject(
+ new ExecError(`Command execution error: ${error.message}`, {
+ command,
+ stdout: stdoutComplete,
+ stderr: stderrComplete,
+ originalError: error,
+ }),
+ );
});
});
};
@@ -108,7 +159,14 @@ export const execAsyncRemote = async (
conn.exec(command, (err, stream) => {
if (err) {
onData?.(err.message);
- throw err;
+ reject(
+ new ExecError(`Remote command execution failed: ${err.message}`, {
+ command,
+ serverId,
+ originalError: err,
+ }),
+ );
+ return;
}
stream
.on("close", (code: number, _signal: string) => {
@@ -117,8 +175,15 @@ export const execAsyncRemote = async (
resolve({ stdout, stderr });
} else {
reject(
- new Error(
- `Command exited with code ${code}. Stderr: ${stderr}, command: ${command}`,
+ new ExecError(
+ `Remote command failed with exit code ${code}`,
+ {
+ command,
+ stdout,
+ stderr,
+ exitCode: code,
+ serverId,
+ },
),
);
}
@@ -136,17 +201,25 @@ export const execAsyncRemote = async (
.on("error", (err) => {
conn.end();
if (err.level === "client-authentication") {
- onData?.(
- `Authentication failed: Invalid SSH private key. β Error: ${err.message} ${err.level}`,
- );
+ const errorMsg = `Authentication failed: Invalid SSH private key. β Error: ${err.message} ${err.level}`;
+ onData?.(errorMsg);
reject(
- new Error(
- `Authentication failed: Invalid SSH private key. β Error: ${err.message} ${err.level}`,
- ),
+ new ExecError(errorMsg, {
+ command,
+ serverId,
+ originalError: err,
+ }),
);
} else {
- onData?.(`SSH connection error: ${err.message}`);
- reject(new Error(`SSH connection error: ${err.message}`));
+ const errorMsg = `SSH connection error: ${err.message}`;
+ onData?.(errorMsg);
+ reject(
+ new ExecError(errorMsg, {
+ command,
+ serverId,
+ originalError: err,
+ }),
+ );
}
})
.connect({
diff --git a/packages/server/src/utils/providers/bitbucket.ts b/packages/server/src/utils/providers/bitbucket.ts
index ed6cd8c31..2248baaaf 100644
--- a/packages/server/src/utils/providers/bitbucket.ts
+++ b/packages/server/src/utils/providers/bitbucket.ts
@@ -1,4 +1,3 @@
-import { createWriteStream } from "node:fs";
import { join } from "node:path";
import { paths } from "@dokploy/server/constants";
import type {
@@ -9,12 +8,8 @@ import {
type Bitbucket,
findBitbucketById,
} from "@dokploy/server/services/bitbucket";
-import type { Compose } from "@dokploy/server/services/compose";
import type { InferResultType } from "@dokploy/server/types/with";
import { TRPCError } from "@trpc/server";
-import { recreateDirectory } from "../filesystem/directory";
-import { execAsyncRemote } from "../process/execAsync";
-import { spawnAsync } from "../process/spawnAsync";
export type ApplicationWithBitbucket = InferResultType<
"applications",
@@ -81,202 +76,52 @@ export const getBitbucketHeaders = (bitbucketProvider: Bitbucket) => {
};
};
-export const cloneBitbucketRepository = async (
- entity: ApplicationWithBitbucket | ComposeWithBitbucket,
- logPath: string,
- isCompose = false,
-) => {
- const { COMPOSE_PATH, APPLICATIONS_PATH } = paths();
- const writeStream = createWriteStream(logPath, { flags: "a" });
+interface CloneBitbucketRepository {
+ appName: string;
+ bitbucketRepository: string | null;
+ bitbucketOwner: string | null;
+ bitbucketBranch: string | null;
+ bitbucketId: string | null;
+ enableSubmodules: boolean;
+ serverId: string | null;
+ type?: "application" | "compose";
+}
+
+export const cloneBitbucketRepository = async ({
+ type = "application",
+ ...entity
+}: CloneBitbucketRepository) => {
+ let command = "set -e;";
const {
appName,
bitbucketRepository,
bitbucketOwner,
bitbucketBranch,
bitbucketId,
- bitbucket,
enableSubmodules,
+ serverId,
} = entity;
+ const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(!!serverId);
if (!bitbucketId) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Bitbucket Provider not found",
- });
+ command += `echo "Error: β Bitbucket Provider not found"; exit 1;`;
+ return command;
}
+ const bitbucket = await findBitbucketById(bitbucketId);
- const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
+ if (!bitbucket) {
+ command += `echo "Error: β Bitbucket Provider not found"; exit 1;`;
+ return command;
+ }
+ const basePath = type === "compose" ? COMPOSE_PATH : APPLICATIONS_PATH;
const outputPath = join(basePath, appName, "code");
- await recreateDirectory(outputPath);
+ command += `rm -rf ${outputPath};`;
+ command += `mkdir -p ${outputPath};`;
const repoclone = `bitbucket.org/${bitbucketOwner}/${bitbucketRepository}.git`;
const cloneUrl = getBitbucketCloneUrl(bitbucket, repoclone);
- try {
- writeStream.write(`\nCloning Repo ${repoclone} to ${outputPath}: β
\n`);
- const cloneArgs = [
- "clone",
- "--branch",
- bitbucketBranch!,
- "--depth",
- "1",
- ...(enableSubmodules ? ["--recurse-submodules"] : []),
- cloneUrl,
- outputPath,
- "--progress",
- ];
-
- await spawnAsync("git", cloneArgs, (data) => {
- if (writeStream.writable) {
- writeStream.write(data);
- }
- });
- writeStream.write(`\nCloned ${repoclone} to ${outputPath}: β
\n`);
- } catch (error) {
- writeStream.write(`ERROR Cloning: ${error}: β`);
- throw error;
- } finally {
- writeStream.end();
- }
-};
-
-export const cloneRawBitbucketRepository = async (entity: Compose) => {
- const { COMPOSE_PATH } = paths();
- const {
- appName,
- bitbucketRepository,
- bitbucketOwner,
- bitbucketBranch,
- bitbucketId,
- enableSubmodules,
- } = entity;
-
- if (!bitbucketId) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Bitbucket Provider not found",
- });
- }
-
- const bitbucketProvider = await findBitbucketById(bitbucketId);
- const basePath = COMPOSE_PATH;
- const outputPath = join(basePath, appName, "code");
- await recreateDirectory(outputPath);
- const repoclone = `bitbucket.org/${bitbucketOwner}/${bitbucketRepository}.git`;
- const cloneUrl = getBitbucketCloneUrl(bitbucketProvider, repoclone);
-
- try {
- const cloneArgs = [
- "clone",
- "--branch",
- bitbucketBranch!,
- "--depth",
- "1",
- ...(enableSubmodules ? ["--recurse-submodules"] : []),
- cloneUrl,
- outputPath,
- "--progress",
- ];
-
- await spawnAsync("git", cloneArgs);
- } catch (error) {
- throw error;
- }
-};
-
-export const cloneRawBitbucketRepositoryRemote = async (compose: Compose) => {
- const { COMPOSE_PATH } = paths(true);
- const {
- appName,
- bitbucketRepository,
- bitbucketOwner,
- bitbucketBranch,
- bitbucketId,
- serverId,
- enableSubmodules,
- } = compose;
-
- if (!serverId) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Server not found",
- });
- }
- if (!bitbucketId) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Bitbucket Provider not found",
- });
- }
-
- const bitbucketProvider = await findBitbucketById(bitbucketId);
- const basePath = COMPOSE_PATH;
- const outputPath = join(basePath, appName, "code");
- const repoclone = `bitbucket.org/${bitbucketOwner}/${bitbucketRepository}.git`;
- const cloneUrl = getBitbucketCloneUrl(bitbucketProvider, repoclone);
-
- try {
- const cloneCommand = `
- rm -rf ${outputPath};
- git clone --branch ${bitbucketBranch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} ${cloneUrl} ${outputPath}
- `;
- await execAsyncRemote(serverId, cloneCommand);
- } catch (error) {
- throw error;
- }
-};
-
-export const getBitbucketCloneCommand = async (
- entity: ApplicationWithBitbucket | ComposeWithBitbucket,
- logPath: string,
- isCompose = false,
-) => {
- const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(true);
- const {
- appName,
- bitbucketRepository,
- bitbucketOwner,
- bitbucketBranch,
- bitbucketId,
- serverId,
- enableSubmodules,
- } = entity;
-
- if (!serverId) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Server not found",
- });
- }
-
- if (!bitbucketId) {
- const command = `
- echo "Error: β Bitbucket Provider not found" >> ${logPath};
- exit 1;
- `;
- await execAsyncRemote(serverId, command);
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Bitbucket Provider not found",
- });
- }
-
- const bitbucketProvider = await findBitbucketById(bitbucketId);
- const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
- const outputPath = join(basePath, appName, "code");
- await recreateDirectory(outputPath);
- const repoclone = `bitbucket.org/${bitbucketOwner}/${bitbucketRepository}.git`;
- const cloneUrl = getBitbucketCloneUrl(bitbucketProvider, repoclone);
-
- const cloneCommand = `
-rm -rf ${outputPath};
-mkdir -p ${outputPath};
-if ! git clone --branch ${bitbucketBranch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} --progress ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then
- echo "β [ERROR] Fail to clone the repository ${repoclone}" >> ${logPath};
- exit 1;
-fi
-echo "Cloned ${repoclone} to ${outputPath}: β
" >> ${logPath};
- `;
-
- return cloneCommand;
+ command += `echo "Cloning Repo ${repoclone} to ${outputPath}: β
";`;
+ command += `git clone --branch ${bitbucketBranch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} ${cloneUrl} ${outputPath} --progress;`;
+ return command;
};
export const getBitbucketRepositories = async (bitbucketId?: string) => {
diff --git a/packages/server/src/utils/providers/docker.ts b/packages/server/src/utils/providers/docker.ts
index 56341b7d6..06f962dc7 100644
--- a/packages/server/src/utils/providers/docker.ts
+++ b/packages/server/src/utils/providers/docker.ts
@@ -1,60 +1,6 @@
-import { createWriteStream } from "node:fs";
-import { type ApplicationNested, mechanizeDockerContainer } from "../builders";
-import { pullImage } from "../docker/utils";
+import type { ApplicationNested } from "../builders";
-interface RegistryAuth {
- username: string;
- password: string;
- registryUrl: string;
-}
-
-export const buildDocker = async (
- application: ApplicationNested,
- logPath: string,
-): Promise => {
- const { buildType, dockerImage, username, password } = application;
- const authConfig: Partial = {
- username: username || "",
- password: password || "",
- registryUrl: application.registryUrl || "",
- };
-
- const writeStream = createWriteStream(logPath, { flags: "a" });
-
- writeStream.write(`\nBuild ${buildType}\n`);
-
- writeStream.write(`Pulling ${dockerImage}: β
\n`);
-
- try {
- if (!dockerImage) {
- throw new Error("Docker image not found");
- }
-
- await pullImage(
- dockerImage,
- (data) => {
- if (writeStream.writable) {
- writeStream.write(`${data}\n`);
- }
- },
- authConfig,
- );
- await mechanizeDockerContainer(application);
- writeStream.write("\nDocker Deployed: β
\n");
- } catch (error) {
- writeStream.write(
- `β Error: ${error instanceof Error ? error.message : String(error)}`,
- );
- throw error;
- } finally {
- writeStream.end();
- }
-};
-
-export const buildRemoteDocker = async (
- application: ApplicationNested,
- logPath: string,
-) => {
+export const buildRemoteDocker = async (application: ApplicationNested) => {
const { registryUrl, dockerImage, username, password } = application;
try {
@@ -62,25 +8,25 @@ export const buildRemoteDocker = async (
throw new Error("Docker image not found");
}
let command = `
-echo "Pulling ${dockerImage}" >> ${logPath};
+echo "Pulling ${dockerImage}";
`;
if (username && password) {
command += `
-if ! echo "${password}" | docker login --username "${username}" --password-stdin "${registryUrl || ""}" >> ${logPath} 2>&1; then
- echo "β Login failed" >> ${logPath};
+if ! echo "${password}" | docker login --username "${username}" --password-stdin "${registryUrl || ""}" 2>&1; then
+ echo "β Login failed";
exit 1;
fi
`;
}
command += `
-docker pull ${dockerImage} >> ${logPath} 2>> ${logPath} || {
- echo "β Pulling image failed" >> ${logPath};
+docker pull ${dockerImage} 2>&1 || {
+ echo "β Pulling image failed";
exit 1;
}
-echo "β
Pulling image completed." >> ${logPath};
+echo "β
Pulling image completed.";
`;
return command;
} catch (error) {
diff --git a/packages/server/src/utils/providers/git.ts b/packages/server/src/utils/providers/git.ts
index 5779252db..8e640892d 100644
--- a/packages/server/src/utils/providers/git.ts
+++ b/packages/server/src/utils/providers/git.ts
@@ -1,159 +1,65 @@
-import { createWriteStream } from "node:fs";
import path, { join } from "node:path";
import { paths } from "@dokploy/server/constants";
-import type { Compose } from "@dokploy/server/services/compose";
import {
findSSHKeyById,
updateSSHKeyById,
} from "@dokploy/server/services/ssh-key";
-import { TRPCError } from "@trpc/server";
-import { recreateDirectory } from "../filesystem/directory";
import { execAsync, execAsyncRemote } from "../process/execAsync";
-import { spawnAsync } from "../process/spawnAsync";
-export const cloneGitRepository = async (
- entity: {
- appName: string;
- customGitUrl?: string | null;
- customGitBranch?: string | null;
- customGitSSHKeyId?: string | null;
- enableSubmodules?: boolean;
- },
- logPath: string,
- isCompose = false,
-) => {
- const { SSH_PATH, COMPOSE_PATH, APPLICATIONS_PATH } = paths();
+interface CloneGitRepository {
+ appName: string;
+ customGitUrl?: string | null;
+ customGitBranch?: string | null;
+ customGitSSHKeyId?: string | null;
+ enableSubmodules?: boolean;
+ serverId: string | null;
+ type?: "application" | "compose";
+}
+
+export const cloneGitRepository = async ({
+ type = "application",
+ ...entity
+}: CloneGitRepository) => {
+ let command = "set -e;";
const {
appName,
customGitUrl,
customGitBranch,
customGitSSHKeyId,
enableSubmodules,
+ serverId,
} = entity;
+ const { SSH_PATH, COMPOSE_PATH, APPLICATIONS_PATH } = paths(!!serverId);
if (!customGitUrl || !customGitBranch) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error: Repository not found",
- });
+ command += `echo "Error: β Repository not found"; exit 1;`;
+ return command;
}
- const writeStream = createWriteStream(logPath, { flags: "a" });
const temporalKeyPath = path.join("/tmp", "id_rsa");
if (customGitSSHKeyId) {
const sshKey = await findSSHKeyById(customGitSSHKeyId);
- await execAsync(`
+ command += `
echo "${sshKey.privateKey}" > ${temporalKeyPath}
- chmod 600 ${temporalKeyPath}
- `);
+ chmod 600 ${temporalKeyPath};
+ `;
}
- const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
+ const basePath = type === "compose" ? COMPOSE_PATH : APPLICATIONS_PATH;
const outputPath = join(basePath, appName, "code");
const knownHostsPath = path.join(SSH_PATH, "known_hosts");
- try {
- if (!isHttpOrHttps(customGitUrl)) {
- if (!customGitSSHKeyId) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message:
- "Error: you are trying to clone a ssh repository without a ssh key, please set a ssh key",
- });
- }
- await addHostToKnownHosts(customGitUrl);
+ if (!isHttpOrHttps(customGitUrl)) {
+ if (!customGitSSHKeyId) {
+ command += `echo "Error: β You are trying to clone a ssh repository without a ssh key, please set a ssh key"; exit 1;`;
+ return command;
}
- await recreateDirectory(outputPath);
- writeStream.write(
- `\nCloning Repo Custom ${customGitUrl} to ${outputPath}: β
\n`,
- );
-
- if (customGitSSHKeyId) {
- await updateSSHKeyById({
- sshKeyId: customGitSSHKeyId,
- lastUsedAt: new Date().toISOString(),
- });
- }
-
- const { port } = sanitizeRepoPathSSH(customGitUrl);
- const cloneArgs = [
- "clone",
- "--branch",
- customGitBranch,
- "--depth",
- "1",
- ...(enableSubmodules ? ["--recurse-submodules"] : []),
- customGitUrl,
- outputPath,
- "--progress",
- ];
-
- await spawnAsync(
- "git",
- cloneArgs,
- (data) => {
- if (writeStream.writable) {
- writeStream.write(data);
- }
- },
- {
- env: {
- ...process.env,
- ...(customGitSSHKeyId && {
- GIT_SSH_COMMAND: `ssh -i ${temporalKeyPath}${port ? ` -p ${port}` : ""} -o UserKnownHostsFile=${knownHostsPath}`,
- }),
- },
- },
- );
-
- writeStream.write(`\nCloned Custom Git ${customGitUrl}: β
\n`);
- } catch (error) {
- writeStream.write(`\nERROR Cloning Custom Git: ${error}: β\n`);
- throw error;
- } finally {
- writeStream.end();
+ command += addHostToKnownHostsCommand(customGitUrl);
}
-};
-
-export const getCustomGitCloneCommand = async (
- entity: {
- appName: string;
- customGitUrl?: string | null;
- customGitBranch?: string | null;
- customGitSSHKeyId?: string | null;
- serverId: string | null;
- enableSubmodules: boolean;
- },
- logPath: string,
- isCompose = false,
-) => {
- const { SSH_PATH, COMPOSE_PATH, APPLICATIONS_PATH } = paths(true);
- const {
- appName,
- customGitUrl,
- customGitBranch,
- customGitSSHKeyId,
- serverId,
- enableSubmodules,
- } = entity;
-
- if (!customGitUrl || !customGitBranch) {
- const command = `
- echo "Error: β Repository not found" >> ${logPath};
- exit 1;
- `;
-
- await execAsyncRemote(serverId, command);
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error: Repository not found",
- });
- }
-
- const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
- const outputPath = join(basePath, appName, "code");
- const knownHostsPath = path.join(SSH_PATH, "known_hosts");
+ command += `rm -rf ${outputPath};`;
+ command += `mkdir -p ${outputPath};`;
+ command += `echo "Cloning Repo Custom ${customGitUrl} to ${outputPath}: β
";`;
if (customGitSSHKeyId) {
await updateSSHKeyById({
@@ -161,48 +67,22 @@ export const getCustomGitCloneCommand = async (
lastUsedAt: new Date().toISOString(),
});
}
- try {
- const command = [];
- if (!isHttpOrHttps(customGitUrl)) {
- if (!customGitSSHKeyId) {
- command.push(
- `echo "Error: you are trying to clone a ssh repository without a ssh key, please set a ssh key β" >> ${logPath};
- exit 1;
- `,
- );
- }
- command.push(addHostToKnownHostsCommand(customGitUrl));
- }
- command.push(`rm -rf ${outputPath};`);
- command.push(`mkdir -p ${outputPath};`);
- command.push(
- `echo "Cloning Custom Git ${customGitUrl}" to ${outputPath}: β
>> ${logPath};`,
- );
- if (customGitSSHKeyId) {
- const sshKey = await findSSHKeyById(customGitSSHKeyId);
- const { port } = sanitizeRepoPathSSH(customGitUrl);
- const gitSshCommand = `ssh -i /tmp/id_rsa${port ? ` -p ${port}` : ""} -o UserKnownHostsFile=${knownHostsPath}`;
- command.push(
- `
- echo "${sshKey.privateKey}" > /tmp/id_rsa
- chmod 600 /tmp/id_rsa
- export GIT_SSH_COMMAND="${gitSshCommand}"
- `,
- );
- }
- command.push(
- `if ! git clone --branch ${customGitBranch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} --progress ${customGitUrl} ${outputPath} >> ${logPath} 2>&1; then
- echo "β [ERROR] Fail to clone the repository ${customGitUrl}" >> ${logPath};
+ if (customGitSSHKeyId) {
+ const sshKey = await findSSHKeyById(customGitSSHKeyId);
+ const { port } = sanitizeRepoPathSSH(customGitUrl);
+ const gitSshCommand = `ssh -i /tmp/id_rsa${port ? ` -p ${port}` : ""} -o UserKnownHostsFile=${knownHostsPath}`;
+ command += `echo "${sshKey.privateKey}" > /tmp/id_rsa;`;
+ command += "chmod 600 /tmp/id_rsa;";
+ command += `export GIT_SSH_COMMAND="${gitSshCommand}";`;
+ }
+ command += `if ! git clone --branch ${customGitBranch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} --progress ${customGitUrl} ${outputPath}; then
+ echo "β [ERROR] Fail to clone the repository ${customGitUrl}";
exit 1;
fi
- `,
- );
- command.push(`echo "Cloned Custom Git ${customGitUrl}: β
" >> ${logPath};`);
- return command.join("\n");
- } catch (error) {
- throw error;
- }
+ `;
+
+ return command;
};
const isHttpOrHttps = (url: string): boolean => {
@@ -210,19 +90,19 @@ const isHttpOrHttps = (url: string): boolean => {
return regex.test(url);
};
-const addHostToKnownHosts = async (repositoryURL: string) => {
- const { SSH_PATH } = paths();
- const { domain, port } = sanitizeRepoPathSSH(repositoryURL);
- const knownHostsPath = path.join(SSH_PATH, "known_hosts");
+// const addHostToKnownHosts = async (repositoryURL: string) => {
+// const { SSH_PATH } = paths();
+// const { domain, port } = sanitizeRepoPathSSH(repositoryURL);
+// const knownHostsPath = path.join(SSH_PATH, "known_hosts");
- const command = `ssh-keyscan -p ${port} ${domain} >> ${knownHostsPath}`;
- try {
- await execAsync(command);
- } catch (error) {
- console.error(`Error adding host to known_hosts: ${error}`);
- throw error;
- }
-};
+// const command = `ssh-keyscan -p ${port} ${domain} >> ${knownHostsPath}`;
+// try {
+// await execAsync(command);
+// } catch (error) {
+// console.error(`Error adding host to known_hosts: ${error}`);
+// throw error;
+// }
+// };
const addHostToKnownHostsCommand = (repositoryURL: string) => {
const { SSH_PATH } = paths(true);
@@ -267,160 +147,43 @@ const sanitizeRepoPathSSH = (input: string) => {
};
};
-export const cloneGitRawRepository = async (entity: {
+interface Props {
appName: string;
- customGitUrl?: string | null;
- customGitBranch?: string | null;
- customGitSSHKeyId?: string | null;
- enableSubmodules?: boolean;
-}) => {
- const {
- appName,
- customGitUrl,
- customGitBranch,
- customGitSSHKeyId,
- enableSubmodules,
- } = entity;
+ type?: "application" | "compose";
+ serverId: string | null;
+}
- if (!customGitUrl || !customGitBranch) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error: Repository not found",
- });
- }
-
- const { SSH_PATH, COMPOSE_PATH } = paths();
- const temporalKeyPath = path.join("/tmp", "id_rsa");
- const basePath = COMPOSE_PATH;
+export const getGitCommitInfo = async ({
+ appName,
+ type = "application",
+ serverId,
+}: Props) => {
+ const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(!!serverId);
+ const basePath = type === "compose" ? COMPOSE_PATH : APPLICATIONS_PATH;
const outputPath = join(basePath, appName, "code");
- const knownHostsPath = path.join(SSH_PATH, "known_hosts");
-
- if (customGitSSHKeyId) {
- const sshKey = await findSSHKeyById(customGitSSHKeyId);
-
- await execAsync(`
- echo "${sshKey.privateKey}" > ${temporalKeyPath}
- chmod 600 ${temporalKeyPath}
- `);
- }
-
+ let stdoutResult = "";
+ const result = {
+ message: "",
+ hash: "",
+ };
try {
- if (!isHttpOrHttps(customGitUrl)) {
- if (!customGitSSHKeyId) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message:
- "Error: you are trying to clone a ssh repository without a ssh key, please set a ssh key",
- });
- }
- await addHostToKnownHosts(customGitUrl);
- }
- await recreateDirectory(outputPath);
-
- if (customGitSSHKeyId) {
- await updateSSHKeyById({
- sshKeyId: customGitSSHKeyId,
- lastUsedAt: new Date().toISOString(),
- });
+ const gitCommand = `git -C ${outputPath} log -1 --pretty=format:"%H---DELIMITER---%B"`;
+ if (serverId) {
+ const { stdout } = await execAsyncRemote(serverId, gitCommand);
+ stdoutResult = stdout.trim();
+ } else {
+ const { stdout } = await execAsync(gitCommand);
+ stdoutResult = stdout.trim();
}
- const { port } = sanitizeRepoPathSSH(customGitUrl);
- const cloneArgs = [
- "clone",
- "--branch",
- customGitBranch,
- "--depth",
- "1",
- ...(enableSubmodules ? ["--recurse-submodules"] : []),
- customGitUrl,
- outputPath,
- "--progress",
- ];
-
- await spawnAsync("git", cloneArgs, (_data) => {}, {
- env: {
- ...process.env,
- ...(customGitSSHKeyId && {
- GIT_SSH_COMMAND: `ssh -i ${temporalKeyPath}${port ? ` -p ${port}` : ""} -o UserKnownHostsFile=${knownHostsPath}`,
- }),
- },
- });
+ const parts = stdoutResult.split("---DELIMITER---");
+ if (parts && parts.length === 2) {
+ result.hash = parts[0]?.trim() || "";
+ result.message = parts[1]?.trim() || "";
+ }
} catch (error) {
- throw error;
- }
-};
-
-export const cloneRawGitRepositoryRemote = async (compose: Compose) => {
- const {
- appName,
- customGitBranch,
- customGitUrl,
- customGitSSHKeyId,
- serverId,
- enableSubmodules,
- } = compose;
-
- if (!serverId) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Server not found",
- });
- }
- if (!customGitUrl) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Git Provider not found",
- });
- }
-
- const { SSH_PATH, COMPOSE_PATH } = paths(true);
- const basePath = COMPOSE_PATH;
- const outputPath = join(basePath, appName, "code");
- const knownHostsPath = path.join(SSH_PATH, "known_hosts");
-
- if (customGitSSHKeyId) {
- await updateSSHKeyById({
- sshKeyId: customGitSSHKeyId,
- lastUsedAt: new Date().toISOString(),
- });
- }
- try {
- const command = [];
- if (!isHttpOrHttps(customGitUrl)) {
- if (!customGitSSHKeyId) {
- command.push(
- `echo "Error: you are trying to clone a ssh repository without a ssh key, please set a ssh key β" ;
- exit 1;
- `,
- );
- }
- command.push(addHostToKnownHostsCommand(customGitUrl));
- }
- command.push(`rm -rf ${outputPath};`);
- command.push(`mkdir -p ${outputPath};`);
- if (customGitSSHKeyId) {
- const sshKey = await findSSHKeyById(customGitSSHKeyId);
- const { port } = sanitizeRepoPathSSH(customGitUrl);
- const gitSshCommand = `ssh -i /tmp/id_rsa${port ? ` -p ${port}` : ""} -o UserKnownHostsFile=${knownHostsPath}`;
- command.push(
- `
- echo "${sshKey.privateKey}" > /tmp/id_rsa
- chmod 600 /tmp/id_rsa
- export GIT_SSH_COMMAND="${gitSshCommand}"
- `,
- );
- }
-
- command.push(
- `if ! git clone --branch ${customGitBranch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} --progress ${customGitUrl} ${outputPath} ; then
- echo "[ERROR] Fail to clone the repository ";
- exit 1;
- fi
- `,
- );
-
- await execAsyncRemote(serverId, command.join("\n"));
- } catch (error) {
- throw error;
+ console.error(`Error getting git commit info: ${error}`);
+ return null;
}
+ return result;
};
diff --git a/packages/server/src/utils/providers/gitea.ts b/packages/server/src/utils/providers/gitea.ts
index db6dcbb78..ec8946ab3 100644
--- a/packages/server/src/utils/providers/gitea.ts
+++ b/packages/server/src/utils/providers/gitea.ts
@@ -1,7 +1,5 @@
-import { createWriteStream } from "node:fs";
import { join } from "node:path";
import { paths } from "@dokploy/server/constants";
-import type { Compose } from "@dokploy/server/services/compose";
import {
findGiteaById,
type Gitea,
@@ -9,9 +7,6 @@ import {
} from "@dokploy/server/services/gitea";
import type { InferResultType } from "@dokploy/server/types/with";
import { TRPCError } from "@trpc/server";
-import { recreateDirectory } from "../filesystem/directory";
-import { execAsyncRemote } from "../process/execAsync";
-import { spawnAsync } from "../process/spawnAsync";
export const getErrorCloneRequirements = (entity: {
giteaRepository?: string | null;
@@ -119,79 +114,27 @@ export type ApplicationWithGitea = InferResultType<
export type ComposeWithGitea = InferResultType<"compose", { gitea: true }>;
-export const getGiteaCloneCommand = async (
- entity: ApplicationWithGitea | ComposeWithGitea,
- logPath: string,
- isCompose = false,
-) => {
- const {
- appName,
- giteaBranch,
- giteaId,
- giteaOwner,
- giteaRepository,
- serverId,
- enableSubmodules,
- } = entity;
-
- if (!serverId) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Server not found",
- });
- }
-
- if (!giteaId) {
- const command = `
- echo "Error: β Gitlab Provider not found" >> ${logPath};
- exit 1;
- `;
-
- await execAsyncRemote(serverId, command);
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Gitea Provider not found",
- });
- }
-
- // Use paths(true) for remote operations
- const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(true);
- await refreshGiteaToken(giteaId);
- const gitea = await findGiteaById(giteaId);
- const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
- const outputPath = join(basePath, appName, "code");
-
- const repoClone = `${giteaOwner}/${giteaRepository}.git`;
- const cloneUrl = buildGiteaCloneUrl(
- gitea?.giteaUrl!,
- gitea?.accessToken!,
- giteaOwner!,
- giteaRepository!,
- );
-
- const cloneCommand = `
- rm -rf ${outputPath};
- mkdir -p ${outputPath};
-
- if ! git clone --branch ${giteaBranch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then
- echo "β [ERROR] Failed to clone the repository ${repoClone}" >> ${logPath};
- exit 1;
- fi
-
- echo "Cloned ${repoClone} to ${outputPath}: β
" >> ${logPath};
- `;
-
- return cloneCommand;
+type GiteaClone = (ApplicationWithGitea | ComposeWithGitea) & {
+ serverId: string | null;
+ type?: "application" | "compose";
};
-export const cloneGiteaRepository = async (
- entity: ApplicationWithGitea | ComposeWithGitea,
- logPath: string,
- isCompose = false,
-) => {
- const { APPLICATIONS_PATH, COMPOSE_PATH } = paths();
+interface CloneGiteaRepository {
+ appName: string;
+ giteaBranch: string | null;
+ giteaId: string | null;
+ giteaOwner: string | null;
+ giteaRepository: string | null;
+ enableSubmodules: boolean;
+ serverId: string | null;
+ type?: "application" | "compose";
+}
- const writeStream = createWriteStream(logPath, { flags: "a" });
+export const cloneGiteaRepository = async ({
+ type = "application",
+ ...entity
+}: CloneGiteaRepository) => {
+ let command = "set -e;";
const {
appName,
giteaBranch,
@@ -199,27 +142,27 @@ export const cloneGiteaRepository = async (
giteaOwner,
giteaRepository,
enableSubmodules,
+ serverId,
} = entity;
+ const { APPLICATIONS_PATH, COMPOSE_PATH } = paths(!!serverId);
if (!giteaId) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Gitea Provider not found",
- });
+ command += `echo "Error: β Gitea Provider not found"; exit 1;`;
+ return command;
}
await refreshGiteaToken(giteaId);
const giteaProvider = await findGiteaById(giteaId);
+
if (!giteaProvider) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Gitea provider not found in the database",
- });
+ command += `echo "β [ERROR] Gitea provider not found in the database"; exit 1;`;
+ return command;
}
- const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
+ const basePath = type === "compose" ? COMPOSE_PATH : APPLICATIONS_PATH;
const outputPath = join(basePath, appName, "code");
- await recreateDirectory(outputPath);
+ command += `rm -rf ${outputPath};`;
+ command += `mkdir -p ${outputPath};`;
const repoClone = `${giteaOwner}/${giteaRepository}.git`;
const cloneUrl = buildGiteaCloneUrl(
@@ -229,134 +172,9 @@ export const cloneGiteaRepository = async (
giteaRepository!,
);
- writeStream.write(`\nCloning Repo ${repoClone} to ${outputPath}...\n`);
-
- try {
- await spawnAsync(
- "git",
- [
- "clone",
- "--branch",
- giteaBranch!,
- "--depth",
- "1",
- ...(enableSubmodules ? ["--recurse-submodules"] : []),
- cloneUrl,
- outputPath,
- "--progress",
- ],
- (data) => {
- if (writeStream.writable) {
- writeStream.write(data);
- }
- },
- );
- writeStream.write(`\nCloned ${repoClone}: β
\n`);
- } catch (error) {
- writeStream.write(`ERROR Cloning: ${error}: β`);
- throw error;
- } finally {
- writeStream.end();
- }
-};
-
-export const cloneRawGiteaRepository = async (entity: Compose) => {
- const {
- appName,
- giteaRepository,
- giteaOwner,
- giteaBranch,
- giteaId,
- enableSubmodules,
- } = entity;
- const { COMPOSE_PATH } = paths();
-
- if (!giteaId) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Gitea Provider not found",
- });
- }
- await refreshGiteaToken(giteaId);
- const giteaProvider = await findGiteaById(giteaId);
- if (!giteaProvider) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Gitea provider not found in the database",
- });
- }
-
- const basePath = COMPOSE_PATH;
- const outputPath = join(basePath, appName, "code");
- await recreateDirectory(outputPath);
-
- const cloneUrl = buildGiteaCloneUrl(
- giteaProvider.giteaUrl,
- giteaProvider.accessToken!,
- giteaOwner!,
- giteaRepository!,
- );
-
- try {
- await spawnAsync("git", [
- "clone",
- "--branch",
- giteaBranch!,
- "--depth",
- "1",
- ...(enableSubmodules ? ["--recurse-submodules"] : []),
- cloneUrl,
- outputPath,
- "--progress",
- ]);
- } catch (error) {
- throw error;
- }
-};
-
-export const cloneRawGiteaRepositoryRemote = async (compose: Compose) => {
- const {
- appName,
- giteaRepository,
- giteaOwner,
- giteaBranch,
- giteaId,
- serverId,
- enableSubmodules,
- } = compose;
-
- if (!serverId) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Server not found",
- });
- }
- if (!giteaId) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Gitea Provider not found",
- });
- }
- const { COMPOSE_PATH } = paths(true);
- const giteaProvider = await findGiteaById(giteaId);
- const basePath = COMPOSE_PATH;
- const outputPath = join(basePath, appName, "code");
- const cloneUrl = buildGiteaCloneUrl(
- giteaProvider.giteaUrl,
- giteaProvider.accessToken!,
- giteaOwner!,
- giteaRepository!,
- );
-
- try {
- const command = `
- rm -rf ${outputPath};
- git clone --branch ${giteaBranch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} ${cloneUrl} ${outputPath}
- `;
- await execAsyncRemote(serverId, command);
- } catch (error) {
- throw error;
- }
+ command += `echo "Cloning Repo ${repoClone} to ${outputPath}: β
";`;
+ command += `git clone --branch ${giteaBranch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} ${cloneUrl} ${outputPath} --progress;`;
+ return command;
};
export const haveGiteaRequirements = (giteaProvider: Gitea) => {
diff --git a/packages/server/src/utils/providers/github.ts b/packages/server/src/utils/providers/github.ts
index 30125db8b..5b7763df7 100644
--- a/packages/server/src/utils/providers/github.ts
+++ b/packages/server/src/utils/providers/github.ts
@@ -1,16 +1,11 @@
-import { createWriteStream } from "node:fs";
import { join } from "node:path";
import { paths } from "@dokploy/server/constants";
import type { apiFindGithubBranches } from "@dokploy/server/db/schema";
-import type { Compose } from "@dokploy/server/services/compose";
import { findGithubById, type Github } from "@dokploy/server/services/github";
import type { InferResultType } from "@dokploy/server/types/with";
import { createAppAuth } from "@octokit/auth-app";
import { TRPCError } from "@trpc/server";
import { Octokit } from "octokit";
-import { recreateDirectory } from "../filesystem/directory";
-import { execAsyncRemote } from "../process/execAsync";
-import { spawnAsync } from "../process/spawnAsync";
export const authGithub = (githubProvider: Github): Octokit => {
if (!haveGithubRequirements(githubProvider)) {
@@ -123,42 +118,39 @@ interface CloneGithubRepository {
branch: string | null;
githubId: string | null;
repository: string | null;
- logPath: string;
type?: "application" | "compose";
enableSubmodules: boolean;
+ serverId: string | null;
}
export const cloneGithubRepository = async ({
- logPath,
type = "application",
...entity
}: CloneGithubRepository) => {
+ let command = "set -e;";
const isCompose = type === "compose";
- const { APPLICATIONS_PATH, COMPOSE_PATH } = paths();
- const writeStream = createWriteStream(logPath, { flags: "a" });
- const { appName, repository, owner, branch, githubId, enableSubmodules } =
- entity;
+ const {
+ appName,
+ repository,
+ owner,
+ branch,
+ githubId,
+ enableSubmodules,
+ serverId,
+ } = entity;
+ const { APPLICATIONS_PATH, COMPOSE_PATH } = paths(!!serverId);
if (!githubId) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "GitHub Provider not found",
- });
+ command += `echo "Error: β Github Provider not found"; exit 1;`;
+
+ return command;
}
const requirements = getErrorCloneRequirements(entity);
// Check if requirements are met
if (requirements.length > 0) {
- writeStream.write(
- `\nGitHub Repository configuration failed for application: ${appName}\n`,
- );
- writeStream.write("Reasons:\n");
- writeStream.write(requirements.join("\n"));
- writeStream.end();
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error: GitHub repository information is incomplete.",
- });
+ command += `echo "GitHub Repository configuration failed for application: ${appName}"; echo "Reasons:"; echo "${requirements.join("\n")}"; exit 1;`;
+ return command;
}
const githubProvider = await findGithubById(githubId);
@@ -167,193 +159,14 @@ export const cloneGithubRepository = async ({
const octokit = authGithub(githubProvider);
const token = await getGithubToken(octokit);
const repoclone = `github.com/${owner}/${repository}.git`;
- await recreateDirectory(outputPath);
+ command += `rm -rf ${outputPath};`;
+ command += `mkdir -p ${outputPath};`;
const cloneUrl = `https://oauth2:${token}@${repoclone}`;
- try {
- writeStream.write(`\nCloning Repo ${repoclone} to ${outputPath}: β
\n`);
- const cloneArgs = [
- "clone",
- "--branch",
- branch!,
- "--depth",
- "1",
- ...(enableSubmodules ? ["--recurse-submodules"] : []),
- cloneUrl,
- outputPath,
- "--progress",
- ];
+ command += `echo "Cloning Repo ${repoclone} to ${outputPath}: β
";`;
+ command += `git clone --branch ${branch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} ${cloneUrl} ${outputPath} --progress;`;
- await spawnAsync("git", cloneArgs, (data) => {
- if (writeStream.writable) {
- writeStream.write(data);
- }
- });
- writeStream.write(`\nCloned ${repoclone}: β
\n`);
- } catch (error) {
- writeStream.write(`ERROR Cloning: ${error}: β`);
- throw error;
- } finally {
- writeStream.end();
- }
-};
-
-export const getGithubCloneCommand = async ({
- logPath,
- type = "application",
- ...entity
-}: CloneGithubRepository & { serverId: string }) => {
- const {
- appName,
- repository,
- owner,
- branch,
- githubId,
- serverId,
- enableSubmodules,
- } = entity;
- const isCompose = type === "compose";
- if (!serverId) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Server not found",
- });
- }
-
- if (!githubId) {
- const command = `
- echo "Error: β Github Provider not found" >> ${logPath};
- exit 1;
- `;
-
- await execAsyncRemote(serverId, command);
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "GitHub Provider not found",
- });
- }
-
- const requirements = getErrorCloneRequirements(entity);
-
- // Build log messages
- let logMessages = "";
- if (requirements.length > 0) {
- logMessages += `\nGitHub Repository configuration failed for application: ${appName}\n`;
- logMessages += "Reasons:\n";
- logMessages += requirements.join("\n");
- const escapedLogMessages = logMessages
- .replace(/\\/g, "\\\\")
- .replace(/"/g, '\\"')
- .replace(/\n/g, "\\n");
-
- const bashCommand = `
- echo "${escapedLogMessages}" >> ${logPath};
- exit 1; # Exit with error code
- `;
-
- await execAsyncRemote(serverId, bashCommand);
- return;
- }
- const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(true);
- const githubProvider = await findGithubById(githubId);
- const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
- const outputPath = join(basePath, appName, "code");
- const octokit = authGithub(githubProvider);
- const token = await getGithubToken(octokit);
- const repoclone = `github.com/${owner}/${repository}.git`;
- const cloneUrl = `https://oauth2:${token}@${repoclone}`;
-
- const cloneCommand = `
-rm -rf ${outputPath};
-mkdir -p ${outputPath};
-if ! git clone --branch ${branch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} --progress ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then
- echo "β [ERROR] Fail to clone repository ${repoclone}" >> ${logPath};
- exit 1;
-fi
-echo "Cloned ${repoclone} to ${outputPath}: β
" >> ${logPath};
- `;
-
- return cloneCommand;
-};
-
-export const cloneRawGithubRepository = async (entity: Compose) => {
- const { appName, repository, owner, branch, githubId, enableSubmodules } =
- entity;
-
- if (!githubId) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "GitHub Provider not found",
- });
- }
- const { COMPOSE_PATH } = paths();
- const githubProvider = await findGithubById(githubId);
- const basePath = COMPOSE_PATH;
- const outputPath = join(basePath, appName, "code");
- const octokit = authGithub(githubProvider);
- const token = await getGithubToken(octokit);
- const repoclone = `github.com/${owner}/${repository}.git`;
- await recreateDirectory(outputPath);
- const cloneUrl = `https://oauth2:${token}@${repoclone}`;
- try {
- const cloneArgs = [
- "clone",
- "--branch",
- branch!,
- "--depth",
- "1",
- ...(enableSubmodules ? ["--recurse-submodules"] : []),
- cloneUrl,
- outputPath,
- "--progress",
- ];
- await spawnAsync("git", cloneArgs);
- } catch (error) {
- throw error;
- }
-};
-
-export const cloneRawGithubRepositoryRemote = async (compose: Compose) => {
- const {
- appName,
- repository,
- owner,
- branch,
- githubId,
- serverId,
- enableSubmodules,
- } = compose;
-
- if (!serverId) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Server not found",
- });
- }
- if (!githubId) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "GitHub Provider not found",
- });
- }
-
- const { COMPOSE_PATH } = paths(true);
- const githubProvider = await findGithubById(githubId);
- const basePath = COMPOSE_PATH;
- const outputPath = join(basePath, appName, "code");
- const octokit = authGithub(githubProvider);
- const token = await getGithubToken(octokit);
- const repoclone = `github.com/${owner}/${repository}.git`;
- const cloneUrl = `https://oauth2:${token}@${repoclone}`;
- try {
- const command = `
- rm -rf ${outputPath};
- git clone --branch ${branch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} ${cloneUrl} ${outputPath}
- `;
- await execAsyncRemote(serverId, command);
- } catch (error) {
- throw error;
- }
+ return command;
};
export const getGithubRepositories = async (githubId?: string) => {
diff --git a/packages/server/src/utils/providers/gitlab.ts b/packages/server/src/utils/providers/gitlab.ts
index 840347fdb..3343c9bb6 100644
--- a/packages/server/src/utils/providers/gitlab.ts
+++ b/packages/server/src/utils/providers/gitlab.ts
@@ -1,8 +1,6 @@
-import { createWriteStream } from "node:fs";
import { join } from "node:path";
import { paths } from "@dokploy/server/constants";
import type { apiGitlabTestConnection } from "@dokploy/server/db/schema";
-import type { Compose } from "@dokploy/server/services/compose";
import {
findGitlabById,
type Gitlab,
@@ -10,9 +8,6 @@ import {
} from "@dokploy/server/services/gitlab";
import type { InferResultType } from "@dokploy/server/types/with";
import { TRPCError } from "@trpc/server";
-import { recreateDirectory } from "../filesystem/directory";
-import { execAsyncRemote } from "../process/execAsync";
-import { spawnAsync } from "../process/spawnAsync";
export const refreshGitlabToken = async (gitlabProviderId: string) => {
const gitlabProvider = await findGitlabById(gitlabProviderId);
@@ -102,25 +97,34 @@ const getGitlabCloneUrl = (gitlab: GitlabInfo, repoClone: string) => {
return cloneUrl;
};
-export const cloneGitlabRepository = async (
- entity: ApplicationWithGitlab | ComposeWithGitlab,
- logPath: string,
- isCompose = false,
-) => {
- const writeStream = createWriteStream(logPath, { flags: "a" });
+interface CloneGitlabRepository {
+ appName: string;
+ gitlabBranch: string | null;
+ gitlabId: string | null;
+ gitlabPathNamespace: string | null;
+ enableSubmodules: boolean;
+ serverId: string | null;
+ type?: "application" | "compose";
+}
+
+export const cloneGitlabRepository = async ({
+ type = "application",
+ ...entity
+}: CloneGitlabRepository) => {
+ let command = "set -e;";
const {
appName,
gitlabBranch,
gitlabId,
gitlabPathNamespace,
enableSubmodules,
+ serverId,
} = entity;
+ const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(!!serverId);
if (!gitlabId) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Gitlab Provider not found",
- });
+ command += `echo "Error: β Gitlab Provider not found"; exit 1;`;
+ return command;
}
await refreshGitlabToken(gitlabId);
@@ -130,127 +134,19 @@ export const cloneGitlabRepository = async (
// Check if requirements are met
if (requirements.length > 0) {
- writeStream.write(
- `\nGitLab Repository configuration failed for application: ${appName}\n`,
- );
- writeStream.write("Reasons:\n");
- writeStream.write(requirements.join("\n"));
- writeStream.end();
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error: GitLab repository information is incomplete.",
- });
+ command += `echo "β [ERROR] GitLab Repository configuration failed for application: ${appName}"; echo "Reasons:"; echo "${requirements.join("\n")}"; exit 1;`;
+ return command;
}
- const { COMPOSE_PATH, APPLICATIONS_PATH } = paths();
- const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
+ const basePath = type === "compose" ? COMPOSE_PATH : APPLICATIONS_PATH;
const outputPath = join(basePath, appName, "code");
- await recreateDirectory(outputPath);
+ command += `rm -rf ${outputPath};`;
+ command += `mkdir -p ${outputPath};`;
const repoClone = getGitlabRepoClone(gitlab, gitlabPathNamespace);
const cloneUrl = getGitlabCloneUrl(gitlab, repoClone);
- try {
- writeStream.write(`\nCloning Repo ${repoClone} to ${outputPath}: β
\n`);
- const cloneArgs = [
- "clone",
- "--branch",
- gitlabBranch!,
- "--depth",
- "1",
- ...(enableSubmodules ? ["--recurse-submodules"] : []),
- cloneUrl,
- outputPath,
- "--progress",
- ];
-
- await spawnAsync("git", cloneArgs, (data) => {
- if (writeStream.writable) {
- writeStream.write(data);
- }
- });
- writeStream.write(`\nCloned ${repoClone}: β
\n`);
- } catch (error) {
- writeStream.write(`ERROR Cloning: ${error}: β`);
- throw error;
- } finally {
- writeStream.end();
- }
-};
-
-export const getGitlabCloneCommand = async (
- entity: ApplicationWithGitlab | ComposeWithGitlab,
- logPath: string,
- isCompose = false,
-) => {
- const {
- appName,
- gitlabPathNamespace,
- gitlabBranch,
- gitlabId,
- serverId,
- enableSubmodules,
- } = entity;
-
- if (!serverId) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Server not found",
- });
- }
-
- if (!gitlabId) {
- const command = `
- echo "Error: β Gitlab Provider not found" >> ${logPath};
- exit 1;
- `;
-
- await execAsyncRemote(serverId, command);
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Gitlab Provider not found",
- });
- }
-
- const requirements = getErrorCloneRequirements(entity);
-
- // Build log messages
- let logMessages = "";
- if (requirements.length > 0) {
- logMessages += `\nGitLab Repository configuration failed for application: ${appName}\n`;
- logMessages += "Reasons:\n";
- logMessages += requirements.join("\n");
- const escapedLogMessages = logMessages
- .replace(/\\/g, "\\\\")
- .replace(/"/g, '\\"')
- .replace(/\n/g, "\\n");
-
- const bashCommand = `
- echo "${escapedLogMessages}" >> ${logPath};
- exit 1; # Exit with error code
- `;
-
- await execAsyncRemote(serverId, bashCommand);
- return;
- }
-
- const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(true);
- await refreshGitlabToken(gitlabId);
- const gitlab = await findGitlabById(gitlabId);
- const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
- const outputPath = join(basePath, appName, "code");
- await recreateDirectory(outputPath);
- const repoClone = getGitlabRepoClone(gitlab, gitlabPathNamespace);
- const cloneUrl = getGitlabCloneUrl(gitlab, repoClone);
- const cloneCommand = `
-rm -rf ${outputPath};
-mkdir -p ${outputPath};
-if ! git clone --branch ${gitlabBranch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} --progress ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then
- echo "β [ERROR] Fail to clone the repository ${repoClone}" >> ${logPath};
- exit 1;
-fi
-echo "Cloned ${repoClone} to ${outputPath}: β
" >> ${logPath};
- `;
-
- return cloneCommand;
+ command += `echo "Cloning Repo ${repoClone} to ${outputPath}: β
";`;
+ command += `git clone --branch ${gitlabBranch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} ${cloneUrl} ${outputPath} --progress;`;
+ return command;
};
export const getGitlabRepositories = async (gitlabId?: string) => {
@@ -355,88 +251,6 @@ export const getGitlabBranches = async (input: {
}[];
};
-export const cloneRawGitlabRepository = async (entity: Compose) => {
- const {
- appName,
- gitlabBranch,
- gitlabId,
- gitlabPathNamespace,
- enableSubmodules,
- } = entity;
-
- if (!gitlabId) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Gitlab Provider not found",
- });
- }
-
- const { COMPOSE_PATH } = paths();
- await refreshGitlabToken(gitlabId);
- const gitlabProvider = await findGitlabById(gitlabId);
- const basePath = COMPOSE_PATH;
- const outputPath = join(basePath, appName, "code");
- await recreateDirectory(outputPath);
- const repoClone = getGitlabRepoClone(gitlabProvider, gitlabPathNamespace);
- const cloneUrl = getGitlabCloneUrl(gitlabProvider, repoClone);
- try {
- const cloneArgs = [
- "clone",
- "--branch",
- gitlabBranch!,
- "--depth",
- "1",
- ...(enableSubmodules ? ["--recurse-submodules"] : []),
- cloneUrl,
- outputPath,
- "--progress",
- ];
- await spawnAsync("git", cloneArgs);
- } catch (error) {
- throw error;
- }
-};
-
-export const cloneRawGitlabRepositoryRemote = async (compose: Compose) => {
- const {
- appName,
- gitlabPathNamespace,
- gitlabBranch,
- gitlabId,
- serverId,
- enableSubmodules,
- } = compose;
-
- if (!serverId) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Server not found",
- });
- }
- if (!gitlabId) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Gitlab Provider not found",
- });
- }
- const { COMPOSE_PATH } = paths(true);
- await refreshGitlabToken(gitlabId);
- const gitlabProvider = await findGitlabById(gitlabId);
- const basePath = COMPOSE_PATH;
- const outputPath = join(basePath, appName, "code");
- const repoClone = getGitlabRepoClone(gitlabProvider, gitlabPathNamespace);
- const cloneUrl = getGitlabCloneUrl(gitlabProvider, repoClone);
- try {
- const command = `
- rm -rf ${outputPath};
- git clone --branch ${gitlabBranch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} ${cloneUrl} ${outputPath}
- `;
- await execAsyncRemote(serverId, command);
- } catch (error) {
- throw error;
- }
-};
-
export const testGitlabConnection = async (
input: typeof apiGitlabTestConnection._type,
) => {
@@ -476,7 +290,7 @@ export const validateGitlabProvider = async (gitlabProvider: Gitlab) => {
while (true) {
const response = await fetch(
- `${gitlabProvider.gitlabUrl}/api/v4/projects?membership=true&owned=true&page=${page}&per_page=${perPage}`,
+ `${gitlabProvider.gitlabUrl}/api/v4/projects?membership=true&page=${page}&per_page=${perPage}`,
{
headers: {
Authorization: `Bearer ${gitlabProvider.accessToken}`,
diff --git a/packages/server/src/utils/providers/raw.ts b/packages/server/src/utils/providers/raw.ts
index 34ba0012a..508df86ed 100644
--- a/packages/server/src/utils/providers/raw.ts
+++ b/packages/server/src/utils/providers/raw.ts
@@ -1,40 +1,10 @@
-import { createWriteStream } from "node:fs";
-import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { paths } from "@dokploy/server/constants";
import type { Compose } from "@dokploy/server/services/compose";
import { encodeBase64 } from "../docker/utils";
-import { recreateDirectory } from "../filesystem/directory";
-import { execAsyncRemote } from "../process/execAsync";
-export const createComposeFile = async (compose: Compose, logPath: string) => {
- const { COMPOSE_PATH } = paths();
- const { appName, composeFile } = compose;
- const writeStream = createWriteStream(logPath, { flags: "a" });
- const outputPath = join(COMPOSE_PATH, appName, "code");
-
- try {
- await recreateDirectory(outputPath);
- writeStream.write(
- `\nCreating File 'docker-compose.yml' to ${outputPath}: β
\n`,
- );
-
- await writeFile(join(outputPath, "docker-compose.yml"), composeFile);
-
- writeStream.write(`\nFile 'docker-compose.yml' created: β
\n`);
- } catch (error) {
- writeStream.write(`\nERROR Creating Compose File: ${error}: β\n`);
- throw error;
- } finally {
- writeStream.end();
- }
-};
-
-export const getCreateComposeFileCommand = (
- compose: Compose,
- logPath: string,
-) => {
- const { COMPOSE_PATH } = paths(true);
+export const getCreateComposeFileCommand = (compose: Compose) => {
+ const { COMPOSE_PATH } = paths(!!compose.serverId);
const { appName, composeFile } = compose;
const outputPath = join(COMPOSE_PATH, appName, "code");
const filePath = join(outputPath, "docker-compose.yml");
@@ -43,39 +13,7 @@ export const getCreateComposeFileCommand = (
rm -rf ${outputPath};
mkdir -p ${outputPath};
echo "${encodedContent}" | base64 -d > "${filePath}";
- echo "File 'docker-compose.yml' created: β
" >> ${logPath};
+ echo "File 'docker-compose.yml' created: β
";
`;
return bashCommand;
};
-
-export const createComposeFileRaw = async (compose: Compose) => {
- const { COMPOSE_PATH } = paths();
- const { appName, composeFile } = compose;
- const outputPath = join(COMPOSE_PATH, appName, "code");
- const filePath = join(outputPath, "docker-compose.yml");
- try {
- await recreateDirectory(outputPath);
- await writeFile(filePath, composeFile);
- } catch (error) {
- throw error;
- }
-};
-
-export const createComposeFileRawRemote = async (compose: Compose) => {
- const { COMPOSE_PATH } = paths(true);
- const { appName, composeFile, serverId } = compose;
- const outputPath = join(COMPOSE_PATH, appName, "code");
- const filePath = join(outputPath, "docker-compose.yml");
-
- try {
- const encodedContent = encodeBase64(composeFile);
- const command = `
- rm -rf ${outputPath};
- mkdir -p ${outputPath};
- echo "${encodedContent}" | base64 -d > "${filePath}";
- `;
- await execAsyncRemote(serverId, command);
- } catch (error) {
- throw error;
- }
-};
diff --git a/packages/server/src/utils/restore/utils.ts b/packages/server/src/utils/restore/utils.ts
index c46077238..23052e642 100644
--- a/packages/server/src/utils/restore/utils.ts
+++ b/packages/server/src/utils/restore/utils.ts
@@ -7,7 +7,7 @@ export const getPostgresRestoreCommand = (
database: string,
databaseUser: string,
) => {
- return `docker exec -i $CONTAINER_ID sh -c "pg_restore -U ${databaseUser} -d ${database} -O --clean --if-exists"`;
+ return `docker exec -i $CONTAINER_ID sh -c "pg_restore -U '${databaseUser}' -d ${database} -O --clean --if-exists"`;
};
export const getMariadbRestoreCommand = (
@@ -15,14 +15,14 @@ export const getMariadbRestoreCommand = (
databaseUser: string,
databasePassword: string,
) => {
- return `docker exec -i $CONTAINER_ID sh -c "mariadb -u ${databaseUser} -p${databasePassword} ${database}"`;
+ return `docker exec -i $CONTAINER_ID sh -c "mariadb -u '${databaseUser}' -p'${databasePassword}' ${database}"`;
};
export const getMysqlRestoreCommand = (
database: string,
databasePassword: string,
) => {
- return `docker exec -i $CONTAINER_ID sh -c "mysql -u root -p${databasePassword} ${database}"`;
+ return `docker exec -i $CONTAINER_ID sh -c "mysql -u root -p'${databasePassword}' ${database}"`;
};
export const getMongoRestoreCommand = (
@@ -30,7 +30,7 @@ export const getMongoRestoreCommand = (
databaseUser: string,
databasePassword: string,
) => {
- return `docker exec -i $CONTAINER_ID sh -c "mongorestore --username ${databaseUser} --password ${databasePassword} --authenticationDatabase admin --db ${database} --archive"`;
+ return `docker exec -i $CONTAINER_ID sh -c "mongorestore --username '${databaseUser}' --password '${databasePassword}' --authenticationDatabase admin --db ${database} --archive"`;
};
export const getComposeSearchCommand = (
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1aae074f7..a03f77f4c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -347,8 +347,8 @@ importers:
specifier: ^0.2.1
version: 0.2.1(next@15.3.2(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
node-os-utils:
- specifier: 1.3.7
- version: 1.3.7
+ specifier: 2.0.1
+ version: 2.0.1
node-pty:
specifier: 1.0.0
version: 1.0.0
@@ -406,6 +406,9 @@ importers:
rotating-file-stream:
specifier: 3.2.3
version: 3.2.3
+ shell-quote:
+ specifier: ^1.8.1
+ version: 1.8.2
slugify:
specifier: ^1.6.6
version: 1.6.6
@@ -473,9 +476,6 @@ importers:
'@types/node':
specifier: ^18.19.104
version: 18.19.104
- '@types/node-os-utils':
- specifier: 1.3.4
- version: 1.3.4
'@types/node-schedule':
specifier: 2.1.6
version: 2.1.6
@@ -491,6 +491,9 @@ importers:
'@types/react-dom':
specifier: 18.3.0
version: 18.3.0
+ '@types/shell-quote':
+ specifier: ^1.7.5
+ version: 1.7.5
'@types/ssh2':
specifier: 1.15.1
version: 1.15.1
@@ -688,8 +691,8 @@ importers:
specifier: 3.3.11
version: 3.3.11
node-os-utils:
- specifier: 1.3.7
- version: 1.3.7
+ specifier: 2.0.1
+ version: 2.0.1
node-pty:
specifier: 1.0.0
version: 1.0.0
@@ -729,6 +732,9 @@ importers:
rotating-file-stream:
specifier: 3.2.3
version: 3.2.3
+ shell-quote:
+ specifier: ^1.8.1
+ version: 1.8.2
slugify:
specifier: ^1.6.6
version: 1.6.6
@@ -766,9 +772,6 @@ importers:
'@types/node':
specifier: ^18.19.104
version: 18.19.104
- '@types/node-os-utils':
- specifier: 1.3.4
- version: 1.3.4
'@types/node-schedule':
specifier: 2.1.6
version: 2.1.6
@@ -784,6 +787,9 @@ importers:
'@types/react-dom':
specifier: 18.3.0
version: 18.3.0
+ '@types/shell-quote':
+ specifier: ^1.7.5
+ version: 1.7.5
'@types/ssh2':
specifier: 1.15.1
version: 1.15.1
@@ -4000,9 +4006,6 @@ packages:
'@types/mysql@2.15.26':
resolution: {integrity: sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==}
- '@types/node-os-utils@1.3.4':
- resolution: {integrity: sha512-BCUYrbdoO4FUbx6MB9atLNFnkxdliFaxdiTJMIPPiecXIApc5zf4NIqV5G1jWv/ReZvtYyHLs40RkBjHX+vykA==}
-
'@types/node-schedule@2.1.6':
resolution: {integrity: sha512-6AlZSUiNTdaVmH5jXYxX9YgmF1zfOlbjUqw0EllTBmZCnN1R5RR/m/u3No1OiWR05bnQ4jM4/+w4FcGvkAtnKQ==}
@@ -4042,6 +4045,9 @@ packages:
'@types/readable-stream@4.0.20':
resolution: {integrity: sha512-eLgbR5KwUh8+6pngBDxS32MymdCsCHnGtwHTrC0GDorbc7NbcnkZAWptDLgZiRk9VRas+B6TyRgPDucq4zRs8g==}
+ '@types/shell-quote@1.7.5':
+ resolution: {integrity: sha512-+UE8GAGRPbJVQDdxi16dgadcBfQ+KG2vgZhV1+3A1XmHbmwcdwhCUwIdy+d3pAGrbvgRoVSjeI9vOWyq376Yzw==}
+
'@types/shimmer@1.2.0':
resolution: {integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==}
@@ -6312,8 +6318,9 @@ packages:
'@types/node':
optional: true
- node-os-utils@1.3.7:
- resolution: {integrity: sha512-fvnX9tZbR7WfCG5BAy3yO/nCLyjVWD6MghEq0z5FDfN+ZXpLWNITBdbifxQkQ25ebr16G0N7eRWJisOcMEHG3Q==}
+ node-os-utils@2.0.1:
+ resolution: {integrity: sha512-rH2N3qHZETLhdgTGhMMCE8zU3gsWO4we1MFtrSiAI7tYWrnJRc6dk2PseV4co3Lb0v/MbRONLQI2biHQYbpTpg==}
+ engines: {node: '>=18.0.0'}
node-pty@1.0.0:
resolution: {integrity: sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==}
@@ -11338,8 +11345,6 @@ snapshots:
dependencies:
'@types/node': 20.17.51
- '@types/node-os-utils@1.3.4': {}
-
'@types/node-schedule@2.1.6':
dependencies:
'@types/node': 20.17.51
@@ -11393,6 +11398,8 @@ snapshots:
dependencies:
'@types/node': 20.17.51
+ '@types/shell-quote@1.7.5': {}
+
'@types/shimmer@1.2.0': {}
'@types/ssh2@1.15.1':
@@ -13852,7 +13859,7 @@ snapshots:
optionalDependencies:
'@types/node': 18.19.104
- node-os-utils@1.3.7: {}
+ node-os-utils@2.0.1: {}
node-pty@1.0.0:
dependencies: