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:
@@ -6,6 +6,7 @@ import {
|
||||
type apiCreateDeployment,
|
||||
type apiCreateDeploymentCompose,
|
||||
type apiCreateDeploymentPreview,
|
||||
type apiCreateDeploymentSchedule,
|
||||
type apiCreateDeploymentServer,
|
||||
deployments,
|
||||
} from "@dokploy/server/db/schema";
|
||||
@@ -27,6 +28,7 @@ import {
|
||||
findPreviewDeploymentById,
|
||||
updatePreviewDeployment,
|
||||
} from "./preview-deployment";
|
||||
import { findScheduleById } from "./schedule";
|
||||
|
||||
export type Deployment = typeof deployments.$inferSelect;
|
||||
|
||||
@@ -57,6 +59,7 @@ export const createDeployment = async (
|
||||
try {
|
||||
await removeLastTenDeployments(
|
||||
deployment.applicationId,
|
||||
"application",
|
||||
application.serverId,
|
||||
);
|
||||
const { LOGS_PATH } = paths(!!application.serverId);
|
||||
@@ -88,6 +91,7 @@ export const createDeployment = async (
|
||||
status: "running",
|
||||
logPath: logFilePath,
|
||||
description: deployment.description || "",
|
||||
startedAt: new Date().toISOString(),
|
||||
})
|
||||
.returning();
|
||||
if (deploymentCreate.length === 0 || !deploymentCreate[0]) {
|
||||
@@ -107,6 +111,8 @@ export const createDeployment = async (
|
||||
logPath: "",
|
||||
description: deployment.description || "",
|
||||
errorMessage: `An error have occured: ${error instanceof Error ? error.message : error}`,
|
||||
startedAt: new Date().toISOString(),
|
||||
finishedAt: new Date().toISOString(),
|
||||
})
|
||||
.returning();
|
||||
await updateApplicationStatus(application.applicationId, "error");
|
||||
@@ -128,8 +134,9 @@ export const createDeploymentPreview = async (
|
||||
deployment.previewDeploymentId,
|
||||
);
|
||||
try {
|
||||
await removeLastTenPreviewDeploymenById(
|
||||
await removeLastTenDeployments(
|
||||
deployment.previewDeploymentId,
|
||||
"previewDeployment",
|
||||
previewDeployment?.application?.serverId,
|
||||
);
|
||||
|
||||
@@ -165,6 +172,7 @@ export const createDeploymentPreview = async (
|
||||
logPath: logFilePath,
|
||||
description: deployment.description || "",
|
||||
previewDeploymentId: deployment.previewDeploymentId,
|
||||
startedAt: new Date().toISOString(),
|
||||
})
|
||||
.returning();
|
||||
if (deploymentCreate.length === 0 || !deploymentCreate[0]) {
|
||||
@@ -184,6 +192,8 @@ export const createDeploymentPreview = async (
|
||||
logPath: "",
|
||||
description: deployment.description || "",
|
||||
errorMessage: `An error have occured: ${error instanceof Error ? error.message : error}`,
|
||||
startedAt: new Date().toISOString(),
|
||||
finishedAt: new Date().toISOString(),
|
||||
})
|
||||
.returning();
|
||||
await updatePreviewDeployment(deployment.previewDeploymentId, {
|
||||
@@ -205,8 +215,9 @@ export const createDeploymentCompose = async (
|
||||
) => {
|
||||
const compose = await findComposeById(deployment.composeId);
|
||||
try {
|
||||
await removeLastTenComposeDeployments(
|
||||
await removeLastTenDeployments(
|
||||
deployment.composeId,
|
||||
"compose",
|
||||
compose.serverId,
|
||||
);
|
||||
const { LOGS_PATH } = paths(!!compose.serverId);
|
||||
@@ -238,6 +249,7 @@ echo "Initializing deployment" >> ${logFilePath};
|
||||
description: deployment.description || "",
|
||||
status: "running",
|
||||
logPath: logFilePath,
|
||||
startedAt: new Date().toISOString(),
|
||||
})
|
||||
.returning();
|
||||
if (deploymentCreate.length === 0 || !deploymentCreate[0]) {
|
||||
@@ -257,6 +269,8 @@ echo "Initializing deployment" >> ${logFilePath};
|
||||
logPath: "",
|
||||
description: deployment.description || "",
|
||||
errorMessage: `An error have occured: ${error instanceof Error ? error.message : error}`,
|
||||
startedAt: new Date().toISOString(),
|
||||
finishedAt: new Date().toISOString(),
|
||||
})
|
||||
.returning();
|
||||
await updateCompose(compose.composeId, {
|
||||
@@ -270,6 +284,82 @@ echo "Initializing deployment" >> ${logFilePath};
|
||||
}
|
||||
};
|
||||
|
||||
export const createDeploymentSchedule = async (
|
||||
deployment: Omit<
|
||||
typeof apiCreateDeploymentSchedule._type,
|
||||
"deploymentId" | "createdAt" | "status" | "logPath"
|
||||
>,
|
||||
) => {
|
||||
const schedule = await findScheduleById(deployment.scheduleId);
|
||||
|
||||
try {
|
||||
const serverId =
|
||||
schedule.application?.serverId ||
|
||||
schedule.compose?.serverId ||
|
||||
schedule.server?.serverId;
|
||||
await removeLastTenDeployments(deployment.scheduleId, "schedule", serverId);
|
||||
const { SCHEDULES_PATH } = paths(!!serverId);
|
||||
const formattedDateTime = format(new Date(), "yyyy-MM-dd:HH:mm:ss");
|
||||
const fileName = `${schedule.appName}-${formattedDateTime}.log`;
|
||||
const logFilePath = path.join(SCHEDULES_PATH, schedule.appName, fileName);
|
||||
|
||||
if (serverId) {
|
||||
const server = await findServerById(serverId);
|
||||
|
||||
const command = `
|
||||
mkdir -p ${SCHEDULES_PATH}/${schedule.appName};
|
||||
echo "Initializing schedule" >> ${logFilePath};
|
||||
`;
|
||||
|
||||
await execAsyncRemote(server.serverId, command);
|
||||
} else {
|
||||
await fsPromises.mkdir(path.join(SCHEDULES_PATH, schedule.appName), {
|
||||
recursive: true,
|
||||
});
|
||||
await fsPromises.writeFile(logFilePath, "Initializing schedule\n");
|
||||
}
|
||||
|
||||
const deploymentCreate = await db
|
||||
.insert(deployments)
|
||||
.values({
|
||||
scheduleId: deployment.scheduleId,
|
||||
title: deployment.title || "Deployment",
|
||||
status: "running",
|
||||
logPath: logFilePath,
|
||||
description: deployment.description || "",
|
||||
startedAt: new Date().toISOString(),
|
||||
})
|
||||
.returning();
|
||||
if (deploymentCreate.length === 0 || !deploymentCreate[0]) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error creating the deployment",
|
||||
});
|
||||
}
|
||||
return deploymentCreate[0];
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
await db
|
||||
.insert(deployments)
|
||||
.values({
|
||||
scheduleId: deployment.scheduleId,
|
||||
title: deployment.title || "Deployment",
|
||||
status: "error",
|
||||
logPath: "",
|
||||
description: deployment.description || "",
|
||||
errorMessage: `An error have occured: ${error instanceof Error ? error.message : error}`,
|
||||
startedAt: new Date().toISOString(),
|
||||
finishedAt: new Date().toISOString(),
|
||||
})
|
||||
.returning();
|
||||
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error creating the deployment",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const removeDeployment = async (deploymentId: string) => {
|
||||
try {
|
||||
const deployment = await db
|
||||
@@ -296,109 +386,15 @@ export const removeDeploymentsByApplicationId = async (
|
||||
.returning();
|
||||
};
|
||||
|
||||
const removeLastTenDeployments = async (
|
||||
applicationId: string,
|
||||
serverId: string | null,
|
||||
const getDeploymentsByType = async (
|
||||
id: string,
|
||||
type: "application" | "compose" | "server" | "schedule" | "previewDeployment",
|
||||
) => {
|
||||
const deploymentList = await db.query.deployments.findMany({
|
||||
where: eq(deployments.applicationId, applicationId),
|
||||
where: eq(deployments[`${type}Id`], id),
|
||||
orderBy: desc(deployments.createdAt),
|
||||
});
|
||||
|
||||
if (deploymentList.length > 10) {
|
||||
const deploymentsToDelete = deploymentList.slice(9);
|
||||
if (serverId) {
|
||||
let command = "";
|
||||
for (const oldDeployment of deploymentsToDelete) {
|
||||
const logPath = path.join(oldDeployment.logPath);
|
||||
|
||||
command += `
|
||||
rm -rf ${logPath};
|
||||
`;
|
||||
await removeDeployment(oldDeployment.deploymentId);
|
||||
}
|
||||
|
||||
await execAsyncRemote(serverId, command);
|
||||
} else {
|
||||
for (const oldDeployment of deploymentsToDelete) {
|
||||
const logPath = path.join(oldDeployment.logPath);
|
||||
if (existsSync(logPath)) {
|
||||
await fsPromises.unlink(logPath);
|
||||
}
|
||||
await removeDeployment(oldDeployment.deploymentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const removeLastTenComposeDeployments = async (
|
||||
composeId: string,
|
||||
serverId: string | null,
|
||||
) => {
|
||||
const deploymentList = await db.query.deployments.findMany({
|
||||
where: eq(deployments.composeId, composeId),
|
||||
orderBy: desc(deployments.createdAt),
|
||||
});
|
||||
if (deploymentList.length > 10) {
|
||||
if (serverId) {
|
||||
let command = "";
|
||||
const deploymentsToDelete = deploymentList.slice(9);
|
||||
for (const oldDeployment of deploymentsToDelete) {
|
||||
const logPath = path.join(oldDeployment.logPath);
|
||||
|
||||
command += `
|
||||
rm -rf ${logPath};
|
||||
`;
|
||||
await removeDeployment(oldDeployment.deploymentId);
|
||||
}
|
||||
|
||||
await execAsyncRemote(serverId, command);
|
||||
} else {
|
||||
const deploymentsToDelete = deploymentList.slice(9);
|
||||
for (const oldDeployment of deploymentsToDelete) {
|
||||
const logPath = path.join(oldDeployment.logPath);
|
||||
if (existsSync(logPath)) {
|
||||
await fsPromises.unlink(logPath);
|
||||
}
|
||||
await removeDeployment(oldDeployment.deploymentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const removeLastTenPreviewDeploymenById = async (
|
||||
previewDeploymentId: string,
|
||||
serverId: string | null,
|
||||
) => {
|
||||
const deploymentList = await db.query.deployments.findMany({
|
||||
where: eq(deployments.previewDeploymentId, previewDeploymentId),
|
||||
orderBy: desc(deployments.createdAt),
|
||||
});
|
||||
|
||||
if (deploymentList.length > 10) {
|
||||
const deploymentsToDelete = deploymentList.slice(9);
|
||||
if (serverId) {
|
||||
let command = "";
|
||||
for (const oldDeployment of deploymentsToDelete) {
|
||||
const logPath = path.join(oldDeployment.logPath);
|
||||
|
||||
command += `
|
||||
rm -rf ${logPath};
|
||||
`;
|
||||
await removeDeployment(oldDeployment.deploymentId);
|
||||
}
|
||||
|
||||
await execAsyncRemote(serverId, command);
|
||||
} else {
|
||||
for (const oldDeployment of deploymentsToDelete) {
|
||||
const logPath = path.join(oldDeployment.logPath);
|
||||
if (existsSync(logPath)) {
|
||||
await fsPromises.unlink(logPath);
|
||||
}
|
||||
await removeDeployment(oldDeployment.deploymentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
return deploymentList;
|
||||
};
|
||||
|
||||
export const removeDeployments = async (application: Application) => {
|
||||
@@ -413,6 +409,38 @@ export const removeDeployments = async (application: Application) => {
|
||||
await removeDeploymentsByApplicationId(applicationId);
|
||||
};
|
||||
|
||||
const removeLastTenDeployments = async (
|
||||
id: string,
|
||||
type: "application" | "compose" | "server" | "schedule" | "previewDeployment",
|
||||
serverId?: string | null,
|
||||
) => {
|
||||
const deploymentList = await getDeploymentsByType(id, type);
|
||||
if (deploymentList.length > 10) {
|
||||
const deploymentsToDelete = deploymentList.slice(10);
|
||||
if (serverId) {
|
||||
let command = "";
|
||||
for (const oldDeployment of deploymentsToDelete) {
|
||||
const logPath = path.join(oldDeployment.logPath);
|
||||
|
||||
command += `
|
||||
rm -rf ${logPath};
|
||||
`;
|
||||
await removeDeployment(oldDeployment.deploymentId);
|
||||
}
|
||||
|
||||
await execAsyncRemote(serverId, command);
|
||||
} else {
|
||||
for (const oldDeployment of deploymentsToDelete) {
|
||||
const logPath = path.join(oldDeployment.logPath);
|
||||
if (existsSync(logPath)) {
|
||||
await fsPromises.unlink(logPath);
|
||||
}
|
||||
await removeDeployment(oldDeployment.deploymentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const removeDeploymentsByPreviewDeploymentId = async (
|
||||
previewDeployment: PreviewDeployment,
|
||||
serverId: string | null,
|
||||
@@ -494,6 +522,10 @@ export const updateDeploymentStatus = async (
|
||||
.update(deployments)
|
||||
.set({
|
||||
status: deploymentStatus,
|
||||
finishedAt:
|
||||
deploymentStatus === "done" || deploymentStatus === "error"
|
||||
? new Date().toISOString()
|
||||
: null,
|
||||
})
|
||||
.where(eq(deployments.deploymentId, deploymentId))
|
||||
.returning();
|
||||
|
||||
126
packages/server/src/services/schedule.ts
Normal file
126
packages/server/src/services/schedule.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { type Schedule, schedules } from "../db/schema/schedule";
|
||||
import { db } from "../db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import type { z } from "zod";
|
||||
import type {
|
||||
createScheduleSchema,
|
||||
updateScheduleSchema,
|
||||
} from "../db/schema/schedule";
|
||||
import { execAsync, execAsyncRemote } from "../utils/process/execAsync";
|
||||
import { paths } from "../constants";
|
||||
import path from "node:path";
|
||||
import { encodeBase64 } from "../utils/docker/utils";
|
||||
|
||||
export type ScheduleExtended = Awaited<ReturnType<typeof findScheduleById>>;
|
||||
|
||||
export const createSchedule = async (
|
||||
input: z.infer<typeof createScheduleSchema>,
|
||||
) => {
|
||||
const { scheduleId, ...rest } = input;
|
||||
const [newSchedule] = await db.insert(schedules).values(rest).returning();
|
||||
|
||||
if (
|
||||
newSchedule &&
|
||||
(newSchedule.scheduleType === "dokploy-server" ||
|
||||
newSchedule.scheduleType === "server")
|
||||
) {
|
||||
await handleScript(newSchedule);
|
||||
}
|
||||
|
||||
return newSchedule;
|
||||
};
|
||||
|
||||
export const findScheduleById = async (scheduleId: string) => {
|
||||
const schedule = await db.query.schedules.findFirst({
|
||||
where: eq(schedules.scheduleId, scheduleId),
|
||||
with: {
|
||||
application: true,
|
||||
compose: true,
|
||||
server: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!schedule) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Schedule not found",
|
||||
});
|
||||
}
|
||||
return schedule;
|
||||
};
|
||||
|
||||
export const deleteSchedule = async (scheduleId: string) => {
|
||||
const schedule = await findScheduleById(scheduleId);
|
||||
const serverId =
|
||||
schedule?.serverId ||
|
||||
schedule?.application?.serverId ||
|
||||
schedule?.compose?.serverId;
|
||||
const { SCHEDULES_PATH } = paths(!!serverId);
|
||||
|
||||
const fullPath = path.join(SCHEDULES_PATH, schedule?.appName || "");
|
||||
const command = `rm -rf ${fullPath}`;
|
||||
if (serverId) {
|
||||
await execAsyncRemote(serverId, command);
|
||||
} else {
|
||||
await execAsync(command);
|
||||
}
|
||||
|
||||
const scheduleResult = await db
|
||||
.delete(schedules)
|
||||
.where(eq(schedules.scheduleId, scheduleId));
|
||||
if (!scheduleResult) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Schedule not found",
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const updateSchedule = async (
|
||||
input: z.infer<typeof updateScheduleSchema>,
|
||||
) => {
|
||||
const { scheduleId, ...rest } = input;
|
||||
const [updatedSchedule] = await db
|
||||
.update(schedules)
|
||||
.set(rest)
|
||||
.where(eq(schedules.scheduleId, scheduleId))
|
||||
.returning();
|
||||
|
||||
if (!updatedSchedule) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Schedule not found",
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
updatedSchedule?.scheduleType === "dokploy-server" ||
|
||||
updatedSchedule?.scheduleType === "server"
|
||||
) {
|
||||
await handleScript(updatedSchedule);
|
||||
}
|
||||
|
||||
return updatedSchedule;
|
||||
};
|
||||
|
||||
const handleScript = async (schedule: Schedule) => {
|
||||
const { SCHEDULES_PATH } = paths(!!schedule?.serverId);
|
||||
const fullPath = path.join(SCHEDULES_PATH, schedule?.appName || "");
|
||||
const encodedContent = encodeBase64(schedule?.script || "");
|
||||
const script = `
|
||||
mkdir -p ${fullPath}
|
||||
rm -f ${fullPath}/script.sh
|
||||
touch ${fullPath}/script.sh
|
||||
chmod +x ${fullPath}/script.sh
|
||||
echo "${encodedContent}" | base64 -d > ${fullPath}/script.sh
|
||||
`;
|
||||
|
||||
if (schedule?.scheduleType === "dokploy-server") {
|
||||
await execAsync(script);
|
||||
} else if (schedule?.scheduleType === "server") {
|
||||
await execAsyncRemote(schedule?.serverId || "", script);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user