added meta date for backups

This commit is contained in:
Philipp
2026-05-21 10:55:59 +02:00
parent b4199ea636
commit 9714dfa864
2 changed files with 55 additions and 2 deletions
+2
View File
@@ -2,6 +2,8 @@
Dark-mode control plane for Incus VM backups using ZFS block devices, Restic, and S3-compatible storage.
Backups include the instance disk or dataset plus Incus metadata captured with `incus config show`, `incus config show --expanded`, `incus info`, and snapshot listing. VM disks are stored as `.raw`; containers are stored as ZFS send streams (`.zfs`).
## Layout
- `management/`: Central Express API with cookie login, SQLite storage, node registry, central schedules, and proxy calls to node agents.
+53 -2
View File
@@ -1,5 +1,8 @@
import { Router } from 'express';
import { spawn } from 'node:child_process';
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { finished } from 'node:stream/promises';
import { setTimeout as delay } from 'node:timers/promises';
import { config } from '../config.js';
@@ -86,8 +89,12 @@ export async function runBackupJob(job, instance = null) {
setJobProgress(job, { percent: 93 });
await deleteIncusSnapshotWithRetry(job, snapshotName);
setJobStep(job, 'Backing up Incus metadata');
setJobProgress(job, { percent: 95 });
await backupInstanceMetadata(job, 'virtual-machine');
setJobStep(job, 'Applying Restic retention policy');
setJobProgress(job, { percent: 96 });
setJobProgress(job, { percent: 97 });
await runRestic(retentionArgs(job.vmName), {
log: (line) => appendJobLog(job, line),
});
@@ -142,8 +149,12 @@ async function runContainerBackupJob(job) {
setJobProgress(job, { percent: 93 });
await deleteIncusSnapshotWithRetry(job, snapshotName);
setJobStep(job, 'Backing up Incus metadata');
setJobProgress(job, { percent: 95 });
await backupInstanceMetadata(job, 'container');
setJobStep(job, 'Applying Restic retention policy');
setJobProgress(job, { percent: 96 });
setJobProgress(job, { percent: 97 });
await runRestic(retentionArgs(job.vmName), {
log: (line) => appendJobLog(job, line),
});
@@ -173,6 +184,46 @@ async function zfsDatasetUsed(dataset) {
return Number.isFinite(size) && size > 0 ? size : null;
}
async function backupInstanceMetadata(job, instanceType) {
const tempDir = await mkdtemp(path.join(tmpdir(), `incus-backup-${job.vmName}-`));
try {
const metadataDir = path.join(tempDir, job.vmName);
await writeMetadataFile(metadataDir, 'README.txt', metadataReadme(job.vmName, instanceType));
await writeCommandOutput(metadataDir, 'config.yaml', ['config', 'show', job.vmName]);
await writeCommandOutput(metadataDir, 'config-expanded.yaml', ['config', 'show', job.vmName, '--expanded']);
await writeCommandOutput(metadataDir, 'info.txt', ['info', job.vmName]);
await writeCommandOutput(metadataDir, 'snapshots.json', ['snapshot', 'list', job.vmName, '--format', 'json']);
await runRestic(['backup', metadataDir, '--tag', job.vmName, '--tag', 'metadata', '--tag', instanceType], {
log: (line) => appendJobLog(job, line),
});
} finally {
await rm(tempDir, { recursive: true, force: true });
}
}
async function writeCommandOutput(directory, filename, args) {
const result = await spawnCommand('incus', args);
await writeMetadataFile(directory, filename, result.stdout);
}
async function writeMetadataFile(directory, filename, content) {
const { mkdir } = await import('node:fs/promises');
await mkdir(directory, { recursive: true });
await writeFile(path.join(directory, filename), content || '', { mode: 0o600 });
}
function metadataReadme(instanceName, instanceType) {
return [
`Instance: ${instanceName}`,
`Type: ${instanceType}`,
`Created: ${new Date().toISOString()}`,
'',
'This directory contains Incus metadata captured alongside the disk/dataset backup.',
'It is intended for disaster-recovery reconstruction and restore preflight workflows.',
'',
].join('\n');
}
function waitForProcess(child, label) {
return new Promise((resolve, reject) => {
child.on('error', reject);