mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Merge branch 'canary' into 187-backups-for-docker-compose
This commit is contained in:
@@ -13,6 +13,7 @@ import type { RedisNested } from "../databases/redis";
|
||||
import { execAsync, execAsyncRemote } from "../process/execAsync";
|
||||
import { spawnAsync } from "../process/spawnAsync";
|
||||
import { getRemoteDocker } from "../servers/remote-docker";
|
||||
import type { Compose } from "@dokploy/server/services/compose";
|
||||
|
||||
interface RegistryAuth {
|
||||
username: string;
|
||||
@@ -541,3 +542,67 @@ export const getRemoteServiceContainer = async (
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getServiceContainerIV2 = async (
|
||||
appName: string,
|
||||
serverId?: string | null,
|
||||
) => {
|
||||
try {
|
||||
const filter = {
|
||||
status: ["running"],
|
||||
label: [`com.docker.swarm.service.name=${appName}`],
|
||||
};
|
||||
const remoteDocker = await getRemoteDocker(serverId);
|
||||
const containers = await remoteDocker.listContainers({
|
||||
filters: JSON.stringify(filter),
|
||||
});
|
||||
|
||||
if (containers.length === 0 || !containers[0]) {
|
||||
throw new Error(`No container found with name: ${appName}`);
|
||||
}
|
||||
|
||||
const container = containers[0];
|
||||
return container;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getComposeContainer = async (
|
||||
compose: Compose,
|
||||
serviceName: string,
|
||||
) => {
|
||||
try {
|
||||
const { appName, composeType, serverId } = compose;
|
||||
// 1. Determine the correct labels based on composeType
|
||||
const labels: string[] = [];
|
||||
if (composeType === "stack") {
|
||||
// Labels for Docker Swarm stack services
|
||||
labels.push(`com.docker.stack.namespace=${appName}`);
|
||||
labels.push(`com.docker.swarm.service.name=${appName}_${serviceName}`);
|
||||
} else {
|
||||
// Labels for Docker Compose projects (default)
|
||||
labels.push(`com.docker.compose.project=${appName}`);
|
||||
labels.push(`com.docker.compose.service=${serviceName}`);
|
||||
}
|
||||
const filter = {
|
||||
status: ["running"],
|
||||
label: labels,
|
||||
};
|
||||
|
||||
const remoteDocker = await getRemoteDocker(serverId);
|
||||
const containers = await remoteDocker.listContainers({
|
||||
filters: JSON.stringify(filter),
|
||||
limit: 1,
|
||||
});
|
||||
|
||||
if (containers.length === 0 || !containers[0]) {
|
||||
throw new Error(`No container found with name: ${appName}`);
|
||||
}
|
||||
|
||||
const container = containers[0];
|
||||
return container;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
28
packages/server/src/utils/schedules/index.ts
Normal file
28
packages/server/src/utils/schedules/index.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { db } from "../../db/index";
|
||||
import { schedules } from "@dokploy/server/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { scheduleJob } from "./utils";
|
||||
|
||||
export const initSchedules = async () => {
|
||||
try {
|
||||
const schedulesResult = await db.query.schedules.findMany({
|
||||
where: eq(schedules.enabled, true),
|
||||
with: {
|
||||
server: true,
|
||||
application: true,
|
||||
compose: true,
|
||||
user: true,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`Initializing ${schedulesResult.length} schedules`);
|
||||
for (const schedule of schedulesResult) {
|
||||
scheduleJob(schedule);
|
||||
console.log(
|
||||
`Initialized schedule: ${schedule.name} ${schedule.scheduleType} ✅`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Error initializing schedules: ${error}`);
|
||||
}
|
||||
};
|
||||
149
packages/server/src/utils/schedules/utils.ts
Normal file
149
packages/server/src/utils/schedules/utils.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import type { Schedule } from "@dokploy/server/db/schema/schedule";
|
||||
import { findScheduleById } from "@dokploy/server/services/schedule";
|
||||
import { scheduledJobs, scheduleJob as scheduleJobNode } from "node-schedule";
|
||||
import { getComposeContainer, getServiceContainerIV2 } from "../docker/utils";
|
||||
import { execAsyncRemote } from "../process/execAsync";
|
||||
import { spawnAsync } from "../process/spawnAsync";
|
||||
import { createDeploymentSchedule } from "@dokploy/server/services/deployment";
|
||||
import { createWriteStream } from "node:fs";
|
||||
import { updateDeploymentStatus } from "@dokploy/server/services/deployment";
|
||||
import { paths } from "@dokploy/server/constants";
|
||||
import path from "node:path";
|
||||
|
||||
export const scheduleJob = (schedule: Schedule) => {
|
||||
const { cronExpression, scheduleId } = schedule;
|
||||
|
||||
scheduleJobNode(scheduleId, cronExpression, async () => {
|
||||
await runCommand(scheduleId);
|
||||
});
|
||||
};
|
||||
|
||||
export const removeScheduleJob = (scheduleId: string) => {
|
||||
const currentJob = scheduledJobs[scheduleId];
|
||||
currentJob?.cancel();
|
||||
};
|
||||
|
||||
export const runCommand = async (scheduleId: string) => {
|
||||
const {
|
||||
application,
|
||||
command,
|
||||
shellType,
|
||||
scheduleType,
|
||||
compose,
|
||||
serviceName,
|
||||
appName,
|
||||
serverId,
|
||||
} = await findScheduleById(scheduleId);
|
||||
|
||||
const deployment = await createDeploymentSchedule({
|
||||
scheduleId,
|
||||
title: "Schedule",
|
||||
description: "Schedule",
|
||||
});
|
||||
|
||||
if (scheduleType === "application" || scheduleType === "compose") {
|
||||
let containerId = "";
|
||||
let serverId = "";
|
||||
if (scheduleType === "application" && application) {
|
||||
const container = await getServiceContainerIV2(
|
||||
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 {
|
||||
await execAsyncRemote(
|
||||
serverId,
|
||||
`
|
||||
set -e
|
||||
echo "Running command: docker exec ${containerId} ${shellType} -c \"${command}\"" >> ${deployment.logPath};
|
||||
docker exec ${containerId} ${shellType} -c "${command}" >> ${deployment.logPath} 2>> ${deployment.logPath} || {
|
||||
echo "❌ Command failed" >> ${deployment.logPath};
|
||||
exit 1;
|
||||
}
|
||||
echo "✅ Command executed successfully" >> ${deployment.logPath};
|
||||
`,
|
||||
);
|
||||
} catch (error) {
|
||||
await updateDeploymentStatus(deployment.deploymentId, "error");
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
const writeStream = createWriteStream(deployment.logPath, { flags: "a" });
|
||||
|
||||
try {
|
||||
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 {
|
||||
const writeStream = createWriteStream(deployment.logPath, { flags: "a" });
|
||||
const { SCHEDULES_PATH } = paths();
|
||||
const fullPath = path.join(SCHEDULES_PATH, appName || "");
|
||||
|
||||
await spawnAsync(
|
||||
"bash",
|
||||
["-c", "./script.sh"],
|
||||
(data) => {
|
||||
if (writeStream.writable) {
|
||||
writeStream.write(data);
|
||||
}
|
||||
},
|
||||
{
|
||||
cwd: fullPath,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
await updateDeploymentStatus(deployment.deploymentId, "error");
|
||||
throw error;
|
||||
}
|
||||
} else if (scheduleType === "server") {
|
||||
try {
|
||||
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 >> ${deployment.logPath} 2>> ${deployment.logPath} || {
|
||||
echo "❌ Command failed" >> ${deployment.logPath};
|
||||
exit 1;
|
||||
}
|
||||
echo "✅ Command executed successfully" >> ${deployment.logPath};
|
||||
`;
|
||||
await execAsyncRemote(serverId, command);
|
||||
} catch (error) {
|
||||
await updateDeploymentStatus(deployment.deploymentId, "error");
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
await updateDeploymentStatus(deployment.deploymentId, "done");
|
||||
};
|
||||
Reference in New Issue
Block a user