921891b27a
Three scripts that surface hosts on the fleet's networks that aren't
already tracked under servers/*/. Goal: spot servers that need management
coverage (inventory, backup, monitoring) without wandering the LAN by
hand.
discover-fortigate.sh SSH to a FortiGate admin, run
`execute dhcp lease-list all`, emit TSV
(IP, MAC, hostname, vdom, source).
SSH was picked over the REST API for now
because it needs no API-token plumbing. The
parser is defensive about FortiOS output
format drift (multiple VDOM sections,
optional hostname). API variant can replace
it when the extra robustness is worth the
token setup.
discover-unifi.sh Cookie-auth REST call against a UniFi
Controller. Tries /api/auth/login (UniFi OS
/ UDM / Cloud Key Gen2+) first; falls back
to legacy /api/login for self-hosted
controllers. Output is the same TSV shape
as the FortiGate script so the two mix.
Needs UNIFI_USER / UNIFI_PASS env and jq.
discover-gaps.sh Consumes one or more TSVs from the sources
above. Builds the set of managed IPs from
servers/*/ssh-target (plus a grep of README
files for documented IPs) and prints any
discovered IPs not in that set.
Exit code is 1 if gaps found — suitable for
cron alerting.
Common pipeline:
scripts/discover-fortigate.sh ana-fw.phasefinal.com > /tmp/ana.tsv
scripts/discover-fortigate.sh nh3-gw.phasefinal.com > /tmp/nh3.tsv
UNIFI_USER=admin UNIFI_PASS=… scripts/discover-unifi.sh esh-uc.esteban.net > /tmp/esh.tsv
scripts/discover-gaps.sh /tmp/ana.tsv /tmp/nh3.tsv /tmp/esh.tsv
First-time use probably needs:
- SSH access configured to each FortiGate (admin login, key preferred)
- UniFi user with read access (the built-in API read-only role works)
- `jq` installed on the runner (for UniFi script)
79 lines
2.5 KiB
Bash
Executable File
79 lines
2.5 KiB
Bash
Executable File
#!/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") <leases.tsv> [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
|