Files
Void-Homelab/lib/cron/index.js
root 359ae21d59 feat(speedtest): full speedtest-tracker-style automation (2.9.0)
Switch worker to the Ookla CLI (jitter, packet loss, server, ISP,
shareable result URL, bytes). Migration 028 enriches speedtest_results
+ adds a generic app_settings store. New /speedtest page: KPIs,
throughput + latency charts, window stats, configurable schedule
(reschedulable cron) & low-speed alert threshold, history table.
SV card gains ping/jitter + a link through to the page.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 22:55:04 +10:00

66 lines
2.7 KiB
JavaScript

import cron from 'node-cron';
import { runSync } from './sync_source_docs.js';
import { log } from '../log.js';
import { enqueue } from '../jobs/queue.js';
import { checkAll } from '../health/checker.js';
import * as statusRepo from '../db/repos/service_status.js';
import * as services from '../db/repos/monitored_services.js';
import { runDeviceScanCycle } from '../infra/scan_cycle.js';
import * as settings from '../db/repos/app_settings.js';
// Speedtest runs on a user-configurable interval (PUT /api/speedtest/config →
// setSpeedtestSchedule). Held module-level so it can be stopped + rescheduled.
let speedtestTask = null;
function speedtestExpr(min) {
if (min < 60) return `*/${min} * * * *`;
if (min % 60 === 0) { const h = min / 60; return h >= 24 ? '0 2 * * *' : `0 */${h} * * *`; }
return '0 * * * *';
}
export function setSpeedtestSchedule(min) {
const m = Math.max(5, Math.min(1440, Number(min) || 60));
if (speedtestTask) { speedtestTask.stop(); speedtestTask = null; }
const expr = speedtestExpr(m);
speedtestTask = cron.schedule(expr, async () => {
try { await enqueue('speedtest', {}); log.info({ expr }, 'cron speedtest enqueued'); }
catch (e) { log.error({ err: e }, 'cron speedtest failed'); }
});
log.info({ expr, min: m }, 'speedtest schedule set');
}
export function startCron() {
// Daily at 03:00 local time
cron.schedule('0 3 * * *', async () => {
try {
const n = await runSync();
log.info({ enqueued: n }, 'cron sync.source_doc complete');
} catch (e) {
log.error({ err: e }, 'cron sync.source_doc failed');
}
});
// Speedtest — interval from the saved config (default 60 min), reschedulable.
settings.get('speedtest', {})
.then(cfg => setSpeedtestSchedule(cfg?.interval_min || 60))
.catch(e => { log.error({ err: e }, 'speedtest schedule init failed'); setSpeedtestSchedule(60); });
// Health checks every minute. NOTE: this runs checkAll() inline; the same
// probe+upsert logic is also exposed on-demand via the `health.check` pg-boss
// worker (lib/jobs/workers/health_check.js, triggered by POST /api/health/check).
// Keep the two in sync — both rely on lib/health/checker.js as the source of truth.
cron.schedule('*/1 * * * *', async () => {
try {
const results = await checkAll(await services.listEnabled());
for (const r of results) await statusRepo.upsert(r);
log.info({ n: results.length }, 'health check complete');
} catch (e) { log.error({ err: e }, 'health check failed'); }
});
// Hourly LAN device scan (staggered off the :00 speedtest)
cron.schedule('7 * * * *', async () => {
try { await runDeviceScanCycle(); }
catch (e) { log.error({ err: e }, 'device scan cycle failed'); }
});
log.info('cron started');
}