mirror of
https://github.com/biersoeckli/QuickStack.git
synced 2026-02-11 05:59:23 -06:00
feat/add backup scheduling functionality
This commit is contained in:
@@ -82,22 +82,19 @@ export const saveBackupVolume = async (prevState: any, inputData: VolumeBackupEd
|
||||
if (validatedData.retention < 1) {
|
||||
throw new ServiceException('Retention must be at least 1');
|
||||
}
|
||||
if (validatedData.id) {
|
||||
await backupService.unregisterBackupJob(validatedData.id);
|
||||
}
|
||||
const savedVolumeBackup = await volumeBackupService.save({
|
||||
...validatedData,
|
||||
id: validatedData.id ?? undefined,
|
||||
});
|
||||
await backupService.registerBackupJob(savedVolumeBackup);
|
||||
await backupService.registerAllBackups();
|
||||
return new SuccessActionResult();
|
||||
});
|
||||
|
||||
export const deleteBackupVolume = async (backupVolumeId: string) =>
|
||||
simpleAction(async () => {
|
||||
await getAuthUserSession();
|
||||
await backupService.unregisterBackupJob(backupVolumeId);
|
||||
await volumeBackupService.deleteById(backupVolumeId);
|
||||
await backupService.registerAllBackups();
|
||||
return new SuccessActionResult(undefined, 'Successfully deleted backup schedule');
|
||||
});
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import dataAccess from "./server/adapter/db.client";
|
||||
import backupService from "./server/services/standalone-services/backup.service";
|
||||
|
||||
|
||||
export default async function registreAllBackupSchedules() {
|
||||
|
||||
const volumeBackups = await dataAccess.client.volumeBackup.findMany();
|
||||
console.log(`Registering ${volumeBackups.length} backup schedules...`);
|
||||
for (const volumeBackup of volumeBackups) {
|
||||
await backupService.registerBackupJob(volumeBackup);
|
||||
}
|
||||
console.log('Backup schedules registered.');
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import { CommandExecutorUtils } from './server/utils/command-executor.utils'
|
||||
import dataAccess from './server/adapter/db.client'
|
||||
import { FancyConsoleUtils } from './shared/utils/fancy-console.utils'
|
||||
import { Constants } from './shared/utils/constants'
|
||||
import registreAllBackupSchedules from './backup.server'
|
||||
import backupService from './server/services/standalone-services/backup.service'
|
||||
|
||||
// Source: https://nextjs.org/docs/app/building-your-application/configuring/custom-server
|
||||
|
||||
@@ -51,10 +51,10 @@ async function initializeNextJs() {
|
||||
}
|
||||
}
|
||||
|
||||
await registreAllBackupSchedules();
|
||||
await backupService.registerAllBackups();
|
||||
|
||||
const app = next({ dev })
|
||||
const handle = app.getRequestHandler()
|
||||
const app = next({ dev });
|
||||
const handle = app.getRequestHandler();
|
||||
|
||||
app.prepare().then(() => {
|
||||
|
||||
|
||||
@@ -3,31 +3,45 @@ import { ServiceException } from "../../../shared/model/service.exception.model"
|
||||
import { PathUtils } from "../../utils/path.utils";
|
||||
import { FsUtils } from "../../utils/fs.utils";
|
||||
import s3Service from "../aws-s3.service";
|
||||
import { VolumeBackup } from "@prisma/client";
|
||||
import scheduleService from "./schedule.service";
|
||||
import standalonePodService from "./standalone-pod.service";
|
||||
import { ListUtils } from "../../../shared/utils/list.utils";
|
||||
|
||||
class BackupService {
|
||||
|
||||
async registerBackupJob(volumeBackup: VolumeBackup) {
|
||||
const cron = volumeBackup.cron;
|
||||
const jobName = `backup-volume-${volumeBackup.id}`;
|
||||
scheduleService.scheduleJob(jobName, cron, async () => {
|
||||
try {
|
||||
await this.runBackupForVolume(volumeBackup.id);
|
||||
} catch (e) {
|
||||
console.error(`Error during backup for volume ${volumeBackup.id}`);
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
async registerAllBackups() {
|
||||
const allVolumeBackups = await dataAccess.client.volumeBackup.findMany();
|
||||
console.log(`Deregistering existing backup schedules...`);
|
||||
this.unregisterAllBackups();
|
||||
|
||||
console.log(`Registering ${allVolumeBackups.length} backup schedules...`);
|
||||
const groupedByCron = ListUtils.groupBy(allVolumeBackups, vb => vb.cron);
|
||||
|
||||
for (const [cron, volumeBackups] of Array.from(groupedByCron.entries())) {
|
||||
scheduleService.scheduleJob(cron, cron, async () => {
|
||||
console.log(`Running backup for ${volumeBackups.length} volumes...`);
|
||||
for (const volumeBackup of volumeBackups) {
|
||||
try {
|
||||
await this.runBackupForVolume(volumeBackup.id);
|
||||
} catch (e) {
|
||||
console.error(`Error during backup for volume ${volumeBackup.volumeId} and backup ${volumeBackup.id}`);
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
console.log(`Backup for ${volumeBackups.length} volumes finished.`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async unregisterBackupJob(volumeBackupId: string) {
|
||||
const jobName = `backup-volume-${volumeBackupId}`;
|
||||
scheduleService.cancelJob(jobName);
|
||||
async unregisterAllBackups() {
|
||||
const allJobs = scheduleService.getAlJobs();
|
||||
for (const jobName of allJobs) {
|
||||
scheduleService.cancelJob(jobName);
|
||||
}
|
||||
}
|
||||
|
||||
async runBackupForVolume(backupVolumeId: string) {
|
||||
console.log(`Running backup for backupVolume ${backupVolumeId}`);
|
||||
|
||||
const backupVolume = await dataAccess.client.volumeBackup.findFirstOrThrow({
|
||||
where: {
|
||||
|
||||
@@ -21,7 +21,7 @@ class ScheduleService {
|
||||
scheduleJob(jobName: string, cronExpression: string, callback: schedule.JobCallback) {
|
||||
const job = new this.schedule.Job(jobName, callback);
|
||||
job.schedule(cronExpression);
|
||||
console.log(`[${ScheduleService.name}] Job ${jobName} scheduled with cron ${cronExpression}`);
|
||||
console.log(`[${ScheduleService.name}] Job scheduled with cron ${cronExpression}`);
|
||||
}
|
||||
|
||||
cancelJob(jobName: string) {
|
||||
@@ -32,6 +32,10 @@ class ScheduleService {
|
||||
}
|
||||
}
|
||||
|
||||
getAlJobs() {
|
||||
return Object.keys(this.schedule.scheduledJobs);
|
||||
}
|
||||
|
||||
printScheduledJobs() {
|
||||
console.log(`[${ScheduleService.name}] Scheduled jobs: \n- ${Object.keys(this.schedule.scheduledJobs).join('\n- ')}`);
|
||||
}
|
||||
|
||||
@@ -114,32 +114,31 @@ class SetupPodService {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
|
||||
const exec = new k8s.Exec(k3s.getKubeConfig());
|
||||
exec
|
||||
.exec(
|
||||
namespace,
|
||||
podName,
|
||||
containerName,
|
||||
command,
|
||||
writerStream,
|
||||
stderrStream,
|
||||
null,
|
||||
false,
|
||||
async ({ status }) => {
|
||||
try {
|
||||
writerStream.close();
|
||||
if (status === 'Failure') {
|
||||
return reject(
|
||||
new Error(
|
||||
`Error from cpFromPod - details: \n ${stderrStream.read().toString()}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
exec.exec(
|
||||
namespace,
|
||||
podName,
|
||||
containerName,
|
||||
command,
|
||||
writerStream,
|
||||
stderrStream,
|
||||
null,
|
||||
false,
|
||||
async ({ status }) => {
|
||||
try {
|
||||
writerStream.close();
|
||||
if (status === 'Failure') {
|
||||
return reject(
|
||||
new Error(
|
||||
`Error from cpFromPod - details: \n ${stderrStream.read().toString()}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
},
|
||||
)
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user