Hot-reload sshHosts/knownDevices from hosts.yaml, no restart needed
Checks the file's mtime on each poll cycle (already running every 30s) rather than adding a separate file-watcher or admin UI. Proxmox hosts already needed no config (auto-discovered every poll); this covers the two lists that did. A parse failure logs and keeps the previous config running instead of crashing the poller. Closes #14. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+47
-11
@@ -1,8 +1,9 @@
|
||||
import "dotenv/config";
|
||||
import { statSync } from "node:fs";
|
||||
import Fastify from "fastify";
|
||||
import fastifyCookie from "@fastify/cookie";
|
||||
import fastifySession from "@fastify/session";
|
||||
import { loadConfig } from "./config/index.js";
|
||||
import { loadConfig, type AppConfig } from "./config/index.js";
|
||||
import { openDb, upsertSnapshots, pruneOldSnapshots } from "./db/index.js";
|
||||
import { seedAdminUser } from "./auth/local.js";
|
||||
import { ProxmoxCollector } from "./collectors/proxmox.js";
|
||||
@@ -17,15 +18,8 @@ import { initOidc } from "./auth/oidc.js";
|
||||
|
||||
const HOSTS_CONFIG_PATH = process.env.HOSTS_CONFIG_PATH ?? "../../config/hosts.yaml";
|
||||
|
||||
async function main() {
|
||||
const cfg = loadConfig(HOSTS_CONFIG_PATH);
|
||||
const db = openDb(cfg.dbPath);
|
||||
|
||||
if (cfg.adminPassword) {
|
||||
seedAdminUser(db, cfg.adminUsername, cfg.adminPassword);
|
||||
}
|
||||
|
||||
const collectors: Collector[] = [
|
||||
function buildCollectors(cfg: AppConfig): Collector[] {
|
||||
return [
|
||||
new ProxmoxCollector({
|
||||
host: cfg.proxmox.host,
|
||||
node: cfg.proxmox.node,
|
||||
@@ -34,8 +28,50 @@ async function main() {
|
||||
}),
|
||||
...cfg.sshHosts.map((h) => new SshHostCollector(h)),
|
||||
];
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const cfg = loadConfig(HOSTS_CONFIG_PATH);
|
||||
const db = openDb(cfg.dbPath);
|
||||
|
||||
if (cfg.adminPassword) {
|
||||
seedAdminUser(db, cfg.adminUsername, cfg.adminPassword);
|
||||
}
|
||||
|
||||
// Proxmox-monitored hosts (the node + every LXC) auto-discover on every poll
|
||||
// already — no config needed there. sshHosts and knownDevices, though, are
|
||||
// declared in hosts.yaml and used to need a container restart to pick up
|
||||
// changes. Re-checking the file's mtime on each poll cycle (already running
|
||||
// every 30s) avoids that without adding a separate file-watcher/admin UI.
|
||||
let knownDevices = cfg.knownDevices;
|
||||
let collectors = buildCollectors(cfg);
|
||||
let hostsConfigMtimeMs = statSync(HOSTS_CONFIG_PATH).mtimeMs;
|
||||
|
||||
function reloadHostsConfigIfChanged() {
|
||||
let mtimeMs: number;
|
||||
try {
|
||||
mtimeMs = statSync(HOSTS_CONFIG_PATH).mtimeMs;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (mtimeMs === hostsConfigMtimeMs) return;
|
||||
hostsConfigMtimeMs = mtimeMs;
|
||||
try {
|
||||
const reloaded = loadConfig(HOSTS_CONFIG_PATH);
|
||||
collectors = buildCollectors(reloaded);
|
||||
knownDevices = reloaded.knownDevices;
|
||||
app.log.info(
|
||||
{ sshHosts: reloaded.sshHosts.length, knownDevices: reloaded.knownDevices.size },
|
||||
"config/hosts.yaml changed, reloaded"
|
||||
);
|
||||
} catch (err) {
|
||||
app.log.error({ err }, "failed to reload config/hosts.yaml, keeping previous config");
|
||||
}
|
||||
}
|
||||
|
||||
async function pollOnce() {
|
||||
reloadHostsConfigIfChanged();
|
||||
|
||||
for (const collector of collectors) {
|
||||
try {
|
||||
const snapshots = await collector.collect();
|
||||
@@ -47,7 +83,7 @@ async function main() {
|
||||
pruneOldSnapshots(db, cfg.snapshotRetentionHours);
|
||||
|
||||
try {
|
||||
await refreshDevices(db, cfg.discoveryFilePath, cfg.knownDevices);
|
||||
await refreshDevices(db, cfg.discoveryFilePath, knownDevices);
|
||||
} catch (err) {
|
||||
app.log.error({ err }, "device discovery refresh failed");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user