Enhance backup and deployment features

- Updated the RestoreBackupSchema to require serviceName for compose backups, improving validation and user feedback.
- Refactored the ShowBackups component to include deployment information, enhancing the user interface and experience.
- Introduced new SQL migration files to add backupId to the deployment table and appName to the backup table, improving data relationships and integrity.
- Enhanced deployment creation logic to support backup deployments, ensuring better tracking and management of backup processes.
- Improved backup and restore utility functions to streamline command execution and error handling during backup operations.
This commit is contained in:
Mauricio Siu
2025-05-03 12:39:52 -06:00
parent 50aeeb2fb8
commit e437903ef8
23 changed files with 11785 additions and 92 deletions

View File

@@ -134,6 +134,7 @@ export const findComposeById = async (composeId: string) => {
backups: {
with: {
destination: true,
deployments: true,
},
},
},

View File

@@ -4,6 +4,7 @@ import { paths } from "@dokploy/server/constants";
import { db } from "@dokploy/server/db";
import {
type apiCreateDeployment,
type apiCreateDeploymentBackup,
type apiCreateDeploymentCompose,
type apiCreateDeploymentPreview,
type apiCreateDeploymentSchedule,
@@ -29,6 +30,7 @@ import {
updatePreviewDeployment,
} from "./preview-deployment";
import { findScheduleById } from "./schedule";
import { findBackupById } from "./backup";
export type Deployment = typeof deployments.$inferSelect;
@@ -284,6 +286,86 @@ echo "Initializing deployment" >> ${logFilePath};
}
};
export const createDeploymentBackup = async (
deployment: Omit<
typeof apiCreateDeploymentBackup._type,
"deploymentId" | "createdAt" | "status" | "logPath"
>,
) => {
const backup = await findBackupById(deployment.backupId);
let serverId: string | null | undefined;
if (backup.backupType === "database") {
serverId =
backup.postgres?.serverId ||
backup.mariadb?.serverId ||
backup.mysql?.serverId ||
backup.mongo?.serverId;
} else if (backup.backupType === "compose") {
serverId = backup.compose?.serverId;
}
try {
await removeLastTenDeployments(deployment.backupId, "backup", serverId);
const { LOGS_PATH } = paths(!!serverId);
const formattedDateTime = format(new Date(), "yyyy-MM-dd:HH:mm:ss");
const fileName = `${backup.appName}-${formattedDateTime}.log`;
const logFilePath = path.join(LOGS_PATH, backup.appName, fileName);
if (serverId) {
const server = await findServerById(serverId);
const command = `
mkdir -p ${LOGS_PATH}/${backup.appName};
echo "Initializing backup" >> ${logFilePath};
`;
await execAsyncRemote(server.serverId, command);
} else {
await fsPromises.mkdir(path.join(LOGS_PATH, backup.appName), {
recursive: true,
});
await fsPromises.writeFile(logFilePath, "Initializing backup");
}
const deploymentCreate = await db
.insert(deployments)
.values({
backupId: deployment.backupId,
title: deployment.title || "Backup",
description: deployment.description || "",
status: "running",
logPath: logFilePath,
startedAt: new Date().toISOString(),
})
.returning();
if (deploymentCreate.length === 0 || !deploymentCreate[0]) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error creating the backup",
});
}
return deploymentCreate[0];
} catch (error) {
await db
.insert(deployments)
.values({
backupId: deployment.backupId,
title: deployment.title || "Backup",
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 backup",
});
}
};
export const createDeploymentSchedule = async (
deployment: Omit<
typeof apiCreateDeploymentSchedule._type,
@@ -388,7 +470,13 @@ export const removeDeploymentsByApplicationId = async (
const getDeploymentsByType = async (
id: string,
type: "application" | "compose" | "server" | "schedule" | "previewDeployment",
type:
| "application"
| "compose"
| "server"
| "schedule"
| "previewDeployment"
| "backup",
) => {
const deploymentList = await db.query.deployments.findMany({
where: eq(deployments[`${type}Id`], id),
@@ -411,7 +499,13 @@ export const removeDeployments = async (application: Application) => {
const removeLastTenDeployments = async (
id: string,
type: "application" | "compose" | "server" | "schedule" | "previewDeployment",
type:
| "application"
| "compose"
| "server"
| "schedule"
| "previewDeployment"
| "backup",
serverId?: string | null,
) => {
const deploymentList = await getDeploymentsByType(id, type);

View File

@@ -63,6 +63,7 @@ export const findMariadbById = async (mariadbId: string) => {
backups: {
with: {
destination: true,
deployments: true,
},
},
},

View File

@@ -60,6 +60,7 @@ export const findMongoById = async (mongoId: string) => {
backups: {
with: {
destination: true,
deployments: true,
},
},
},

View File

@@ -59,6 +59,7 @@ export const findMySqlById = async (mysqlId: string) => {
backups: {
with: {
destination: true,
deployments: true,
},
},
},

View File

@@ -58,6 +58,7 @@ export const findPostgresById = async (postgresId: string) => {
backups: {
with: {
destination: true,
deployments: true,
},
},
},

View File

@@ -40,6 +40,8 @@ export const getServiceImageDigest = async () => {
"docker service inspect dokploy --format '{{.Spec.TaskTemplate.ContainerSpec.Image}}'",
);
console.log("stdout", stdout);
const currentDigest = stdout.trim().split("@")[1];
if (!currentDigest) {