#!/usr/bin/env bash # discover-gaps.sh — find devices in discovery TSVs that aren't in # servers/*/ssh-target (the authoritative "managed hosts" list). # # Input: one or more TSV files, each a line of: # IP MAC HOSTNAME (EXTRA1) (EXTRA2) … # Produced by discover-fortigate.sh, discover-unifi.sh, or equivalent. # # Usage: # scripts/discover-gaps.sh leases-ana.tsv leases-nh3.tsv # # Output: rows where IP does NOT match any IP in servers/*/ssh-target, # sorted for readability. Same TSV format as input. # # Exit code: # 0 if zero gaps found # 1 if one or more unmanaged IPs detected (useful for cron alerting) set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" SERVERS_DIR="$REPO_ROOT/servers" if [ "$#" -lt 1 ]; then echo "usage: $(basename "$0") [more.tsv ...]" >&2 exit 2 fi # Build the set of managed IPs from servers/*/ssh-target. # ssh-target may contain "user@IP" or a bare IP/hostname. We extract any # IPv4 and (if the value is a hostname) try to resolve it. managed_ips=$( for stf in "$SERVERS_DIR"/*/ssh-target; do [ -r "$stf" ] || continue target=$(awk 'NF{print $1; exit}' "$stf") val=${target##*@} if [[ "$val" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "$val" else # getent hosts returns "IP name [alias…]"; take first field if resolved ip=$(getent hosts "$val" 2>/dev/null | awk 'NR==1{print $1}') [ -n "$ip" ] && echo "$ip" fi done | sort -u ) if [ -z "$managed_ips" ]; then echo "warning: no managed IPs found under $SERVERS_DIR/*/ssh-target" >&2 fi # Also tolerate IPs that appear in servers/*/README.md (for hosts that # have snapshot info but no ssh-target — e.g. Proxmox hosts documented # there). Extract any IPv4-looking token from READMEs. readme_ips=$( grep -rhEo '([0-9]+\.){3}[0-9]+' "$SERVERS_DIR"/*/README.md 2>/dev/null | sort -u || true ) all_known=$(printf '%s\n%s\n' "$managed_ips" "$readme_ips" | sort -u) # Read the discovery TSVs, filter out lines whose IP is in all_known. unmanaged=$( cat "$@" | awk -v known="$all_known" ' BEGIN { n=split(known, arr, "\n") for (i=1; i<=n; i++) if (arr[i] != "") k[arr[i]] = 1 } NF >= 1 && $1 ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ && !($1 in k) ' | sort -t$'\t' -k1,1 -V -u ) if [ -z "$unmanaged" ]; then echo "no unmanaged devices — every discovered IP is tracked under servers/" >&2 exit 0 fi printf '%s\n' "$unmanaged" exit 1