#!/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 ` output: " lladdr [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"