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,45 @@
|
||||
#!/bin/bash
|
||||
# Runs on the CT122 host (not in a container) via the homelab-monitor-discover
|
||||
# systemd timer. Real ARP entries live in the host's network namespace, not an
|
||||
# isolated Docker bridge network, so this has to run here rather than inside
|
||||
# the api container — see docs/device-discovery.md.
|
||||
#
|
||||
# Pings every address in the subnet (ICMP only, no port scanning) to populate
|
||||
# the ARP cache, then dumps ip+mac+state pairs as JSON. The API service reads
|
||||
# this file and does known/unknown labeling using config/hosts.yaml.
|
||||
set -euo pipefail
|
||||
|
||||
SUBNET_PREFIX="${SUBNET_PREFIX:-192.168.1}"
|
||||
OUTPUT_FILE="${OUTPUT_FILE:-/opt/homelab-monitor/data/devices-raw.json}"
|
||||
IFACE="${IFACE:-eth0}"
|
||||
|
||||
mkdir -p "$(dirname "$OUTPUT_FILE")"
|
||||
|
||||
for i in $(seq 1 254); do
|
||||
ping -c 1 -W 1 "${SUBNET_PREFIX}.${i}" >/dev/null 2>&1 &
|
||||
# cap concurrency so we don't fork 254 pings at once
|
||||
if (( i % 32 == 0 )); then wait; fi
|
||||
done
|
||||
wait
|
||||
|
||||
TMP_FILE="$(mktemp)"
|
||||
{
|
||||
echo "["
|
||||
first=1
|
||||
# `ip neigh show dev <iface>` output: "<ip> lladdr <mac> <state> [router]"
|
||||
while read -r ip _ mac state _; do
|
||||
[[ "$ip" == *:* ]] && continue # skip IPv6 (link-local neighbor entries)
|
||||
[[ -z "$mac" ]] && continue
|
||||
case "$state" in
|
||||
REACHABLE|STALE|DELAY|PERMANENT) ;;
|
||||
*) continue ;;
|
||||
esac
|
||||
[[ $first -eq 0 ]] && echo ","
|
||||
first=0
|
||||
printf '{"ip":"%s","mac":"%s","state":"%s"}' "$ip" "$mac" "$state"
|
||||
done < <(ip neigh show dev "$IFACE")
|
||||
echo
|
||||
echo "]"
|
||||
} > "$TMP_FILE"
|
||||
|
||||
mv "$TMP_FILE" "$OUTPUT_FILE"
|
||||
Reference in New Issue
Block a user