From a3a3f2ce2e34c263f76b979ae3643c499ef2be61 Mon Sep 17 00:00:00 2001 From: Furox Date: Fri, 4 Sep 2026 20:24:18 +0300 Subject: [PATCH] feat: make backup retention destination-neutral --- packages/server/src/utils/backups/index.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/server/src/utils/backups/index.ts b/packages/server/src/utils/backups/index.ts index d0d304558..d9584dc83 100644 --- a/packages/server/src/utils/backups/index.ts +++ b/packages/server/src/utils/backups/index.ts @@ -6,13 +6,18 @@ import { getAllServers } from "@dokploy/server/services/server"; import { getWebServerSettings } from "@dokploy/server/services/web-server-settings"; import { eq } from "drizzle-orm"; import { scheduleJob } from "node-schedule"; +import { quote } from "shell-quote"; import { db } from "../../db/index"; import { startLogCleanup } from "../access-log/handler"; import { cleanupAll } from "../docker/utils"; import { sendDockerCleanupNotifications } from "../notifications/docker-cleanup"; import { execAsync, execAsyncRemote } from "../process/execAsync"; import { redactRcloneCredentials } from "./redact"; -import { getS3Credentials, normalizeS3Path, scheduleBackup } from "./utils"; +import { + getRclonePathAndFlags, + normalizeS3Path, + scheduleBackup, +} from "./utils"; export const initCronJobs = async () => { console.log("Setting up cron jobs...."); @@ -93,7 +98,7 @@ export const initCronJobs = async () => { } } catch (error) { console.error(`[Backup] ${backup.databaseType} Error`, error); - } + } } if (webServerSettings?.logCleanupCron) { @@ -134,17 +139,19 @@ export const keepLatestNBackups = async ( try { const destination = await findDestinationById(backup.destinationId); - const rcloneFlags = getS3Credentials(destination); const appName = getServiceAppName(backup); - const backupFilesPath = `:s3:${destination.bucket}/${appName}/${normalizeS3Path(backup.prefix)}`; + const { flags: rcloneFlags, path: backupFilesPath } = + await getRclonePathAndFlags( + destination, + `${appName}/${normalizeS3Path(backup.prefix)}`, + ); // --include "*.bson.gz" or "*.sql.gz" or "*.zip" ensures nothing else other than the dokploy backup files are touched by rclone - const rcloneList = `rclone lsf ${rcloneFlags.join(" ")} --include "*${backup.databaseType === "web-server" ? ".zip" : ".{sql.gz,bson.gz}"}" ${backupFilesPath}`; + const rcloneList = `rclone lsf ${rcloneFlags.join(" ")} --include "*${backup.databaseType === "web-server" ? ".zip" : ".{sql.gz,bson.gz}"}" ${quote([backupFilesPath])}`; // when we pipe the above command with this one, we only get the list of files we want to delete const sortAndPickUnwantedBackups = `sort -r | tail -n +$((${backup.keepLatestCount}+1)) | xargs -I{}`; // this command deletes the files - // to test the deletion before actually deleting we can add --dry-run before ${backupFilesPath}{} - const rcloneDelete = `rclone delete ${rcloneFlags.join(" ")} ${backupFilesPath}{}`; + const rcloneDelete = `rclone deletefile ${rcloneFlags.join(" ")} ${quote([`${backupFilesPath}/{}`])}`; const rcloneCommand = `${rcloneList} | ${sortAndPickUnwantedBackups} ${rcloneDelete}`;