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
+25
View File
@@ -0,0 +1,25 @@
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
import type Database from "better-sqlite3";
import { getRecentDevices } from "../db/index.js";
async function requireAuth(req: FastifyRequest, reply: FastifyReply) {
if (!req.session.username) {
reply.code(401).send({ error: "not authenticated" });
}
}
export function registerDeviceRoutes(app: FastifyInstance, db: Database.Database): void {
app.get("/api/devices", { preHandler: requireAuth }, async () => {
const rows = getRecentDevices(db);
return {
devices: rows.map((r) => ({
ip: r.ip,
mac: r.mac,
name: r.known_name,
known: r.known_name !== null,
firstSeen: r.first_seen,
lastSeen: r.last_seen,
})),
};
});
}