Add LAN device discovery (ping sweep + ARP, known/unknown labeling)
CI / web (push) Successful in 17s
CI / api (push) Successful in 23s

Runs as a host-level systemd timer on CT122 (scripts/discover-devices.sh)
rather than inside the api container, since real ARP entries live in the
host's network namespace, not Docker's bridge network. See
docs/device-discovery.md for the full writeup, including why literal
passive-only ARP reading was dropped (near-empty result in practice).

API reads the resulting JSON file each poll cycle, cross-references
config/hosts.yaml's knownDevices list by IP, and serves /api/devices.
Dashboard gets a new "Network Devices" table.

Closes #9.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-12 20:49:22 -06:00
parent d75bd58792
commit 04282232cc
15 changed files with 434 additions and 8 deletions
+9
View File
@@ -10,6 +10,8 @@ import { SshHostCollector } from "./collectors/sshHost.js";
import type { Collector } from "./collectors/types.js";
import { registerAuthRoutes } from "./routes/auth.js";
import { registerHostRoutes } from "./routes/hosts.js";
import { registerDeviceRoutes } from "./routes/devices.js";
import { refreshDevices } from "./discovery/index.js";
const HOSTS_CONFIG_PATH = process.env.HOSTS_CONFIG_PATH ?? "../../config/hosts.yaml";
@@ -41,6 +43,12 @@ async function main() {
}
}
pruneOldSnapshots(db, cfg.snapshotRetentionHours);
try {
await refreshDevices(db, cfg.discoveryFilePath, cfg.knownDevices);
} catch (err) {
app.log.error({ err }, "device discovery refresh failed");
}
}
const app = Fastify({ logger: true });
@@ -53,6 +61,7 @@ async function main() {
registerAuthRoutes(app, db);
registerHostRoutes(app, db);
registerDeviceRoutes(app, db);
await pollOnce();
setInterval(() => {