feat: add appName field to volume_backup schema and enhance deployment volume backup functionality

- Introduced a new appName column in the volume_backup table to improve application identification.
- Updated the volume backup schema to ensure appName is a required field.
- Enhanced the deployment service to support volume backup creation, integrating appName into the deployment process.
- Added validation for volume backup creation to ensure proper handling of appName and related data.
This commit is contained in:
Mauricio Siu
2025-06-29 23:18:12 -06:00
parent 392e2d66ec
commit 12860a0736
7 changed files with 6212 additions and 2 deletions

View File

@@ -0,0 +1 @@
ALTER TABLE "volume_backup" ADD COLUMN "appName" text NOT NULL;

File diff suppressed because it is too large Load Diff

View File

@@ -750,6 +750,13 @@
"when": 1751260111986,
"tag": "0106_furry_gargoyle",
"breakpoints": true
},
{
"idx": 107,
"version": "7",
"when": 1751260608863,
"tag": "0107_brief_the_fallen",
"breakpoints": true
}
]
}

View File

@@ -188,6 +188,17 @@ export const apiCreateDeploymentSchedule = schema
scheduleId: z.string().min(1),
});
export const apiCreateDeploymentVolumeBackup = schema
.pick({
title: true,
status: true,
logPath: true,
description: true,
})
.extend({
volumeBackupId: z.string().min(1),
});
export const apiFindAllByApplication = schema
.pick({
applicationId: true,

View File

@@ -13,6 +13,7 @@ import { postgres } from "./postgres";
import { mariadb } from "./mariadb";
import { destinations } from "./destination";
import { deployments } from "./deployment";
import { generateAppName } from "./utils";
export const volumeBackups = pgTable("volume_backup", {
volumeBackupId: text("volumeBackupId")
@@ -23,6 +24,9 @@ export const volumeBackups = pgTable("volume_backup", {
volumeName: text("volumeName").notNull(),
prefix: text("prefix").notNull(),
serviceType: serviceType("serviceType").notNull().default("application"),
appName: text("appName")
.notNull()
.$defaultFn(() => generateAppName("volumeBackup")),
serviceName: text("serviceName"),
turnOff: boolean("turnOff").notNull().default(false),
cronExpression: text("cronExpression").notNull(),

View File

@@ -9,6 +9,7 @@ import {
type apiCreateDeploymentPreview,
type apiCreateDeploymentSchedule,
type apiCreateDeploymentServer,
type apiCreateDeploymentVolumeBackup,
deployments,
} from "@dokploy/server/db/schema";
import { removeDirectoryIfExistsContent } from "@dokploy/server/utils/filesystem/directory";
@@ -32,6 +33,7 @@ import {
} from "./preview-deployment";
import { findScheduleById } from "./schedule";
import { removeRollbackById } from "./rollbacks";
import { findVolumeBackupById } from "./volume-backups";
export type Deployment = typeof deployments.$inferSelect;
@@ -458,6 +460,88 @@ export const createDeploymentSchedule = async (
}
};
export const createDeploymentVolumeBackup = async (
deployment: Omit<
typeof apiCreateDeploymentVolumeBackup._type,
"deploymentId" | "createdAt" | "status" | "logPath"
>,
) => {
const volumeBackup = await findVolumeBackupById(deployment.volumeBackupId);
try {
const serverId =
volumeBackup.application?.serverId || volumeBackup.compose?.serverId;
await removeLastTenDeployments(
deployment.volumeBackupId,
"volumeBackup",
serverId,
);
const { SCHEDULES_PATH } = paths(!!serverId);
const formattedDateTime = format(new Date(), "yyyy-MM-dd:HH:mm:ss");
const fileName = `${volumeBackup.appName}-${formattedDateTime}.log`;
const logFilePath = path.join(
SCHEDULES_PATH,
volumeBackup.appName,
fileName,
);
if (serverId) {
const server = await findServerById(serverId);
const command = `
mkdir -p ${SCHEDULES_PATH}/${volumeBackup.appName};
echo "Initializing volume backup" >> ${logFilePath};
`;
await execAsyncRemote(server.serverId, command);
} else {
await fsPromises.mkdir(path.join(SCHEDULES_PATH, volumeBackup.appName), {
recursive: true,
});
await fsPromises.writeFile(logFilePath, "Initializing volume backup\n");
}
const deploymentCreate = await db
.insert(deployments)
.values({
volumeBackupId: deployment.volumeBackupId,
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({
volumeBackupId: deployment.volumeBackupId,
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
@@ -492,7 +576,8 @@ const getDeploymentsByType = async (
| "server"
| "schedule"
| "previewDeployment"
| "backup",
| "backup"
| "volumeBackup",
) => {
const deploymentList = await db.query.deployments.findMany({
where: eq(deployments[`${type}Id`], id),
@@ -524,7 +609,8 @@ const removeLastTenDeployments = async (
| "server"
| "schedule"
| "previewDeployment"
| "backup",
| "backup"
| "volumeBackup",
serverId?: string | null,
) => {
const deploymentList = await getDeploymentsByType(id, type);

View File

@@ -11,6 +11,15 @@ import type { z } from "zod";
export const findVolumeBackupById = async (volumeBackupId: string) => {
const volumeBackup = await db.query.volumeBackups.findFirst({
where: eq(volumeBackups.volumeBackupId, volumeBackupId),
with: {
application: true,
postgres: true,
mysql: true,
mariadb: true,
mongo: true,
redis: true,
compose: true,
},
});
if (!volumeBackup) {