From 57c944ad5f6e926978b416af77858ece2ee2a22f Mon Sep 17 00:00:00 2001 From: Vuong Hoang Date: Tue, 21 Apr 2026 14:12:42 -0700 Subject: [PATCH] scripts/discover-unifi: correct field selectors for real API response shape MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Raw dumps of /ea/hosts and /ea/devices surfaced the actual JSON: - /ea/hosts: LAN IP isn't at top-level ipAddress (that's WAN public); it's buried in reportedState.ipAddrs[] mixed with WAN + link-local. Have to pick the first RFC1918 entry that ISN'T also a WAN interface IP (reportedState.wans[].ipv4). Name/mac/model all live under reportedState.{hostname,mac,hardware.shortname}. - /ea/devices: outer records are per-host wrappers; real AP/switch records are in the nested `devices` array with top-level `ip`, `mac`, `name`, `model` fields. Previous parser was reading the wrapper and getting all `-`. Reorder all TSV outputs so IP is column 1 — makes discover-gaps.sh work uniformly against both FortiGate and UniFi sources. Sites TSV dropped its IP slot since sites have no meaningful IP (metadata only). Verified against the real payloads the user captured: ESH-UDMPM now surfaces as 10.0.0.1 (LAN) instead of 192.168.200.111 (WAN2, RFC1918 but excluded via the wans cross-check). A sample device record (E7-ESH-Media at 10.0.250.176) flattens correctly into a single TSV row. --- scripts/discover-unifi.sh | 56 +++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/scripts/discover-unifi.sh b/scripts/discover-unifi.sh index 19f3c1f..3ae616b 100755 --- a/scripts/discover-unifi.sh +++ b/scripts/discover-unifi.sh @@ -20,15 +20,19 @@ # UNIFI_API_KEY=... # and source it before calling. # -# Output: TSV on stdout. Column layout depends on the endpoint: +# Output: TSV on stdout. IP is column 1 for hosts/devices so these can +# be piped into discover-gaps.sh directly: # -# hosts: host_id host_name host_ip model_name controller_version -# sites: site_id site_name host_id description -# devices: mac ip name model site_id host_id +# hosts: IP MAC NAME MODEL FW_VERSION SOURCE +# (IP is LAN — picked from reportedState.ipAddrs first +# private-range entry; falls back to WAN if none found) +# devices: IP MAC NAME MODEL HOST_NAME SOURCE +# (APs, switches — the actual network equipment) +# sites: SITE_ID SITE_NAME HOST_ID SOURCE +# (metadata only — no IP column, not useful for gap analysis) # -# Each row ends with a SOURCE column so outputs can be concatenated and -# still distinguished: -# unifi: +# SOURCE column is "unifi:" so concatenated outputs stay +# distinguishable. # # Requires: curl + jq (apt install jq). @@ -107,12 +111,27 @@ ui_get() { # ---------------------------------------------------------------------- emit_hosts() { + # LAN IP is the first RFC1918 entry in reportedState.ipAddrs that is + # NOT also present as a WAN ipv4 (reportedState.wans[]) — UDMs with + # RFC1918-addressed WAN2 interfaces would otherwise get mis-picked as + # their LAN IP. Falls back through the usual chain if nothing matches. ui_get /ea/hosts | jq -r ' + . as $h | + (($h.reportedState.wans // []) | map(.ipv4 // empty)) as $wans | [ - (.id // "-"), - (.hardware.name // .userData.name // .reportedState.name // "-"), - (.ipAddress // .reportedState.ip // "-"), - (.hardware.shortname // .type // "-"), + ( + ([(.reportedState.ipAddrs // [])[] | select( + (test("^10\\.") or + test("^192\\.168\\.") or + test("^172\\.(1[6-9]|2[0-9]|3[0-1])\\.") + ) and + (. as $ip | ($wans | index($ip)) == null) + )] | .[0]) + // .reportedState.ip // .ipAddress // "-" + ), + (.reportedState.mac // "-"), + (.reportedState.hostname // .reportedState.name // "-"), + (.reportedState.hardware.shortname // .reportedState.hardware.name // .type // "-"), (.reportedState.version // "-"), "unifi:hosts" ] | @tsv @@ -120,26 +139,29 @@ emit_hosts() { } emit_sites() { + # Sites have no IP — no point running these through discover-gaps.sh. + # Kept here for inventory purposes only. ui_get /ea/sites | jq -r ' [ (.siteId // .id // "-"), (.meta.name // .name // "-"), (.hostId // "-"), - (.meta.desc // "-"), "unifi:sites" ] | @tsv ' } emit_devices() { + # /ea/devices returns per-host wrappers; flatten into one row per AP/switch. ui_get /ea/devices | jq -r ' + . as $h | + (.devices // [])[] | [ - (.mac // "-"), (.ip // "-"), - (.name // .hardware.name // "-"), - (.model // .hardware.shortname // "-"), - (.siteId // "-"), - (.hostId // "-"), + (.mac // .id // "-"), + (.name // "-"), + (.model // .shortname // "-"), + ($h.hostName // $h.hostId // "-"), "unifi:devices" ] | @tsv '