Files
dokploy/packages/server/src/utils/backups/compose.ts
Mauricio Siu 0690f07262 Enhance backup validation and improve backup display
- Added validation to require a service name for compose backups in the backup handling logic, improving user feedback.
- Refactored the ShowBackups component to sort deployments by creation date and enhance the display of backup information, including service names and statuses, for better user experience.
- Updated logging in the compose backup utility to include command execution details, aiding in debugging and monitoring.
2025-05-03 12:59:22 -06:00

135 lines
3.9 KiB
TypeScript

import type { BackupSchedule } from "@dokploy/server/services/backup";
import type { Compose } from "@dokploy/server/services/compose";
import { findProjectById } from "@dokploy/server/services/project";
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
import { execAsyncRemote, execAsyncStream } from "../process/execAsync";
import {
getMariadbBackupCommand,
getMysqlBackupCommand,
getMongoBackupCommand,
getPostgresBackupCommand,
getS3Credentials,
normalizeS3Path,
} from "./utils";
import {
createDeploymentBackup,
updateDeploymentStatus,
} from "@dokploy/server/services/deployment";
import { createWriteStream } from "node:fs";
import { getComposeContainer } from "../docker/utils";
export const runComposeBackup = async (
compose: Compose,
backup: BackupSchedule,
) => {
const { projectId, name } = compose;
const project = await findProjectById(projectId);
const { prefix, database } = backup;
const destination = backup.destination;
const backupFileName = `${new Date().toISOString()}.dump.gz`;
const bucketDestination = `${normalizeS3Path(prefix)}${backupFileName}`;
const deployment = await createDeploymentBackup({
backupId: backup.backupId,
title: "Compose Backup",
description: "Compose Backup",
});
try {
const rcloneFlags = getS3Credentials(destination);
const rcloneDestination = `:s3:${destination.bucket}/${bucketDestination}`;
const { Id: containerId } = await getComposeContainer(
compose,
backup.serviceName || "",
);
const rcloneCommand = `rclone rcat ${rcloneFlags.join(" ")} "${rcloneDestination}"`;
let backupCommand = "";
if (backup.databaseType === "postgres") {
backupCommand = getPostgresBackupCommand(
containerId,
database,
backup.metadata?.postgres?.databaseUser || "",
);
} else if (backup.databaseType === "mariadb") {
backupCommand = getMariadbBackupCommand(
containerId,
database,
backup.metadata?.mariadb?.databaseUser || "",
backup.metadata?.mariadb?.databasePassword || "",
);
} else if (backup.databaseType === "mysql") {
backupCommand = getMysqlBackupCommand(
containerId,
database,
backup.metadata?.mysql?.databaseRootPassword || "",
);
} else if (backup.databaseType === "mongo") {
backupCommand = getMongoBackupCommand(
containerId,
database,
backup.metadata?.mongo?.databaseUser || "",
backup.metadata?.mongo?.databasePassword || "",
);
}
if (compose.serverId) {
await execAsyncRemote(
compose.serverId,
`
set -e;
echo "Running command." >> ${deployment.logPath};
export RCLONE_LOG_LEVEL=DEBUG;
${backupCommand} | ${rcloneCommand} >> ${deployment.logPath} 2>> ${deployment.logPath} || {
echo "❌ Command failed" >> ${deployment.logPath};
exit 1;
}
echo "✅ Command executed successfully" >> ${deployment.logPath};
`,
);
} else {
const writeStream = createWriteStream(deployment.logPath, { flags: "a" });
await execAsyncStream(
`${backupCommand} | ${rcloneCommand}`,
(data) => {
if (writeStream.write(data)) {
console.log(data);
}
},
{
env: {
...process.env,
RCLONE_LOG_LEVEL: "DEBUG",
},
},
);
writeStream.write("Backup done✅");
writeStream.end();
}
await sendDatabaseBackupNotifications({
applicationName: name,
projectName: project.name,
databaseType: "mongodb",
type: "success",
organizationId: project.organizationId,
});
await updateDeploymentStatus(deployment.deploymentId, "done");
} catch (error) {
console.log(error);
await sendDatabaseBackupNotifications({
applicationName: name,
projectName: project.name,
databaseType: "mongodb",
type: "error",
// @ts-ignore
errorMessage: error?.message || "Error message not provided",
organizationId: project.organizationId,
});
await updateDeploymentStatus(deployment.deploymentId, "error");
throw error;
}
};