Compare commits

...

17 Commits

Author SHA1 Message Date
Mauricio Siu
2fe7349889 chore: update version in package.json to v0.23.6 2025-07-01 23:18:44 -06:00
Mauricio Siu
5c1c969873 Merge pull request #2113 from Dokploy/fix/schedules-rm
fix: prevent removal of current directory in deployment logs
2025-07-01 23:18:15 -06:00
Mauricio Siu
2f6f1b19e7 fix: prevent removal of current directory in deployment logs
- Updated the removeLastTenDeployments function to check if logPath is not the current directory before executing the removal command.
- Enhanced the unlink operation to ensure it only attempts to delete valid log paths.
2025-07-01 23:17:27 -06:00
Mauricio Siu
03e04b7bce Merge pull request #2105 from Dokploy/fix/cancel-schedules
feat: add kill process functionality to schedules
2025-06-29 21:13:37 -06:00
Mauricio Siu
b920e7c0f1 fix: handle potential null data in PID extraction for runCommand function 2025-06-29 21:10:11 -06:00
Mauricio Siu
15fde820a3 Merge pull request #2104 from DearTanker/canary
Style: Make Node Applications more readable.
2025-06-29 21:09:29 -06:00
Mauricio Siu
64293fce79 feat: add kill process functionality to deployments
- Implemented a new mutation to kill a running deployment process by its PID.
- Updated the deployment schema to include a PID field.
- Enhanced the deployment service to handle process termination and status updates.
- Modified the deployment scripts to echo PID and schedule ID for better tracking.
- Added error handling for the kill process operation.
2025-06-29 21:08:51 -06:00
DearTanker
526f249d0e Style: Make Node Applications more readable 2025-06-30 10:54:45 +08:00
Mauricio Siu
4796b0cf4e chore: update version in package.json to v0.23.5 2025-06-29 14:09:31 -06:00
Mauricio Siu
159a055bc6 Merge pull request #2102 from Dokploy/2092-preview-link-without-protocol
fix: update preview deployment comment to include protocol in domain URL
2025-06-29 13:47:30 -06:00
Mauricio Siu
cfade317f1 fix: update preview deployment comment to include protocol in domain URL 2025-06-29 13:47:17 -06:00
Mauricio Siu
36ebefff16 Merge pull request #2083 from Marukome0743/models
chore: remove unknown `apps/models` from pnpm-workspace.yaml
2025-06-29 13:24:33 -06:00
Mauricio Siu
7cc0603078 Merge pull request #2101 from SashkaHavr/fix-docker-config-path
fix: change default DOCKER_CONFIG to a config directory instead of config.json file
2025-06-29 13:24:08 -06:00
Mauricio Siu
e0e42ac554 Merge pull request #2100 from DearTanker/canary
style: Remove the width restriction on the server name.
2025-06-29 12:44:12 -06:00
Oleksandr Havrylov
e004d8bd52 fix: change default DOCKER_CONFIG to a config directory instead of config.json file 2025-06-29 17:14:50 +02:00
DearTanker
0c0912f606 Style: Remove the width restriction on the server name so that it can be displayed in full. 2025-06-29 21:13:36 +08:00
Marukome0743
1e31ebb9c2 chore: remove unknown apps/models from pnpm-workspace.yaml 2025-06-26 16:49:44 +09:00
18 changed files with 5961 additions and 27 deletions

View File

@@ -62,6 +62,8 @@ export const ShowDeployments = ({
const { mutateAsync: rollback, isLoading: isRollingBack } = const { mutateAsync: rollback, isLoading: isRollingBack } =
api.rollback.rollback.useMutation(); api.rollback.rollback.useMutation();
const { mutateAsync: killProcess, isLoading: isKillingProcess } =
api.deployment.killProcess.useMutation();
const [url, setUrl] = React.useState(""); const [url, setUrl] = React.useState("");
useEffect(() => { useEffect(() => {
@@ -170,6 +172,32 @@ export const ShowDeployments = ({
</div> </div>
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
{deployment.pid && deployment.status === "running" && (
<DialogAction
title="Kill Process"
description="Are you sure you want to kill the process?"
type="default"
onClick={async () => {
await killProcess({
deploymentId: deployment.deploymentId,
})
.then(() => {
toast.success("Process killed successfully");
})
.catch(() => {
toast.error("Error killing process");
});
}}
>
<Button
variant="destructive"
size="sm"
isLoading={isKillingProcess}
>
Kill Process
</Button>
</DialogAction>
)}
<Button <Button
onClick={() => { onClick={() => {
setActiveLog(deployment); setActiveLog(deployment);

View File

@@ -166,12 +166,16 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => {
await runManually({ await runManually({
scheduleId: schedule.scheduleId, scheduleId: schedule.scheduleId,
}).then(async () => { })
await new Promise((resolve) => .then(async () => {
setTimeout(resolve, 1500), await new Promise((resolve) =>
); setTimeout(resolve, 1500),
refetchSchedules(); );
}); refetchSchedules();
})
.catch(() => {
toast.error("Error running schedule");
});
}} }}
> >
<Play className="size-4 transition-colors" /> <Play className="size-4 transition-colors" />

View File

@@ -20,7 +20,7 @@ export const ShowNodesModal = ({ serverId }: Props) => {
Show Swarm Nodes Show Swarm Nodes
</DropdownMenuItem> </DropdownMenuItem>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-5xl overflow-y-auto max-h-screen "> <DialogContent className="min-w-[70vw] overflow-y-auto max-h-screen">
<div className="grid w-full gap-1"> <div className="grid w-full gap-1">
<ShowNodes serverId={serverId} /> <ShowNodes serverId={serverId} />
</div> </div>

View File

@@ -87,7 +87,7 @@ export const ShowNodes = ({ serverId }: Props) => {
</TableCaption> </TableCaption>
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableHead className="w-[100px]">Hostname</TableHead> <TableHead className="text-left">Hostname</TableHead>
<TableHead className="text-right">Status</TableHead> <TableHead className="text-right">Status</TableHead>
<TableHead className="text-right">Role</TableHead> <TableHead className="text-right">Role</TableHead>
<TableHead className="text-right">Availability</TableHead> <TableHead className="text-right">Availability</TableHead>
@@ -104,7 +104,7 @@ export const ShowNodes = ({ serverId }: Props) => {
const isManager = node.Spec.Role === "manager"; const isManager = node.Spec.Role === "manager";
return ( return (
<TableRow key={node.ID}> <TableRow key={node.ID}>
<TableCell className="w-[100px]"> <TableCell className="text-left">
{node.Description.Hostname} {node.Description.Hostname}
</TableCell> </TableCell>
<TableCell className="text-right"> <TableCell className="text-right">

View File

@@ -141,7 +141,7 @@ export const ShowServers = () => {
</TableCaption> </TableCaption>
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableHead className="w-[100px]">Name</TableHead> <TableHead className="text-left">Name</TableHead>
{isCloud && ( {isCloud && (
<TableHead className="text-center"> <TableHead className="text-center">
Status Status
@@ -173,7 +173,7 @@ export const ShowServers = () => {
const isActive = server.serverStatus === "active"; const isActive = server.serverStatus === "active";
return ( return (
<TableRow key={server.serverId}> <TableRow key={server.serverId}>
<TableCell className="w-[100px]"> <TableCell className="text-left">
{server.name} {server.name}
</TableCell> </TableCell>
{isCloud && ( {isCloud && (

View File

@@ -4,7 +4,7 @@ import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
const badgeVariants = cva( const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold whitespace-nowrap transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{ {
variants: { variants: {
variant: { variant: {

View File

@@ -0,0 +1 @@
ALTER TABLE "deployment" ADD COLUMN "pid" text;

File diff suppressed because it is too large Load Diff

View File

@@ -687,6 +687,13 @@
"when": 1750567641441, "when": 1750567641441,
"tag": "0097_hard_lizard", "tag": "0097_hard_lizard",
"breakpoints": true "breakpoints": true
},
{
"idx": 98,
"version": "7",
"when": 1751233265357,
"tag": "0098_conscious_chat",
"breakpoints": true
} }
] ]
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "dokploy", "name": "dokploy",
"version": "v0.23.4", "version": "v0.23.6",
"private": true, "private": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"type": "module", "type": "module",

View File

@@ -7,16 +7,21 @@ import {
deployments, deployments,
} from "@/server/db/schema"; } from "@/server/db/schema";
import { import {
execAsync,
execAsyncRemote,
findAllDeploymentsByApplicationId, findAllDeploymentsByApplicationId,
findAllDeploymentsByComposeId, findAllDeploymentsByComposeId,
findAllDeploymentsByServerId, findAllDeploymentsByServerId,
findApplicationById, findApplicationById,
findComposeById, findComposeById,
findDeploymentById,
findServerById, findServerById,
updateDeploymentStatus,
} from "@dokploy/server"; } from "@dokploy/server";
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { desc, eq } from "drizzle-orm"; import { desc, eq } from "drizzle-orm";
import { createTRPCRouter, protectedProcedure } from "../trpc"; import { createTRPCRouter, protectedProcedure } from "../trpc";
import { z } from "zod";
export const deploymentRouter = createTRPCRouter({ export const deploymentRouter = createTRPCRouter({
all: protectedProcedure all: protectedProcedure
@@ -72,4 +77,30 @@ export const deploymentRouter = createTRPCRouter({
return deploymentsList; return deploymentsList;
}), }),
killProcess: protectedProcedure
.input(
z.object({
deploymentId: z.string().min(1),
}),
)
.mutation(async ({ input }) => {
const deployment = await findDeploymentById(input.deploymentId);
if (!deployment.pid) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Deployment is not running",
});
}
const command = `kill -9 ${deployment.pid}`;
if (deployment.schedule?.serverId) {
await execAsyncRemote(deployment.schedule.serverId, command);
} else {
await execAsync(command);
}
await updateDeploymentStatus(deployment.deploymentId, "error");
}),
}); });

View File

@@ -31,6 +31,7 @@ export const deployments = pgTable("deployment", {
description: text("description"), description: text("description"),
status: deploymentStatus("status").default("running"), status: deploymentStatus("status").default("running"),
logPath: text("logPath").notNull(), logPath: text("logPath").notNull(),
pid: text("pid"),
applicationId: text("applicationId").references( applicationId: text("applicationId").references(
() => applications.applicationId, () => applications.applicationId,
{ onDelete: "cascade" }, { onDelete: "cascade" },

View File

@@ -40,6 +40,7 @@ export const findDeploymentById = async (deploymentId: string) => {
where: eq(deployments.deploymentId, deploymentId), where: eq(deployments.deploymentId, deploymentId),
with: { with: {
application: true, application: true,
schedule: true,
}, },
}); });
if (!deployment) { if (!deployment) {
@@ -537,9 +538,11 @@ const removeLastTenDeployments = async (
await removeRollbackById(oldDeployment.rollbackId); await removeRollbackById(oldDeployment.rollbackId);
} }
command += ` if (logPath !== ".") {
rm -rf ${logPath}; command += `
`; rm -rf ${logPath};
`;
}
await removeDeployment(oldDeployment.deploymentId); await removeDeployment(oldDeployment.deploymentId);
} }
@@ -550,7 +553,11 @@ const removeLastTenDeployments = async (
await removeRollbackById(oldDeployment.rollbackId); await removeRollbackById(oldDeployment.rollbackId);
} }
const logPath = path.join(oldDeployment.logPath); const logPath = path.join(oldDeployment.logPath);
if (existsSync(logPath) && !oldDeployment.errorMessage) { if (
existsSync(logPath) &&
!oldDeployment.errorMessage &&
logPath !== "."
) {
await fsPromises.unlink(logPath); await fsPromises.unlink(logPath);
} }
await removeDeployment(oldDeployment.deploymentId); await removeDeployment(oldDeployment.deploymentId);

View File

@@ -171,7 +171,7 @@ export const createPreviewDeployment = async (
const runningComment = getIssueComment( const runningComment = getIssueComment(
application.name, application.name,
"initializing", "initializing",
generateDomain, `${application.previewHttps ? "https" : "http"}://${generateDomain}`,
); );
const issue = await octokit.rest.issues.createComment({ const issue = await octokit.rest.issues.createComment({

View File

@@ -109,7 +109,12 @@ export const updateSchedule = async (
const handleScript = async (schedule: Schedule) => { const handleScript = async (schedule: Schedule) => {
const { SCHEDULES_PATH } = paths(!!schedule?.serverId); const { SCHEDULES_PATH } = paths(!!schedule?.serverId);
const fullPath = path.join(SCHEDULES_PATH, schedule?.appName || ""); const fullPath = path.join(SCHEDULES_PATH, schedule?.appName || "");
const encodedContent = encodeBase64(schedule?.script || "");
// Add PID and Schedule ID echo by default to all scripts
const scriptWithPid = `echo "PID: $$ | Schedule ID: ${schedule.scheduleId}"
${schedule?.script || ""}`;
const encodedContent = encodeBase64(scriptWithPid);
const script = ` const script = `
mkdir -p ${fullPath} mkdir -p ${fullPath}
rm -f ${fullPath}/script.sh rm -f ${fullPath}/script.sh

View File

@@ -193,7 +193,7 @@ const createEnvFile = (compose: ComposeNested) => {
let envContent = `APP_NAME=${appName}\n`; let envContent = `APP_NAME=${appName}\n`;
envContent += env || ""; envContent += env || "";
if (!envContent.includes("DOCKER_CONFIG")) { if (!envContent.includes("DOCKER_CONFIG")) {
envContent += "\nDOCKER_CONFIG=/root/.docker/config.json"; envContent += "\nDOCKER_CONFIG=/root/.docker";
} }
if (compose.randomize) { if (compose.randomize) {
@@ -223,7 +223,7 @@ export const getCreateEnvFileCommand = (compose: ComposeNested) => {
let envContent = `APP_NAME=${appName}\n`; let envContent = `APP_NAME=${appName}\n`;
envContent += env || ""; envContent += env || "";
if (!envContent.includes("DOCKER_CONFIG")) { if (!envContent.includes("DOCKER_CONFIG")) {
envContent += "\nDOCKER_CONFIG=/root/.docker/config.json"; envContent += "\nDOCKER_CONFIG=/root/.docker";
} }
if (compose.randomize) { if (compose.randomize) {

View File

@@ -2,7 +2,10 @@ import { createWriteStream } from "node:fs";
import path from "node:path"; import path from "node:path";
import { paths } from "@dokploy/server/constants"; import { paths } from "@dokploy/server/constants";
import type { Schedule } from "@dokploy/server/db/schema/schedule"; import type { Schedule } from "@dokploy/server/db/schema/schedule";
import { createDeploymentSchedule } from "@dokploy/server/services/deployment"; import {
createDeploymentSchedule,
updateDeployment,
} from "@dokploy/server/services/deployment";
import { updateDeploymentStatus } from "@dokploy/server/services/deployment"; import { updateDeploymentStatus } from "@dokploy/server/services/deployment";
import { findScheduleById } from "@dokploy/server/services/schedule"; import { findScheduleById } from "@dokploy/server/services/schedule";
import { scheduleJob as scheduleJobNode, scheduledJobs } from "node-schedule"; import { scheduleJob as scheduleJobNode, scheduledJobs } from "node-schedule";
@@ -113,8 +116,16 @@ export const runCommand = async (scheduleId: string) => {
await spawnAsync( await spawnAsync(
"bash", "bash",
["-c", "./script.sh"], ["-c", "./script.sh"],
(data) => { async (data) => {
if (writeStream.writable) { if (writeStream.writable) {
// we need to extract the PID and Schedule ID from the data
const pid = data?.match(/PID: (\d+)/)?.[1];
if (pid) {
await updateDeployment(deployment.deploymentId, {
pid,
});
}
writeStream.write(data); writeStream.write(data);
} }
}, },
@@ -133,13 +144,21 @@ export const runCommand = async (scheduleId: string) => {
const command = ` const command = `
set -e set -e
echo "Running script" >> ${deployment.logPath}; echo "Running script" >> ${deployment.logPath};
bash -c ${fullPath}/script.sh >> ${deployment.logPath} 2>> ${deployment.logPath} || { bash -c ${fullPath}/script.sh 2>&1 | tee -a ${deployment.logPath} || {
echo "❌ Command failed" >> ${deployment.logPath}; echo "❌ Command failed" >> ${deployment.logPath};
exit 1; exit 1;
} }
echo "✅ Command executed successfully" >> ${deployment.logPath}; echo "✅ Command executed successfully" >> ${deployment.logPath};
`; `;
await execAsyncRemote(serverId, command); await execAsyncRemote(serverId, command, async (data) => {
// we need to extract the PID and Schedule ID from the data
const pid = data?.match(/PID: (\d+)/)?.[1];
if (pid) {
await updateDeployment(deployment.deploymentId, {
pid,
});
}
});
} catch (error) { } catch (error) {
await updateDeploymentStatus(deployment.deploymentId, "error"); await updateDeploymentStatus(deployment.deploymentId, "error");
throw error; throw error;

View File

@@ -1,7 +1,6 @@
packages: packages:
- "apps/dokploy"
- "apps/api" - "apps/api"
- "apps/dokploy"
- "apps/monitoring" - "apps/monitoring"
- "apps/schedules" - "apps/schedules"
- "apps/models"
- "packages/server" - "packages/server"