#!/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 mDNS-resolves each responder (avahi-resolve, bounded # per-IP timeout so one non-mDNS device can't stall the whole run), and dumps # ip+mac+state+mdnsHostname 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 MDNS_DIR="$(mktemp -d)" trap 'rm -rf "$MDNS_DIR"' EXIT # `ip neigh show dev ` output: " lladdr [router]" mapfile -t neighbors < <(ip neigh show dev "$IFACE") i=0 for line in "${neighbors[@]}"; do read -r ip _ mac state _ <<< "$line" [[ "$ip" == *:* ]] && continue [[ -z "$mac" ]] && continue case "$state" in REACHABLE|STALE|DELAY|PERMANENT) ;; *) continue ;; esac ( hostname=$(timeout 2 avahi-resolve -a "$ip" 2>/dev/null | awk '{print $2}') echo "${hostname:-}" > "$MDNS_DIR/$ip" ) & i=$((i + 1)) if (( i % 32 == 0 )); then wait; fi done wait TMP_FILE="$(mktemp)" { echo "[" first=1 for line in "${neighbors[@]}"; do read -r ip _ mac state _ <<< "$line" [[ "$ip" == *:* ]] && continue [[ -z "$mac" ]] && continue case "$state" in REACHABLE|STALE|DELAY|PERMANENT) ;; *) continue ;; esac mdns="" [[ -f "$MDNS_DIR/$ip" ]] && mdns=$(cat "$MDNS_DIR/$ip") # Minimal JSON string escaping -- mDNS hostnames come from the network, # not a source we control. mdns="${mdns//\\/\\\\}" mdns="${mdns//\"/\\\"}" [[ $first -eq 0 ]] && echo "," first=0 if [[ -n "$mdns" ]]; then printf '{"ip":"%s","mac":"%s","state":"%s","mdnsHostname":"%s"}' "$ip" "$mac" "$state" "$mdns" else printf '{"ip":"%s","mac":"%s","state":"%s"}' "$ip" "$mac" "$state" fi done echo echo "]" } > "$TMP_FILE" mv "$TMP_FILE" "$OUTPUT_FILE"