mirror of
https://github.com/andwati/dokploy.git
synced 2026-11-06 04:02:16 +00:00
feat: enhance volume backup scheduling and management
- Added initVolumeBackupsCronJobs function to initialize scheduled volume backups on server startup. - Updated volumeBackupsRouter to handle scheduling and removal of volume backup jobs based on user input. - Improved create and update volume backup logic to include scheduling functionality for both cloud and local environments. - Introduced utility functions for scheduling and removing volume backup jobs, enhancing overall backup management.
This commit is contained in:
@@ -6,6 +6,8 @@ import {
|
|||||||
runVolumeBackup,
|
runVolumeBackup,
|
||||||
findVolumeBackupById,
|
findVolumeBackupById,
|
||||||
restoreVolume,
|
restoreVolume,
|
||||||
|
scheduleVolumeBackup,
|
||||||
|
removeVolumeBackupJob,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import {
|
import {
|
||||||
createVolumeBackupSchema,
|
createVolumeBackupSchema,
|
||||||
@@ -21,6 +23,8 @@ import {
|
|||||||
execAsyncRemote,
|
execAsyncRemote,
|
||||||
execAsyncStream,
|
execAsyncStream,
|
||||||
} from "@dokploy/server/utils/process/execAsync";
|
} from "@dokploy/server/utils/process/execAsync";
|
||||||
|
import { removeJob, schedule, updateJob } from "@/server/utils/backup";
|
||||||
|
import { TRPCError } from "@trpc/server";
|
||||||
|
|
||||||
export const volumeBackupsRouter = createTRPCRouter({
|
export const volumeBackupsRouter = createTRPCRouter({
|
||||||
list: protectedProcedure
|
list: protectedProcedure
|
||||||
@@ -55,7 +59,20 @@ export const volumeBackupsRouter = createTRPCRouter({
|
|||||||
create: protectedProcedure
|
create: protectedProcedure
|
||||||
.input(createVolumeBackupSchema)
|
.input(createVolumeBackupSchema)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
return await createVolumeBackup(input);
|
const newVolumeBackup = await createVolumeBackup(input);
|
||||||
|
|
||||||
|
if (newVolumeBackup?.enabled) {
|
||||||
|
if (IS_CLOUD) {
|
||||||
|
await schedule({
|
||||||
|
cronSchedule: newVolumeBackup.cronExpression,
|
||||||
|
volumeBackupId: newVolumeBackup.volumeBackupId,
|
||||||
|
type: "volume-backup",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await scheduleVolumeBackup(newVolumeBackup.volumeBackupId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newVolumeBackup;
|
||||||
}),
|
}),
|
||||||
one: protectedProcedure
|
one: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
@@ -73,15 +90,46 @@ export const volumeBackupsRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
if (IS_CLOUD) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return await removeVolumeBackup(input.volumeBackupId);
|
return await removeVolumeBackup(input.volumeBackupId);
|
||||||
}),
|
}),
|
||||||
update: protectedProcedure
|
update: protectedProcedure
|
||||||
.input(updateVolumeBackupSchema)
|
.input(updateVolumeBackupSchema)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
return await updateVolumeBackup(input.volumeBackupId, input);
|
const updatedVolumeBackup = await updateVolumeBackup(
|
||||||
|
input.volumeBackupId,
|
||||||
|
input,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!updatedVolumeBackup) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "Volume backup not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_CLOUD) {
|
||||||
|
if (updatedVolumeBackup.enabled) {
|
||||||
|
await updateJob({
|
||||||
|
cronSchedule: updatedVolumeBackup.cronExpression,
|
||||||
|
volumeBackupId: updatedVolumeBackup.volumeBackupId,
|
||||||
|
type: "volume-backup",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await removeJob({
|
||||||
|
cronSchedule: updatedVolumeBackup.cronExpression,
|
||||||
|
volumeBackupId: updatedVolumeBackup.volumeBackupId,
|
||||||
|
type: "volume-backup",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (updatedVolumeBackup?.enabled) {
|
||||||
|
removeVolumeBackupJob(updatedVolumeBackup.volumeBackupId);
|
||||||
|
scheduleVolumeBackup(updatedVolumeBackup.volumeBackupId);
|
||||||
|
} else {
|
||||||
|
removeVolumeBackupJob(updatedVolumeBackup.volumeBackupId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return updatedVolumeBackup;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
runManually: protectedProcedure
|
runManually: protectedProcedure
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
createDefaultTraefikConfig,
|
createDefaultTraefikConfig,
|
||||||
initCronJobs,
|
initCronJobs,
|
||||||
initSchedules,
|
initSchedules,
|
||||||
|
initVolumeBackupsCronJobs,
|
||||||
initializeNetwork,
|
initializeNetwork,
|
||||||
sendDokployRestartNotifications,
|
sendDokployRestartNotifications,
|
||||||
setupDirectories,
|
setupDirectories,
|
||||||
@@ -51,6 +52,7 @@ void app.prepare().then(async () => {
|
|||||||
await migration();
|
await migration();
|
||||||
await initCronJobs();
|
await initCronJobs();
|
||||||
await initSchedules();
|
await initSchedules();
|
||||||
|
await initVolumeBackupsCronJobs();
|
||||||
await sendDokployRestartNotifications();
|
await sendDokployRestartNotifications();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ type QueueJob =
|
|||||||
type: "schedule";
|
type: "schedule";
|
||||||
cronSchedule: string;
|
cronSchedule: string;
|
||||||
scheduleId: string;
|
scheduleId: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "volume-backup";
|
||||||
|
cronSchedule: string;
|
||||||
|
volumeBackupId: string;
|
||||||
};
|
};
|
||||||
export const schedule = async (job: QueueJob) => {
|
export const schedule = async (job: QueueJob) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
import { db } from "../db";
|
import { db } from "../db";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import type { z } from "zod";
|
import type { z } from "zod";
|
||||||
import { scheduleBackup } from "../utils/backups/utils";
|
|
||||||
|
|
||||||
export const findVolumeBackupById = async (volumeBackupId: string) => {
|
export const findVolumeBackupById = async (volumeBackupId: string) => {
|
||||||
const volumeBackup = await db.query.volumeBackups.findFirst({
|
const volumeBackup = await db.query.volumeBackups.findFirst({
|
||||||
@@ -43,14 +42,6 @@ export const createVolumeBackup = async (
|
|||||||
.returning()
|
.returning()
|
||||||
.then((e) => e[0]);
|
.then((e) => e[0]);
|
||||||
|
|
||||||
await schedule({
|
|
||||||
cronSchedule: backup.schedule,
|
|
||||||
backupId: backup.backupId,
|
|
||||||
type: "backup",
|
|
||||||
});
|
|
||||||
|
|
||||||
scheduleBackup(backup);
|
|
||||||
|
|
||||||
return newVolumeBackup;
|
return newVolumeBackup;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -64,8 +55,10 @@ export const updateVolumeBackup = async (
|
|||||||
volumeBackupId: string,
|
volumeBackupId: string,
|
||||||
volumeBackup: z.infer<typeof updateVolumeBackupSchema>,
|
volumeBackup: z.infer<typeof updateVolumeBackupSchema>,
|
||||||
) => {
|
) => {
|
||||||
await db
|
return await db
|
||||||
.update(volumeBackups)
|
.update(volumeBackups)
|
||||||
.set(volumeBackup)
|
.set(volumeBackup)
|
||||||
.where(eq(volumeBackups.volumeBackupId, volumeBackupId));
|
.where(eq(volumeBackups.volumeBackupId, volumeBackupId))
|
||||||
|
.returning()
|
||||||
|
.then((e) => e[0]);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,30 @@
|
|||||||
export * from "./backup";
|
export * from "./backup";
|
||||||
export * from "./restore";
|
export * from "./restore";
|
||||||
export * from "./utils";
|
export * from "./utils";
|
||||||
|
import { volumeBackups } from "@dokploy/server/db/schema";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { db } from "../../db/index";
|
||||||
|
import { scheduleVolumeBackup } from "./utils";
|
||||||
|
|
||||||
|
export const initVolumeBackupsCronJobs = async () => {
|
||||||
|
console.log("Setting up volume backups cron jobs....");
|
||||||
|
try {
|
||||||
|
const volumeBackupsResult = await db.query.volumeBackups.findMany({
|
||||||
|
where: eq(volumeBackups.enabled, true),
|
||||||
|
with: {
|
||||||
|
application: true,
|
||||||
|
compose: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Initializing ${volumeBackupsResult.length} volume backups`);
|
||||||
|
for (const volumeBackup of volumeBackupsResult) {
|
||||||
|
scheduleVolumeBackup(volumeBackup.volumeBackupId);
|
||||||
|
console.log(
|
||||||
|
`Initialized volume backup: ${volumeBackup.name} ${volumeBackup.serviceType} ✅`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Error initializing volume backups: ${error}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -6,6 +6,19 @@ import {
|
|||||||
updateDeploymentStatus,
|
updateDeploymentStatus,
|
||||||
} from "../..";
|
} from "../..";
|
||||||
import { backupVolume } from "./backup";
|
import { backupVolume } from "./backup";
|
||||||
|
import { scheduleJob, scheduledJobs } from "node-schedule";
|
||||||
|
|
||||||
|
export const scheduleVolumeBackup = async (volumeBackupId: string) => {
|
||||||
|
const volumeBackup = await findVolumeBackupById(volumeBackupId);
|
||||||
|
scheduleJob(volumeBackupId, volumeBackup.cronExpression, async () => {
|
||||||
|
await runVolumeBackup(volumeBackupId);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeVolumeBackupJob = async (volumeBackupId: string) => {
|
||||||
|
const currentJob = scheduledJobs[volumeBackupId];
|
||||||
|
currentJob?.cancel();
|
||||||
|
};
|
||||||
|
|
||||||
export const runVolumeBackup = async (volumeBackupId: string) => {
|
export const runVolumeBackup = async (volumeBackupId: string) => {
|
||||||
const volumeBackup = await findVolumeBackupById(volumeBackupId);
|
const volumeBackup = await findVolumeBackupById(volumeBackupId);
|
||||||
|
|||||||
Reference in New Issue
Block a user