added install script for agent

changed backend to agent
This commit is contained in:
Philipp committed 2026-06-04 14:05:50 +02:00
1 parent 7f2785fb05
commit 0b059aec1d
669 files changed
+767 -70582

No files matched your search

+129
View File
@@ -0,0 +1,129 @@
import crypto from 'node:crypto';
import { readFile, rename, writeFile } from 'node:fs/promises';
import path from 'node:path';
import { startBackupForVm } from './routes/backup.js';
const schedulesPath = path.resolve(process.cwd(), 'schedules.json');
const checkIntervalMs = 30 * 1000;
let schedules = [];
let timer = null;
export async function startScheduler() {
await loadSchedules();
if (timer) return;
timer = setInterval(runDueSchedules, checkIntervalMs);
timer.unref?.();
runDueSchedules();
}
export function listSchedules() {
return schedules
.map((schedule) => ({ ...schedule }))
.sort((a, b) => a.vmName.localeCompare(b.vmName));
}
export async function replaceSchedules(nextSchedules) {
schedules = normalizeSchedules(nextSchedules);
await saveSchedules();
return listSchedules();
}
async function runDueSchedules() {
const now = new Date();
let changed = false;
for (const schedule of schedules) {
if (!schedule.enabled) continue;
if (!schedule.nextRunAt) {
schedule.nextRunAt = nextRunFrom(now, schedule.intervalHours, schedule.timeOfDay);
changed = true;
continue;
}
if (new Date(schedule.nextRunAt) > now) continue;
try {
await startBackupForVm(schedule.vmName);
schedule.lastRunAt = now.toISOString();
schedule.lastError = '';
schedule.nextRunAt = nextRunFrom(now, schedule.intervalHours, schedule.timeOfDay);
} catch (error) {
schedule.lastError = String(error.message || error);
schedule.nextRunAt = new Date(now.getTime() + 5 * 60 * 1000).toISOString();
}
changed = true;
}
if (changed) {
await saveSchedules();
}
}
async function loadSchedules() {
try {
const content = await readFile(schedulesPath, 'utf8');
schedules = normalizeSchedules(JSON.parse(content));
} catch (error) {
if (error.code === 'ENOENT') {
schedules = [];
return;
}
throw error;
}
}
async function saveSchedules() {
const tempPath = `${schedulesPath}.tmp`;
await writeFile(tempPath, `${JSON.stringify(schedules, null, 2)}\n`, { mode: 0o600 });
await rename(tempPath, schedulesPath);
}
function normalizeSchedules(values) {
const seen = new Set();
return (Array.isArray(values) ? values : [])
.map((value) => {
const vmName = String(value.vmName || '').trim();
const intervalHours = Math.max(1, Number(value.intervalHours) || 24);
if (!vmName || seen.has(vmName)) return null;
seen.add(vmName);
return {
id: value.id || `schedule_${crypto.randomBytes(8).toString('hex')}`,
vmName,
enabled: Boolean(value.enabled),
intervalHours,
timeOfDay: normalizeTimeOfDay(value.timeOfDay),
nextRunAt: value.nextRunAt || nextRunFrom(new Date(), intervalHours, normalizeTimeOfDay(value.timeOfDay)),
lastRunAt: value.lastRunAt || null,
lastError: value.lastError || '',
};
})
.filter(Boolean);
}
function nextRunFrom(date, intervalHours, timeOfDay) {
const intervalMs = intervalHours * 60 * 60 * 1000;
const candidate = withTimeOfDay(date, timeOfDay);
while (candidate <= date) {
candidate.setTime(candidate.getTime() + intervalMs);
}
return candidate.toISOString();
}
function withTimeOfDay(date, timeOfDay) {
const [hours, minutes] = normalizeTimeOfDay(timeOfDay).split(':').map(Number);
const next = new Date(date);
next.setHours(hours, minutes, 0, 0);
return next;
}
function normalizeTimeOfDay(value) {
const text = String(value || '02:00').trim();
if (/^\d{2}:\d{2}$/.test(text)) {
const [hours, minutes] = text.split(':').map(Number);
if (hours >= 0 && hours <= 23 && minutes >= 0 && minutes <= 59) {
return text;
}
}
return '02:00';
}