diff --git a/apps/dokploy/components/dashboard/settings/notifications/handle-notifications.tsx b/apps/dokploy/components/dashboard/settings/notifications/handle-notifications.tsx index 28d762c1c..37463fd62 100644 --- a/apps/dokploy/components/dashboard/settings/notifications/handle-notifications.tsx +++ b/apps/dokploy/components/dashboard/settings/notifications/handle-notifications.tsx @@ -12,9 +12,9 @@ import { toast } from "sonner"; import { z } from "zod"; import { DiscordIcon, - MattermostIcon, GotifyIcon, LarkIcon, + MattermostIcon, NtfyIcon, PushoverIcon, ResendIcon, @@ -54,6 +54,7 @@ const notificationBaseSchema = z.object({ appDeploy: z.boolean().default(false), appBuildError: z.boolean().default(false), databaseBackup: z.boolean().default(false), + dokployBackup: z.boolean().default(false), volumeBackup: z.boolean().default(false), dokployRestart: z.boolean().default(false), dockerCleanup: z.boolean().default(false), @@ -355,6 +356,7 @@ export const HandleNotifications = ({ notificationId }: Props) => { appDeploy: notification.appDeploy, dokployRestart: notification.dokployRestart, databaseBackup: notification.databaseBackup, + dokployBackup: notification.dokployBackup, volumeBackup: notification.volumeBackup, dockerCleanup: notification.dockerCleanup, webhookUrl: notification.slack?.webhookUrl, @@ -369,6 +371,7 @@ export const HandleNotifications = ({ notificationId }: Props) => { appDeploy: notification.appDeploy, dokployRestart: notification.dokployRestart, databaseBackup: notification.databaseBackup, + dokployBackup: notification.dokployBackup, volumeBackup: notification.volumeBackup, botToken: notification.telegram?.botToken, messageThreadId: notification.telegram?.messageThreadId || "", @@ -384,6 +387,7 @@ export const HandleNotifications = ({ notificationId }: Props) => { appDeploy: notification.appDeploy, dokployRestart: notification.dokployRestart, databaseBackup: notification.databaseBackup, + dokployBackup: notification.dokployBackup, volumeBackup: notification.volumeBackup, type: notification.notificationType, webhookUrl: notification.discord?.webhookUrl, @@ -398,6 +402,7 @@ export const HandleNotifications = ({ notificationId }: Props) => { appDeploy: notification.appDeploy, dokployRestart: notification.dokployRestart, databaseBackup: notification.databaseBackup, + dokployBackup: notification.dokployBackup, volumeBackup: notification.volumeBackup, type: notification.notificationType, smtpServer: notification.email?.smtpServer, @@ -416,6 +421,7 @@ export const HandleNotifications = ({ notificationId }: Props) => { appDeploy: notification.appDeploy, dokployRestart: notification.dokployRestart, databaseBackup: notification.databaseBackup, + dokployBackup: notification.dokployBackup, volumeBackup: notification.volumeBackup, type: notification.notificationType, apiKey: notification.resend?.apiKey, @@ -431,6 +437,7 @@ export const HandleNotifications = ({ notificationId }: Props) => { appDeploy: notification.appDeploy, dokployRestart: notification.dokployRestart, databaseBackup: notification.databaseBackup, + dokployBackup: notification.dokployBackup, volumeBackup: notification.volumeBackup, type: notification.notificationType, appToken: notification.gotify?.appToken, @@ -446,6 +453,7 @@ export const HandleNotifications = ({ notificationId }: Props) => { appDeploy: notification.appDeploy, dokployRestart: notification.dokployRestart, databaseBackup: notification.databaseBackup, + dokployBackup: notification.dokployBackup, volumeBackup: notification.volumeBackup, type: notification.notificationType, accessToken: notification.ntfy?.accessToken || "", @@ -462,6 +470,7 @@ export const HandleNotifications = ({ notificationId }: Props) => { appDeploy: notification.appDeploy, dokployRestart: notification.dokployRestart, databaseBackup: notification.databaseBackup, + dokployBackup: notification.dokployBackup, volumeBackup: notification.volumeBackup, type: notification.notificationType, webhookUrl: notification.mattermost?.webhookUrl, @@ -477,6 +486,7 @@ export const HandleNotifications = ({ notificationId }: Props) => { appDeploy: notification.appDeploy, dokployRestart: notification.dokployRestart, databaseBackup: notification.databaseBackup, + dokployBackup: notification.dokployBackup, type: notification.notificationType, webhookUrl: notification.lark?.webhookUrl, name: notification.name, @@ -490,6 +500,7 @@ export const HandleNotifications = ({ notificationId }: Props) => { appDeploy: notification.appDeploy, dokployRestart: notification.dokployRestart, databaseBackup: notification.databaseBackup, + dokployBackup: notification.dokployBackup, volumeBackup: notification.volumeBackup, type: notification.notificationType, webhookUrl: notification.teams?.webhookUrl, @@ -503,6 +514,7 @@ export const HandleNotifications = ({ notificationId }: Props) => { appDeploy: notification.appDeploy, dokployRestart: notification.dokployRestart, databaseBackup: notification.databaseBackup, + dokployBackup: notification.dokployBackup, type: notification.notificationType, endpoint: notification.custom?.endpoint || "", headers: notification.custom?.headers @@ -524,6 +536,7 @@ export const HandleNotifications = ({ notificationId }: Props) => { appDeploy: notification.appDeploy, dokployRestart: notification.dokployRestart, databaseBackup: notification.databaseBackup, + dokployBackup: notification.dokployBackup, volumeBackup: notification.volumeBackup, type: notification.notificationType, userKey: notification.pushover?.userKey, @@ -562,6 +575,7 @@ export const HandleNotifications = ({ notificationId }: Props) => { appDeploy, dokployRestart, databaseBackup, + dokployBackup, volumeBackup, dockerCleanup, serverThreshold, @@ -573,6 +587,7 @@ export const HandleNotifications = ({ notificationId }: Props) => { appDeploy: appDeploy, dokployRestart: dokployRestart, databaseBackup: databaseBackup, + dokployBackup: dokployBackup, volumeBackup: volumeBackup, webhookUrl: data.webhookUrl, channel: data.channel, @@ -588,6 +603,7 @@ export const HandleNotifications = ({ notificationId }: Props) => { appDeploy: appDeploy, dokployRestart: dokployRestart, databaseBackup: databaseBackup, + dokployBackup: dokployBackup, volumeBackup: volumeBackup, botToken: data.botToken, messageThreadId: data.messageThreadId || "", @@ -604,6 +620,7 @@ export const HandleNotifications = ({ notificationId }: Props) => { appDeploy: appDeploy, dokployRestart: dokployRestart, databaseBackup: databaseBackup, + dokployBackup: dokployBackup, volumeBackup: volumeBackup, webhookUrl: data.webhookUrl, decoration: data.decoration, @@ -619,6 +636,7 @@ export const HandleNotifications = ({ notificationId }: Props) => { appDeploy: appDeploy, dokployRestart: dokployRestart, databaseBackup: databaseBackup, + dokployBackup: dokployBackup, volumeBackup: volumeBackup, smtpServer: data.smtpServer, smtpPort: data.smtpPort, @@ -638,6 +656,7 @@ export const HandleNotifications = ({ notificationId }: Props) => { appDeploy: appDeploy, dokployRestart: dokployRestart, databaseBackup: databaseBackup, + dokployBackup: dokployBackup, volumeBackup: volumeBackup, apiKey: data.apiKey, fromAddress: data.fromAddress, @@ -654,6 +673,7 @@ export const HandleNotifications = ({ notificationId }: Props) => { appDeploy: appDeploy, dokployRestart: dokployRestart, databaseBackup: databaseBackup, + dokployBackup: dokployBackup, volumeBackup: volumeBackup, serverUrl: data.serverUrl, appToken: data.appToken, @@ -670,6 +690,7 @@ export const HandleNotifications = ({ notificationId }: Props) => { appDeploy: appDeploy, dokployRestart: dokployRestart, databaseBackup: databaseBackup, + dokployBackup: dokployBackup, volumeBackup: volumeBackup, serverUrl: data.serverUrl, accessToken: data.accessToken || "", @@ -686,6 +707,7 @@ export const HandleNotifications = ({ notificationId }: Props) => { appDeploy: appDeploy, dokployRestart: dokployRestart, databaseBackup: databaseBackup, + dokployBackup: dokployBackup, volumeBackup: volumeBackup, webhookUrl: data.webhookUrl, channel: data.channel || undefined, @@ -702,6 +724,7 @@ export const HandleNotifications = ({ notificationId }: Props) => { appDeploy: appDeploy, dokployRestart: dokployRestart, databaseBackup: databaseBackup, + dokployBackup: dokployBackup, volumeBackup: volumeBackup, webhookUrl: data.webhookUrl, name: data.name, @@ -716,6 +739,7 @@ export const HandleNotifications = ({ notificationId }: Props) => { appDeploy: appDeploy, dokployRestart: dokployRestart, databaseBackup: databaseBackup, + dokployBackup: dokployBackup, volumeBackup: volumeBackup, webhookUrl: data.webhookUrl, name: data.name, @@ -742,6 +766,7 @@ export const HandleNotifications = ({ notificationId }: Props) => { appDeploy: appDeploy, dokployRestart: dokployRestart, databaseBackup: databaseBackup, + dokployBackup: dokployBackup, volumeBackup: volumeBackup, endpoint: data.endpoint, headers: headersRecord, @@ -761,6 +786,7 @@ export const HandleNotifications = ({ notificationId }: Props) => { appDeploy: appDeploy, dokployRestart: dokployRestart, databaseBackup: databaseBackup, + dokployBackup: dokployBackup, volumeBackup: volumeBackup, userKey: data.userKey, apiToken: data.apiToken, @@ -1856,6 +1882,27 @@ export const HandleNotifications = ({ notificationId }: Props) => { )} /> + ( + +
+ Dokploy Backup + + Trigger the action when a dokploy backup is created. + +
+ + + +
+ )} + /> + { + const previewText = `Dokploy instance backup was ${type === "success" ? "successful ✅" : "failed ❌"}`; + + return ( + + {previewText} + + + + +
+ Dokploy +
+ + Dokploy Instance Backup + + + Hello, + + + Your Dokploy instance backup was{" "} + {type === "success" + ? "successful ✅" + : "failed. Please check the error message below. ❌"} + . + +
+ Details: + + Backup Type: Complete Dokploy Instance + + + Content: /etc/dokploy + PostgreSQL Database + + {backupSize && ( + + Backup Size: {backupSize} + + )} + + Date: {date} + + + Status:{" "} + {type === "success" ? "Successful" : "Failed"} + +
+ {type === "error" && errorMessage ? ( +
+ Reason: + + {errorMessage || "Error message not provided"} + +
+ ) : null} +
+ +
+ + ); +}; + +export default DokployBackupEmail; diff --git a/packages/server/src/services/notification.ts b/packages/server/src/services/notification.ts index a285458ef..2c8247f0c 100644 --- a/packages/server/src/services/notification.ts +++ b/packages/server/src/services/notification.ts @@ -73,6 +73,7 @@ export const createSlackNotification = async ( appDeploy: input.appDeploy, appBuildError: input.appBuildError, databaseBackup: input.databaseBackup, + dokployBackup: input.dokployBackup, volumeBackup: input.volumeBackup, dokployRestart: input.dokployRestart, dockerCleanup: input.dockerCleanup, @@ -105,6 +106,7 @@ export const updateSlackNotification = async ( appDeploy: input.appDeploy, appBuildError: input.appBuildError, databaseBackup: input.databaseBackup, + dokployBackup: input.dokployBackup, volumeBackup: input.volumeBackup, dokployRestart: input.dokployRestart, dockerCleanup: input.dockerCleanup, @@ -166,6 +168,7 @@ export const createTelegramNotification = async ( appDeploy: input.appDeploy, appBuildError: input.appBuildError, databaseBackup: input.databaseBackup, + dokployBackup: input.dokployBackup, volumeBackup: input.volumeBackup, dokployRestart: input.dokployRestart, dockerCleanup: input.dockerCleanup, @@ -198,6 +201,7 @@ export const updateTelegramNotification = async ( appDeploy: input.appDeploy, appBuildError: input.appBuildError, databaseBackup: input.databaseBackup, + dokployBackup: input.dokployBackup, volumeBackup: input.volumeBackup, dokployRestart: input.dokployRestart, dockerCleanup: input.dockerCleanup, @@ -259,6 +263,7 @@ export const createDiscordNotification = async ( appDeploy: input.appDeploy, appBuildError: input.appBuildError, databaseBackup: input.databaseBackup, + dokployBackup: input.dokployBackup, volumeBackup: input.volumeBackup, dokployRestart: input.dokployRestart, dockerCleanup: input.dockerCleanup, @@ -291,6 +296,7 @@ export const updateDiscordNotification = async ( appDeploy: input.appDeploy, appBuildError: input.appBuildError, databaseBackup: input.databaseBackup, + dokployBackup: input.dokployBackup, volumeBackup: input.volumeBackup, dokployRestart: input.dokployRestart, dockerCleanup: input.dockerCleanup, @@ -355,6 +361,7 @@ export const createEmailNotification = async ( appDeploy: input.appDeploy, appBuildError: input.appBuildError, databaseBackup: input.databaseBackup, + dokployBackup: input.dokployBackup, volumeBackup: input.volumeBackup, dokployRestart: input.dokployRestart, dockerCleanup: input.dockerCleanup, @@ -387,6 +394,7 @@ export const updateEmailNotification = async ( appDeploy: input.appDeploy, appBuildError: input.appBuildError, databaseBackup: input.databaseBackup, + dokployBackup: input.dokployBackup, volumeBackup: input.volumeBackup, dokployRestart: input.dokployRestart, dockerCleanup: input.dockerCleanup, @@ -452,6 +460,7 @@ export const createResendNotification = async ( appDeploy: input.appDeploy, appBuildError: input.appBuildError, databaseBackup: input.databaseBackup, + dokployBackup: input.dokployBackup, volumeBackup: input.volumeBackup, dokployRestart: input.dokployRestart, dockerCleanup: input.dockerCleanup, @@ -484,6 +493,7 @@ export const updateResendNotification = async ( appDeploy: input.appDeploy, appBuildError: input.appBuildError, databaseBackup: input.databaseBackup, + dokployBackup: input.dokployBackup, volumeBackup: input.volumeBackup, dokployRestart: input.dokployRestart, dockerCleanup: input.dockerCleanup, @@ -547,6 +557,7 @@ export const createGotifyNotification = async ( appDeploy: input.appDeploy, appBuildError: input.appBuildError, databaseBackup: input.databaseBackup, + dokployBackup: input.dokployBackup, volumeBackup: input.volumeBackup, dokployRestart: input.dokployRestart, dockerCleanup: input.dockerCleanup, @@ -578,6 +589,7 @@ export const updateGotifyNotification = async ( appDeploy: input.appDeploy, appBuildError: input.appBuildError, databaseBackup: input.databaseBackup, + dokployBackup: input.dokployBackup, volumeBackup: input.volumeBackup, dokployRestart: input.dokployRestart, dockerCleanup: input.dockerCleanup, @@ -639,6 +651,7 @@ export const createNtfyNotification = async ( appDeploy: input.appDeploy, appBuildError: input.appBuildError, databaseBackup: input.databaseBackup, + dokployBackup: input.dokployBackup, volumeBackup: input.volumeBackup, dokployRestart: input.dokployRestart, dockerCleanup: input.dockerCleanup, @@ -670,6 +683,7 @@ export const updateNtfyNotification = async ( appDeploy: input.appDeploy, appBuildError: input.appBuildError, databaseBackup: input.databaseBackup, + dokployBackup: input.dokployBackup, volumeBackup: input.volumeBackup, dokployRestart: input.dokployRestart, dockerCleanup: input.dockerCleanup, @@ -729,6 +743,7 @@ export const createCustomNotification = async ( appDeploy: input.appDeploy, appBuildError: input.appBuildError, databaseBackup: input.databaseBackup, + dokployBackup: input.dokployBackup, volumeBackup: input.volumeBackup, dokployRestart: input.dokployRestart, dockerCleanup: input.dockerCleanup, @@ -761,6 +776,7 @@ export const updateCustomNotification = async ( appDeploy: input.appDeploy, appBuildError: input.appBuildError, databaseBackup: input.databaseBackup, + dokployBackup: input.dokployBackup, volumeBackup: input.volumeBackup, dokployRestart: input.dokployRestart, dockerCleanup: input.dockerCleanup, @@ -854,6 +870,7 @@ export const createLarkNotification = async ( appDeploy: input.appDeploy, appBuildError: input.appBuildError, databaseBackup: input.databaseBackup, + dokployBackup: input.dokployBackup, volumeBackup: input.volumeBackup, dokployRestart: input.dokployRestart, dockerCleanup: input.dockerCleanup, @@ -886,6 +903,7 @@ export const updateLarkNotification = async ( appDeploy: input.appDeploy, appBuildError: input.appBuildError, databaseBackup: input.databaseBackup, + dokployBackup: input.dokployBackup, volumeBackup: input.volumeBackup, dokployRestart: input.dokployRestart, dockerCleanup: input.dockerCleanup, @@ -944,6 +962,7 @@ export const createTeamsNotification = async ( appDeploy: input.appDeploy, appBuildError: input.appBuildError, databaseBackup: input.databaseBackup, + dokployBackup: input.dokployBackup, volumeBackup: input.volumeBackup, dokployRestart: input.dokployRestart, dockerCleanup: input.dockerCleanup, @@ -976,6 +995,7 @@ export const updateTeamsNotification = async ( appDeploy: input.appDeploy, appBuildError: input.appBuildError, databaseBackup: input.databaseBackup, + dokployBackup: input.dokployBackup, volumeBackup: input.volumeBackup, dokployRestart: input.dokployRestart, dockerCleanup: input.dockerCleanup, @@ -1051,6 +1071,7 @@ export const createMattermostNotification = async ( appDeploy: input.appDeploy, appBuildError: input.appBuildError, databaseBackup: input.databaseBackup, + dokployBackup: input.dokployBackup, volumeBackup: input.volumeBackup, dokployRestart: input.dokployRestart, dockerCleanup: input.dockerCleanup, @@ -1083,6 +1104,7 @@ export const updateMattermostNotification = async ( appDeploy: input.appDeploy, appBuildError: input.appBuildError, databaseBackup: input.databaseBackup, + dokployBackup: input.dokployBackup, volumeBackup: input.volumeBackup, dokployRestart: input.dokployRestart, dockerCleanup: input.dockerCleanup, @@ -1147,6 +1169,7 @@ export const createPushoverNotification = async ( appDeploy: input.appDeploy, appBuildError: input.appBuildError, databaseBackup: input.databaseBackup, + dokployBackup: input.dokployBackup, volumeBackup: input.volumeBackup, dokployRestart: input.dokployRestart, dockerCleanup: input.dockerCleanup, @@ -1179,6 +1202,7 @@ export const updatePushoverNotification = async ( appDeploy: input.appDeploy, appBuildError: input.appBuildError, databaseBackup: input.databaseBackup, + dokployBackup: input.dokployBackup, volumeBackup: input.volumeBackup, dokployRestart: input.dokployRestart, dockerCleanup: input.dockerCleanup, diff --git a/packages/server/src/utils/backups/web-server.ts b/packages/server/src/utils/backups/web-server.ts index a6ab20a8c..19dd44eed 100644 --- a/packages/server/src/utils/backups/web-server.ts +++ b/packages/server/src/utils/backups/web-server.ts @@ -1,5 +1,5 @@ import { createWriteStream } from "node:fs"; -import { mkdtemp, rm } from "node:fs/promises"; +import { mkdtemp, rm, stat } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { IS_CLOUD, paths } from "@dokploy/server/constants"; @@ -9,9 +9,19 @@ import { updateDeploymentStatus, } from "@dokploy/server/services/deployment"; import { findDestinationById } from "@dokploy/server/services/destination"; +import { sendDokployBackupNotifications } from "../notifications/dokploy-backup"; import { execAsync } from "../process/execAsync"; import { getS3Credentials, normalizeS3Path } from "./utils"; +function formatBytes(bytes?: number) { + if (bytes === undefined) return "Unknown size"; + if (bytes === 0) return "0 B"; + const sizes = ["B", "KB", "MB", "GB", "TB"]; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + const value = bytes / Math.pow(1024, i); + return `${value.toFixed(2)} ${sizes[i]} (${bytes} bytes)`; +} + export const runWebServerBackup = async (backup: BackupSchedule) => { if (IS_CLOUD) { return; @@ -23,7 +33,7 @@ export const runWebServerBackup = async (backup: BackupSchedule) => { description: "Web Server Backup", }); const writeStream = createWriteStream(deployment.logPath, { flags: "a" }); - + let computedBackupSize: number | undefined; try { const destination = await findDestinationById(backup.destinationId); const rcloneFlags = getS3Credentials(destination); @@ -79,11 +89,24 @@ export const runWebServerBackup = async (backup: BackupSchedule) => { writeStream.write("Zipped database and filesystem\n"); - const uploadCommand = `rclone copyto ${rcloneFlags.join(" ")} "${tempDir}/${backupFileName}" "${s3Path}"`; + const zipPath = join(tempDir, backupFileName); + try { + const { size } = await stat(zipPath); + computedBackupSize = size; + writeStream.write(`Backup size: ${size} bytes\n`); + } catch { + // If stat fails, keep undefined + } + + const uploadCommand = `rclone copyto ${rcloneFlags.join(" ")} "${zipPath}" "${s3Path}"`; writeStream.write("Running command to upload backup to S3\n"); await execAsync(uploadCommand); writeStream.write("Uploaded backup to S3 ✅\n"); writeStream.end(); + await sendDokployBackupNotifications({ + type: "success", + backupSize: formatBytes(computedBackupSize), + }); await updateDeploymentStatus(deployment.deploymentId, "done"); return true; } finally { @@ -100,6 +123,12 @@ export const runWebServerBackup = async (backup: BackupSchedule) => { error instanceof Error ? error.message : "Unknown error\n", ); writeStream.end(); + await sendDokployBackupNotifications({ + type: "error", + // @ts-ignore + errorMessage: error?.message || "Error message not provided", + backupSize: formatBytes(computedBackupSize), + }); await updateDeploymentStatus(deployment.deploymentId, "error"); throw error; } diff --git a/packages/server/src/utils/notifications/dokploy-backup.ts b/packages/server/src/utils/notifications/dokploy-backup.ts new file mode 100644 index 000000000..26a88d657 --- /dev/null +++ b/packages/server/src/utils/notifications/dokploy-backup.ts @@ -0,0 +1,416 @@ +import { db } from "@dokploy/server/db"; +import { notifications } from "@dokploy/server/db/schema"; +import DokployBackupEmail from "@dokploy/server/emails/emails/dokploy-backup"; +import { renderAsync } from "@react-email/components"; +import { format } from "date-fns"; +import { eq } from "drizzle-orm"; +import { + sendCustomNotification, + sendDiscordNotification, + sendEmailNotification, + sendGotifyNotification, + sendLarkNotification, + sendMattermostNotification, + sendNtfyNotification, + sendPushoverNotification, + sendResendNotification, + sendSlackNotification, + sendTeamsNotification, + sendTelegramNotification, +} from "./utils"; + +export const sendDokployBackupNotifications = async ({ + type, + errorMessage, + backupSize, +}: { + type: "error" | "success"; + errorMessage?: string; + backupSize?: string; +}) => { + const date = new Date(); + const unixDate = ~~(Number(date) / 1000); + const notificationList = await db.query.notifications.findMany({ + where: eq(notifications.dokployBackup, true), + with: { + email: true, + discord: true, + telegram: true, + slack: true, + resend: true, + gotify: true, + ntfy: true, + mattermost: true, + custom: true, + lark: true, + pushover: true, + teams: true, + }, + }); + + for (const notification of notificationList) { + const { + email, + discord, + telegram, + slack, + resend, + gotify, + ntfy, + mattermost, + custom, + lark, + pushover, + teams, + } = notification; + + try { + if (email || resend) { + const template = await renderAsync( + DokployBackupEmail({ + type, + errorMessage, + date: date.toLocaleString(), + backupSize, + }), + ).catch(); + + if (email) { + await sendEmailNotification( + email, + "Dokploy instance backup", + template, + ); + } + + if (resend) { + await sendResendNotification( + resend, + "Dokploy instance backup", + template, + ); + } + } + + if (discord) { + const decorate = (decoration: string, text: string) => + `${discord.decoration ? decoration : ""} ${text}`.trim(); + + await sendDiscordNotification(discord, { + title: + type === "success" + ? decorate(">", "`✅` Dokploy Backup Successful") + : decorate(">", "`❌` Dokploy Backup Failed"), + color: type === "success" ? 0x57f287 : 0xed4245, + fields: [ + { + name: decorate("`📦`", "Backup Type"), + value: "Complete Dokploy Instance", + inline: true, + }, + ...(backupSize + ? [ + { + name: decorate("`💾`", "Backup Size"), + value: backupSize, + inline: true, + }, + ] + : []), + { + name: decorate("`📅`", "Date"), + value: ``, + inline: true, + }, + { + name: decorate("`⌚`", "Time"), + value: ``, + inline: true, + }, + { + name: decorate("`❓`", "Status"), + value: type + .replace("error", "Failed") + .replace("success", "Successful"), + inline: true, + }, + ...(type === "error" && errorMessage + ? [ + { + name: decorate("`⚠️`", "Error Message"), + value: `\`\`\`${errorMessage}\`\`\``, + }, + ] + : []), + ], + timestamp: date.toISOString(), + footer: { + text: "Dokploy Instance Backup Notification", + }, + }); + } + + if (gotify) { + const decorate = (decoration: string, text: string) => + `${gotify.decoration ? decoration : ""} ${text}\n`; + + await sendGotifyNotification( + gotify, + decorate( + type === "success" ? "✅" : "❌", + `Dokploy Backup ${type === "success" ? "Successful" : "Failed"}`, + ), + `${decorate("📦", "Backup Type: Complete Dokploy Instance")}` + + `${backupSize ? decorate("💾", `Backup Size: ${backupSize}`) : ""}` + + `${decorate("🕒", `Date: ${date.toLocaleString()}`)}` + + `${type === "error" && errorMessage ? decorate("❌", `Error:\n${errorMessage}`) : ""}`, + ); + } + + if (ntfy) { + await sendNtfyNotification( + ntfy, + `Dokploy Backup ${type === "success" ? "Successful" : "Failed"}`, + `${type === "success" ? "white_check_mark" : "x"}`, + "", + "📦Backup Type: Complete Dokploy Instance\n" + + `${backupSize ? `💾Backup Size: ${backupSize}\n` : ""}` + + `🕒Date: ${date.toLocaleString()}\n` + + `${type === "error" && errorMessage ? `❌Error:\n${errorMessage}` : ""}`, + ); + } + + if (telegram) { + const isError = type === "error" && errorMessage; + + const statusEmoji = type === "success" ? "✅" : "❌"; + const typeStatus = type === "success" ? "Successful" : "Failed"; + const errorMsg = isError + ? `\n\nError:\n
${errorMessage}
` + : ""; + const sizeInfo = backupSize + ? `\nBackup Size: ${backupSize}` + : ""; + + const messageText = `${statusEmoji} Dokploy Backup ${typeStatus}\n\nBackup Type: Complete Dokploy Instance${sizeInfo}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}${isError ? errorMsg : ""}`; + + await sendTelegramNotification(telegram, messageText); + } + + if (slack) { + const { channel } = slack; + await sendSlackNotification(slack, { + channel: channel, + attachments: [ + { + color: type === "success" ? "#00FF00" : "#FF0000", + pretext: + type === "success" + ? ":white_check_mark: *Dokploy Backup Successful*" + : ":x: *Dokploy Backup Failed*", + fields: [ + ...(type === "error" && errorMessage + ? [ + { + title: "Error Message", + value: errorMessage, + short: false, + }, + ] + : []), + { + title: "Backup Type", + value: "Complete Dokploy Instance", + short: true, + }, + ...(backupSize + ? [ + { + title: "Backup Size", + value: backupSize, + short: true, + }, + ] + : []), + { + title: "Time", + value: date.toLocaleString(), + short: true, + }, + { + title: "Status", + value: type === "success" ? "Successful" : "Failed", + short: true, + }, + ], + }, + ], + }); + } + + if (lark) { + const limitCharacter = 800; + const truncatedErrorMessage = + errorMessage && errorMessage.length > limitCharacter + ? errorMessage.substring(0, limitCharacter) + : errorMessage; + + await sendLarkNotification(lark, { + msg_type: "interactive", + card: { + schema: "2.0", + config: { + update_multi: true, + style: { + text_size: { + normal_v2: { + default: "normal", + pc: "normal", + mobile: "heading", + }, + }, + }, + }, + header: { + title: { + tag: "plain_text", + content: + type === "success" + ? "✅ Dokploy Backup Successful" + : "❌ Dokploy Backup Failed", + }, + subtitle: { + tag: "plain_text", + content: "", + }, + template: type === "success" ? "green" : "red", + padding: "12px 12px 12px 12px", + }, + body: { + direction: "vertical", + padding: "12px 12px 12px 12px", + elements: [ + { + tag: "column_set", + columns: [ + { + tag: "column", + width: "weighted", + elements: [ + { + tag: "markdown", + content: + "**Backup Type:**\nComplete Dokploy Instance", + text_align: "left", + text_size: "normal_v2", + }, + { + tag: "markdown", + content: `**Status:**\n${type === "success" ? "Successful" : "Failed"}`, + text_align: "left", + text_size: "normal_v2", + }, + ], + vertical_align: "top", + weight: 1, + }, + { + tag: "column", + width: "weighted", + elements: [ + ...(backupSize + ? [ + { + tag: "markdown", + content: `**Backup Size:**\n${backupSize}`, + text_align: "left", + text_size: "normal_v2", + }, + ] + : []), + { + tag: "markdown", + content: `**Date:**\n${format(date, "PP pp")}`, + text_align: "left", + text_size: "normal_v2", + }, + ], + vertical_align: "top", + weight: 1, + }, + ], + }, + ...(type === "error" && truncatedErrorMessage + ? [ + { + tag: "markdown", + content: `**Error Message:**\n\`\`\`\n${truncatedErrorMessage}\n\`\`\``, + text_align: "left", + text_size: "normal_v2", + }, + ] + : []), + ], + }, + }, + }); + } + + if (mattermost) { + const statusEmoji = type === "success" ? ":white_check_mark:" : ":x:"; + const typeStatus = type === "success" ? "Successful" : "Failed"; + await sendMattermostNotification(mattermost, { + text: `${statusEmoji} **Dokploy Backup ${typeStatus}** + +**Backup Type:** Complete Dokploy Instance${backupSize ? `\n**Backup Size:** ${backupSize}` : ""} +**Date:** ${date.toLocaleString()} +**Status:** ${typeStatus}${type === "error" && errorMessage ? `\n\n**Error:**\n\`\`\`\n${errorMessage}\n\`\`\`` : ""}`, + channel: mattermost.channel, + username: mattermost.username || "Dokploy Bot", + }); + } + + if (custom) { + await sendCustomNotification(custom, { + title: `Dokploy Backup ${type === "success" ? "Successful" : "Failed"}`, + message: `Dokploy instance backup ${type === "success" ? "completed successfully" : "failed"}`, + backupType: "Complete Dokploy Instance", + ...(backupSize ? { backupSize } : {}), + ...(type === "error" && errorMessage ? { errorMessage } : {}), + timestamp: date.toISOString(), + date: date.toLocaleString(), + status: type, + type: "dokploy-backup", + }); + } + + if (pushover) { + await sendPushoverNotification( + pushover, + `Dokploy Backup ${type === "success" ? "Successful" : "Failed"}`, + `Backup Type: Complete Dokploy Instance${backupSize ? `\nBackup Size: ${backupSize}` : ""}\nDate: ${date.toLocaleString()}${type === "error" && errorMessage ? `\nError: ${errorMessage}` : ""}`, + ); + } + + if (teams) { + await sendTeamsNotification(teams, { + title: `${type === "success" ? "✅" : "❌"} Dokploy Backup ${type === "success" ? "Successful" : "Failed"}`, + facts: [ + { name: "Backup Type", value: "Complete Dokploy Instance" }, + ...(backupSize ? [{ name: "Backup Size", value: backupSize }] : []), + { name: "Date", value: format(date, "PP pp") }, + { + name: "Status", + value: type === "success" ? "Successful" : "Failed", + }, + ...(type === "error" && errorMessage + ? [{ name: "Error Message", value: errorMessage }] + : []), + ], + }); + } + } catch (error) { + console.error(error); + } + } +};