From cbb3450b9346c40c83b5209c6fdc51f1d89524c8 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Sun, 2 Aug 2026 16:15:20 -0600 Subject: [PATCH] fix(schedule): return deployment metadata from runManually and fail early on missing container --- .../application/schedules/show-schedules.tsx | 8 +- apps/dokploy/server/api/routers/schedule.ts | 8 +- packages/server/src/utils/schedules/utils.ts | 142 ++++++++++-------- 3 files changed, 90 insertions(+), 68 deletions(-) diff --git a/apps/dokploy/components/dashboard/application/schedules/show-schedules.tsx b/apps/dokploy/components/dashboard/application/schedules/show-schedules.tsx index b1e8a74f8..3c76fb969 100644 --- a/apps/dokploy/components/dashboard/application/schedules/show-schedules.tsx +++ b/apps/dokploy/components/dashboard/application/schedules/show-schedules.tsx @@ -58,8 +58,12 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => { const handleRunManually = async (scheduleId: string) => { setRunningSchedules((prev) => new Set(prev).add(scheduleId)); try { - await runManually({ scheduleId }); - toast.success("Schedule run successfully"); + const result = await runManually({ scheduleId }); + if (result.status === "error") { + toast.error("Schedule run failed, check the deployment logs"); + } else { + toast.success("Schedule run successfully"); + } await refetchSchedules(); } catch { toast.error("Error running schedule"); diff --git a/apps/dokploy/server/api/routers/schedule.ts b/apps/dokploy/server/api/routers/schedule.ts index 5be4d7b32..d69760124 100644 --- a/apps/dokploy/server/api/routers/schedule.ts +++ b/apps/dokploy/server/api/routers/schedule.ts @@ -329,13 +329,17 @@ export const scheduleRouter = createTRPCRouter({ await checkPermission(ctx, { schedule: ["create"] }); } try { - await runCommand(input.scheduleId); + const deployment = await runCommand(input.scheduleId); await audit(ctx, { action: "run", resourceType: "schedule", resourceId: input.scheduleId, }); - return true; + return { + status: deployment.status, + deploymentId: deployment.deploymentId, + logPath: deployment.logPath, + }; } catch (error) { throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", diff --git a/packages/server/src/utils/schedules/utils.ts b/packages/server/src/utils/schedules/utils.ts index 8107839d8..40bc0816c 100644 --- a/packages/server/src/utils/schedules/utils.ts +++ b/packages/server/src/utils/schedules/utils.ts @@ -55,25 +55,46 @@ export const runCommand = async (scheduleId: string) => { description: "Schedule", }); - if (scheduleType === "application" || scheduleType === "compose") { - let containerId = ""; - let serverId = ""; - if (scheduleType === "application" && application) { - const container = await getServiceContainer( - application.appName, - application.serverId, - ); - containerId = container?.Id || ""; - serverId = application.serverId || ""; - } - if (scheduleType === "compose" && compose) { - const container = await getComposeContainer(compose, serviceName || ""); - containerId = container?.Id || ""; - serverId = compose.serverId || ""; - } + try { + if (scheduleType === "application" || scheduleType === "compose") { + let containerId = ""; + let serverId = ""; + if (scheduleType === "application" && application) { + const container = await getServiceContainer( + application.appName, + application.serverId, + ); + containerId = container?.Id || ""; + serverId = application.serverId || ""; + } + if (scheduleType === "compose" && compose) { + const container = await getComposeContainer(compose, serviceName || ""); + containerId = container?.Id || ""; + serverId = compose.serverId || ""; + } - if (serverId) { - try { + if (!containerId) { + const target = + scheduleType === "compose" + ? `service '${serviceName}' of compose '${compose?.name}'` + : `application '${application?.appName}'`; + const message = `Container not found for ${target}, make sure the service is running`; + if (serverId) { + await execAsyncRemote( + serverId, + `echo ${quote([`❌ ${message}`])} >> ${quote([deployment.logPath])}`, + ); + } else { + const writeStream = createWriteStream(deployment.logPath, { + flags: "a", + }); + writeStream.write(`❌ ${message}\n`); + writeStream.end(); + } + throw new Error(message); + } + + if (serverId) { await execAsyncRemote( serverId, ` @@ -86,47 +107,44 @@ export const runCommand = async (scheduleId: string) => { echo "✅ Command executed successfully" >> ${quote([deployment.logPath])}; `, ); - } catch (error) { - await updateDeploymentStatus(deployment.deploymentId, "error"); - throw error; - } - } else { - const writeStream = createWriteStream(deployment.logPath, { flags: "a" }); + } else { + const writeStream = createWriteStream(deployment.logPath, { + flags: "a", + }); - try { - if (IS_CLOUD) { + try { + if (IS_CLOUD) { + writeStream.write( + "This feature is not available in the cloud version.", + ); + writeStream.end(); + return { ...deployment, status: "running" as const }; + } writeStream.write( - "This feature is not available in the cloud version.", + `docker exec ${containerId} ${shellType} -c ${command}\n`, + ); + await spawnAsync( + "docker", + ["exec", containerId, shellType, "-c", command], + (data) => { + if (writeStream.writable) { + writeStream.write(data); + } + }, + ); + + writeStream.write("✅ Command executed successfully\n"); + writeStream.end(); + } catch (error) { + writeStream.write("❌ Command failed\n"); + writeStream.write( + error instanceof Error ? error.message : "Unknown error", ); writeStream.end(); - return; + throw error; } - writeStream.write( - `docker exec ${containerId} ${shellType} -c ${command}\n`, - ); - await spawnAsync( - "docker", - ["exec", containerId, shellType, "-c", command], - (data) => { - if (writeStream.writable) { - writeStream.write(data); - } - }, - ); - - writeStream.write("✅ Command executed successfully\n"); - } catch (error) { - writeStream.write("❌ Command failed\n"); - writeStream.write( - error instanceof Error ? error.message : "Unknown error", - ); - writeStream.end(); - await updateDeploymentStatus(deployment.deploymentId, "error"); - throw error; } - } - } else if (scheduleType === "dokploy-server") { - try { + } else if (scheduleType === "dokploy-server") { const writeStream = createWriteStream(deployment.logPath, { flags: "a" }); const { SCHEDULES_PATH } = paths(); const fullPath = path.join(SCHEDULES_PATH, appName || ""); @@ -151,18 +169,13 @@ export const runCommand = async (scheduleId: string) => { cwd: fullPath, }, ); - } catch (error) { - await updateDeploymentStatus(deployment.deploymentId, "error"); - throw error; - } - } else if (scheduleType === "server") { - try { + } else if (scheduleType === "server") { const { SCHEDULES_PATH } = paths(true); const fullPath = path.join(SCHEDULES_PATH, appName || ""); const command = ` set -e echo "Running script" >> ${deployment.logPath}; - bash -c ${fullPath}/script.sh 2>&1 | tee -a ${deployment.logPath} || { + bash -c ${fullPath}/script.sh 2>&1 | tee -a ${deployment.logPath} || { echo "❌ Command failed" >> ${deployment.logPath}; exit 1; } @@ -177,10 +190,11 @@ export const runCommand = async (scheduleId: string) => { }); } }); - } catch (error) { - await updateDeploymentStatus(deployment.deploymentId, "error"); - throw error; } + await updateDeploymentStatus(deployment.deploymentId, "done"); + return { ...deployment, status: "done" as const }; + } catch { + await updateDeploymentStatus(deployment.deploymentId, "error"); + return { ...deployment, status: "error" as const }; } - await updateDeploymentStatus(deployment.deploymentId, "done"); };