mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-06 14:35:26 +02:00
- Updated the volume backups query to include ordering by the `createdAt` field in descending order. - This change improves the retrieval of backup records, ensuring the most recent backups are prioritized in the response.
220 lines
5.6 KiB
TypeScript
220 lines
5.6 KiB
TypeScript
import {
|
|
createVolumeBackup,
|
|
findVolumeBackupById,
|
|
IS_CLOUD,
|
|
removeVolumeBackup,
|
|
removeVolumeBackupJob,
|
|
restoreVolume,
|
|
runVolumeBackup,
|
|
scheduleVolumeBackup,
|
|
updateVolumeBackup,
|
|
} from "@dokploy/server";
|
|
import { db } from "@dokploy/server/db";
|
|
import {
|
|
createVolumeBackupSchema,
|
|
updateVolumeBackupSchema,
|
|
volumeBackups,
|
|
} from "@dokploy/server/db/schema";
|
|
import {
|
|
execAsyncRemote,
|
|
execAsyncStream,
|
|
} from "@dokploy/server/utils/process/execAsync";
|
|
import { TRPCError } from "@trpc/server";
|
|
import { observable } from "@trpc/server/observable";
|
|
import { desc, eq } from "drizzle-orm";
|
|
import { z } from "zod";
|
|
import { removeJob, schedule, updateJob } from "@/server/utils/backup";
|
|
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
|
|
|
export const volumeBackupsRouter = createTRPCRouter({
|
|
list: protectedProcedure
|
|
.input(
|
|
z.object({
|
|
id: z.string().min(1),
|
|
volumeBackupType: z.enum([
|
|
"application",
|
|
"postgres",
|
|
"mysql",
|
|
"mariadb",
|
|
"mongo",
|
|
"redis",
|
|
"compose",
|
|
]),
|
|
}),
|
|
)
|
|
.query(async ({ input }) => {
|
|
return await db.query.volumeBackups.findMany({
|
|
where: eq(volumeBackups[`${input.volumeBackupType}Id`], input.id),
|
|
with: {
|
|
application: true,
|
|
postgres: true,
|
|
mysql: true,
|
|
mariadb: true,
|
|
mongo: true,
|
|
redis: true,
|
|
compose: true,
|
|
},
|
|
orderBy: [desc(volumeBackups.createdAt)],
|
|
});
|
|
}),
|
|
create: protectedProcedure
|
|
.input(createVolumeBackupSchema)
|
|
.mutation(async ({ 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
|
|
.input(
|
|
z.object({
|
|
volumeBackupId: z.string().min(1),
|
|
}),
|
|
)
|
|
.query(async ({ input }) => {
|
|
return await findVolumeBackupById(input.volumeBackupId);
|
|
}),
|
|
delete: protectedProcedure
|
|
.input(
|
|
z.object({
|
|
volumeBackupId: z.string().min(1),
|
|
}),
|
|
)
|
|
.mutation(async ({ input }) => {
|
|
return await removeVolumeBackup(input.volumeBackupId);
|
|
}),
|
|
update: protectedProcedure
|
|
.input(updateVolumeBackupSchema)
|
|
.mutation(async ({ 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
|
|
.input(z.object({ volumeBackupId: z.string().min(1) }))
|
|
.mutation(async ({ input }) => {
|
|
try {
|
|
return await runVolumeBackup(input.volumeBackupId);
|
|
} catch (error) {
|
|
console.error(error);
|
|
return false;
|
|
}
|
|
}),
|
|
restoreVolumeBackupWithLogs: protectedProcedure
|
|
.meta({
|
|
openapi: {
|
|
enabled: false,
|
|
path: "/restore-volume-backup-with-logs",
|
|
method: "POST",
|
|
override: true,
|
|
},
|
|
})
|
|
.input(
|
|
z.object({
|
|
backupFileName: z.string().min(1),
|
|
destinationId: z.string().min(1),
|
|
volumeName: z.string().min(1),
|
|
id: z.string().min(1),
|
|
serviceType: z.enum(["application", "compose"]),
|
|
serverId: z.string().optional(),
|
|
}),
|
|
)
|
|
.subscription(async ({ input }) => {
|
|
return observable<string>((emit) => {
|
|
const runRestore = async () => {
|
|
try {
|
|
emit.next("🚀 Starting volume restore process...");
|
|
emit.next(`📂 Backup File: ${input.backupFileName}`);
|
|
emit.next(`🔧 Volume Name: ${input.volumeName}`);
|
|
emit.next(`🏷️ Service Type: ${input.serviceType}`);
|
|
emit.next(""); // Empty line for better readability
|
|
|
|
// Generate the restore command
|
|
const restoreCommand = await restoreVolume(
|
|
input.id,
|
|
input.destinationId,
|
|
input.volumeName,
|
|
input.backupFileName,
|
|
input.serverId || "",
|
|
input.serviceType,
|
|
);
|
|
|
|
emit.next("📋 Generated restore command:");
|
|
emit.next("▶️ Executing restore...");
|
|
emit.next(""); // Empty line
|
|
|
|
// Execute the restore command with real-time output
|
|
if (input.serverId) {
|
|
emit.next(`🌐 Executing on remote server: ${input.serverId}`);
|
|
await execAsyncRemote(input.serverId, restoreCommand, (data) => {
|
|
emit.next(data);
|
|
});
|
|
} else {
|
|
emit.next("🖥️ Executing on local server");
|
|
await execAsyncStream(restoreCommand, (data) => {
|
|
emit.next(data);
|
|
});
|
|
}
|
|
|
|
emit.next("");
|
|
emit.next("✅ Volume restore completed successfully!");
|
|
emit.next(
|
|
"🎉 All containers/services have been restarted with the restored volume.",
|
|
);
|
|
} catch {
|
|
emit.next("");
|
|
emit.next("❌ Volume restore failed!");
|
|
} finally {
|
|
emit.complete();
|
|
}
|
|
};
|
|
|
|
// Start the restore process
|
|
runRestore();
|
|
});
|
|
}),
|
|
});
|