added to remote repo
This commit is contained in:
1 parent
8697c9f405
commit
cc599b6e18
8049 files changed
+1096323
No files matched your search
@@ -0,0 +1,78 @@
|
||||
import { Router } from 'express';
|
||||
import { config } from '../config.js';
|
||||
import { spawnCommand, runRestic } from '../executor.js';
|
||||
import { appendJobLog, createJob, finishJob, setJobRunning, setJobStep } from '../jobs.js';
|
||||
import { validateVmExists } from '../validators.js';
|
||||
|
||||
export const backupRouter = Router();
|
||||
|
||||
backupRouter.post('/:vmName', async (req, res, next) => {
|
||||
try {
|
||||
const vmName = req.params.vmName;
|
||||
await validateVmExists(vmName);
|
||||
const job = createJob('backup', vmName);
|
||||
runBackupJob(job).catch(() => {});
|
||||
res.status(202).json({ jobId: job.id, message: 'Backup job started.' });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
async function runBackupJob(job) {
|
||||
const timestamp = new Date().toISOString().replace(/[-:.TZ]/g, '').slice(0, 14);
|
||||
const snapshotName = `s3-backup-${timestamp}`;
|
||||
const zvol = `${config.zfsPoolName}/virtual-machines/${job.vmName}.block`;
|
||||
const snapshotDevice = `/dev/zvol/${zvol}@snapshot-${snapshotName}`;
|
||||
|
||||
try {
|
||||
setJobRunning(job, 'Creating Incus snapshot');
|
||||
await spawnCommand('incus', ['snapshot', 'create', job.vmName, snapshotName], { log: (line) => appendJobLog(job, line) });
|
||||
|
||||
setJobStep(job, 'Making ZFS snapshot device visible');
|
||||
await spawnCommand('zfs', ['set', 'snapdev=visible', zvol], { log: (line) => appendJobLog(job, line) });
|
||||
|
||||
setJobStep(job, 'Waiting for snapshot device');
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
|
||||
setJobStep(job, 'Streaming block device to Restic');
|
||||
await spawnCommand('restic', ['backup', '--stdin', '--stdin-filename', `${job.vmName}.raw`, '--tag', job.vmName], {
|
||||
env: { ...process.env, ...config.resticEnv },
|
||||
input: await deviceReadStream(snapshotDevice),
|
||||
log: (line) => appendJobLog(job, line),
|
||||
});
|
||||
|
||||
setJobStep(job, 'Hiding ZFS snapshot device');
|
||||
await spawnCommand('zfs', ['set', 'snapdev=hidden', zvol], { log: (line) => appendJobLog(job, line) });
|
||||
|
||||
setJobStep(job, 'Deleting temporary Incus snapshot');
|
||||
await spawnCommand('incus', ['snapshot', 'delete', job.vmName, snapshotName], { log: (line) => appendJobLog(job, line) });
|
||||
|
||||
setJobStep(job, 'Applying Restic retention policy');
|
||||
await runRestic(['forget', '--tag', job.vmName, '--keep-daily', '7', '--prune'], {
|
||||
log: (line) => appendJobLog(job, line),
|
||||
});
|
||||
|
||||
finishJob(job, 'success');
|
||||
} catch (error) {
|
||||
appendJobLog(job, error.message);
|
||||
await cleanupBackup(job, zvol, snapshotName);
|
||||
finishJob(job, 'failed', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function deviceReadStream(path) {
|
||||
const { createReadStream } = await import('node:fs');
|
||||
return createReadStream(path);
|
||||
}
|
||||
|
||||
async function cleanupBackup(job, zvol, snapshotName) {
|
||||
setJobStep(job, 'Running cleanup');
|
||||
await spawnCommand('zfs', ['set', 'snapdev=hidden', zvol], {
|
||||
ignoreExitCode: true,
|
||||
log: (line) => appendJobLog(job, line),
|
||||
}).catch((error) => appendJobLog(job, error.message));
|
||||
await spawnCommand('incus', ['snapshot', 'delete', job.vmName, snapshotName], {
|
||||
ignoreExitCode: true,
|
||||
log: (line) => appendJobLog(job, line),
|
||||
}).catch((error) => appendJobLog(job, error.message));
|
||||
}
|
||||
Reference in new issue
Block a user