Add LAN device discovery (ping sweep + ARP, known/unknown labeling)
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:
@@ -0,0 +1,49 @@
|
||||
# LAN device discovery
|
||||
|
||||
Shows every device currently on the LAN, labeled known/unknown, in the dashboard's
|
||||
"Network Devices" section.
|
||||
|
||||
## Why this isn't pure passive ARP reading
|
||||
|
||||
The original idea was to just read the ARP table (no active network traffic). In
|
||||
practice that produces a nearly empty list: a host's ARP cache only contains entries
|
||||
for peers it has actually exchanged traffic with, and the dashboard container has no
|
||||
reason to talk to most LAN devices on its own. So discovery does a lightweight ICMP
|
||||
ping sweep (no port scanning) first, to populate the cache, then reads it.
|
||||
|
||||
## Why this runs outside the Docker container
|
||||
|
||||
Real ARP entries live in the network namespace of whichever host is directly on the
|
||||
LAN interface. The `api` container sits behind Docker's own bridge network — pinging
|
||||
from inside it and reading `/proc/net/arp` there would show Docker's internal network,
|
||||
not the actual LAN. Rather than switch the containers to `network_mode: host` (loses
|
||||
container network isolation), discovery runs as a small script directly on the CT122
|
||||
host via a systemd timer — the same pattern the homelab already uses for host-level
|
||||
periodic jobs like the rclone backups on CT105.
|
||||
|
||||
## Pieces
|
||||
|
||||
- `scripts/discover-devices.sh` — ping sweep + `ip neigh show` parse, writes
|
||||
`/opt/homelab-monitor/data/devices-raw.json` (`[{ip, mac, state}]`).
|
||||
- `deploy/systemd/homelab-monitor-discover.{service,timer}` — runs the script every
|
||||
5 minutes on the CT122 host (not in Docker).
|
||||
- `apps/api/src/discovery/index.ts` — reads that JSON file each poll cycle, labels
|
||||
entries against `knownDevices` in `config/hosts.yaml` (matched by IP), upserts into
|
||||
the `devices` SQLite table.
|
||||
- `GET /api/devices` — serves devices seen in the last 24h (older entries are dropped
|
||||
rather than shown as stale-known, since there's no per-device up/down tracking).
|
||||
|
||||
## Deploying/updating
|
||||
|
||||
```bash
|
||||
scp scripts/discover-devices.sh homelab-monitor:/opt/homelab-monitor/scripts/
|
||||
scp deploy/systemd/homelab-monitor-discover.* homelab-monitor:/etc/systemd/system/
|
||||
ssh homelab-monitor "chmod +x /opt/homelab-monitor/scripts/discover-devices.sh && \
|
||||
systemctl daemon-reload && systemctl enable --now homelab-monitor-discover.timer"
|
||||
```
|
||||
|
||||
## Known limitation
|
||||
|
||||
`knownDevices` in `config/hosts.yaml` is matched by **IP**, not MAC — fine as long as
|
||||
DHCP reservations don't change, but a device losing its reservation would show up as
|
||||
"unknown" until the config is updated by hand.
|
||||
Reference in New Issue
Block a user