refactor(backend): better job scheduling pattern

This commit is contained in:
Nicolas Meienberger
2025-10-25 17:46:01 +02:00
parent 47ff720adb
commit 2202ad3247
6 changed files with 399 additions and 69 deletions

View File

@@ -1,42 +0,0 @@
import fs from "node:fs/promises";
import path from "node:path";
import { VOLUME_MOUNT_BASE } from "../../core/constants";
import { toMessage } from "../../utils/errors";
import { logger } from "../../utils/logger";
import { readMountInfo } from "../../utils/mountinfo";
import { executeUnmount } from "../backends/utils/backend-utils";
import { getVolumePath } from "../volumes/helpers";
import { volumeService } from "../volumes/volume.service";
export const cleanupDanglingMounts = async () => {
const allVolumes = await volumeService.listVolumes();
const allSystemMounts = await readMountInfo();
for (const mount of allSystemMounts) {
if (mount.mountPoint.includes("ironmount") && mount.mountPoint.endsWith("_data")) {
const matchingVolume = allVolumes.find((v) => getVolumePath(v.name) === mount.mountPoint);
if (!matchingVolume) {
logger.info(`Found dangling mount at ${mount.mountPoint}, attempting to unmount...`);
await executeUnmount(mount.mountPoint);
await fs.rmdir(path.dirname(mount.mountPoint)).catch((err) => {
logger.warn(`Failed to remove dangling mount directory ${path.dirname(mount.mountPoint)}: ${toMessage(err)}`);
});
}
}
}
const allIronmountDirs = await fs.readdir(VOLUME_MOUNT_BASE).catch(() => []);
for (const dir of allIronmountDirs) {
const volumePath = getVolumePath(dir);
const matchingVolume = allVolumes.find((v) => getVolumePath(v.name) === volumePath);
if (!matchingVolume) {
const fullPath = path.join(VOLUME_MOUNT_BASE, dir);
logger.info(`Found dangling mount directory at ${fullPath}, attempting to remove...`);
await fs.rmdir(fullPath, { recursive: true }).catch((err) => {
logger.warn(`Failed to remove dangling mount directory ${fullPath}: ${toMessage(err)}`);
});
}
}
};

View File

@@ -1,19 +1,19 @@
import { Scheduler } from "../../core/scheduler";
import { and, eq, or } from "drizzle-orm";
import { getTasks, schedule } from "node-cron";
import { db } from "../../db/db";
import { volumesTable } from "../../db/schema";
import { logger } from "../../utils/logger";
import { restic } from "../../utils/restic";
import { volumeService } from "../volumes/volume.service";
import { cleanupDanglingMounts } from "./cleanup";
import { CleanupDanglingMountsJob } from "../../jobs/cleanup-dangling";
import { VolumeHealthCheckJob } from "../../jobs/healthchecks";
export const startup = async () => {
await Scheduler.start();
await restic.ensurePassfile().catch((err) => {
logger.error(`Error ensuring restic passfile exists: ${err.message}`);
});
cleanupDanglingMounts().catch((err) => {
logger.error(`Error during startup cleanup of dangling mounts: ${err.message}`);
});
const volumes = await db.query.volumesTable.findMany({
where: or(
@@ -28,26 +28,6 @@ export const startup = async () => {
});
}
const existingTasks = getTasks();
existingTasks.forEach(async (task) => await task.destroy());
schedule("0 * * * *", async () => {
logger.debug("Running hourly cleanup of dangling mounts...");
await cleanupDanglingMounts();
});
schedule("* * * * *", async () => {
logger.debug("Running health check for all volumes...");
const volumes = await db.query.volumesTable.findMany({
where: or(eq(volumesTable.status, "mounted"), eq(volumesTable.status, "error")),
});
for (const volume of volumes) {
const { status } = await volumeService.checkHealth(volume.name);
if (status === "error" && volume.autoRemount) {
await volumeService.mountVolume(volume.name);
}
}
});
Scheduler.build(CleanupDanglingMountsJob).schedule("0 * * * *");
Scheduler.build(VolumeHealthCheckJob).schedule("* * * * *");
};