46 lines
2.8 KiB
Bash
46 lines
2.8 KiB
Bash
#!/usr/bin/env bash
|
|
# watch-ap.sh — live, low-latency RF watch of a UniFi AP straight from the AP (no controller lag).
|
|
# SSHes into the AP and streams, per radio, every INTERVAL seconds: channel utilization + clients +
|
|
# tx-retries (from mca-dump) and channel busy% + noise floor (from `iw survey`, diffed). Ideal for
|
|
# watching an AP react to a targeted change (power/channel/neighbor-disable) in seconds.
|
|
#
|
|
# REQUIRES: L3 reach to the AP's mgmt IP. At Cascades the APs are on 192.168.2.x/3.x (mgmt VLANs) —
|
|
# bring up the Cascades VPN first. Device-auth SSH cred is vaulted (clients/cascades-tucson/unifi-ap-ssh).
|
|
# Needs `sshpass` locally (UniFi device-auth is password-based). Find AP IPs via:
|
|
# echo 'db.device.find({site_id:"685f39068e65331c46ef6dd2",type:"uap"},{name:1,ip:1}).forEach(printjson)' | bash .claude/scripts/uos-mongo.sh
|
|
#
|
|
# Usage: bash .claude/skills/unifi-wifi/scripts/watch-ap.sh <ap-ip> [interval=2] [vault-path]
|
|
set -euo pipefail
|
|
REPO="$(git rev-parse --show-toplevel 2>/dev/null || echo .)"
|
|
VAULT="$REPO/.claude/scripts/vault.sh"
|
|
AP="${1:?usage: watch-ap.sh <ap-ip> [interval] [vault-path]}"; INT="${2:-2}"; VP="${3:-clients/cascades-tucson/unifi-ap-ssh}"
|
|
U="$(bash "$VAULT" get-field "$VP" credentials.username 2>/dev/null)"
|
|
P="$(bash "$VAULT" get-field "$VP" credentials.password 2>/dev/null)"
|
|
[ -n "$U" ] && [ -n "$P" ] || { echo "[ERROR] no device-auth cred at vault:$VP"; exit 1; }
|
|
command -v sshpass >/dev/null || { echo "[ERROR] sshpass not installed (apt-get install sshpass / brew install sshpass)"; exit 1; }
|
|
|
|
echo "[INFO] watching $AP every ${INT}s (Ctrl-C to stop). Needs Cascades VPN reach."
|
|
# Run the sampling loop ON the AP so each tick is one round-trip; mca-dump for cu/clients, iw survey for busy%/noise.
|
|
SSHPASS="$P" sshpass -e ssh -o ConnectTimeout=8 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
|
"$U@$AP" "INT=$INT sh -s" <<'REMOTE' 2>&1 | grep -viE 'Warning: Permanently|pq.html'
|
|
radios=$(iw dev 2>/dev/null | awk '/Interface/{print $2}' | grep -E 'wifi|ath' || echo "wifi0 wifi1 wifi2")
|
|
prev=""
|
|
while :; do
|
|
ts=$(date +%H:%M:%S)
|
|
# cu_total + num_sta + tx_retries per radio from mca-dump (JSON; parse with grep/sed, no jq on AP)
|
|
dump=$(mca-dump 2>/dev/null)
|
|
for r in $radios; do
|
|
# iw survey: in-use channel active/busy/noise
|
|
surv=$(iw dev "$r" survey dump 2>/dev/null | awk '
|
|
/\[in use\]/{u=1} u&&/frequency/{f=$2} u&&/noise/{n=$2} u&&/channel active time/{a=$4}
|
|
u&&/channel busy time/{b=$4; print f,n,a,b; u=0}')
|
|
set -- $surv; freq="${1:-?}"; noise="${2:-?}"; act="${3:-0}"; busy="${4:-0}"
|
|
key="$r"; pa=$(eval echo \${ACT_$r:-0}); pb=$(eval echo \${BSY_$r:-0})
|
|
da=$((act-pa)); db=$((busy-pb)); pct="?"; [ "$da" -gt 0 ] 2>/dev/null && pct=$((100*db/da))
|
|
eval ACT_$r=$act; eval BSY_$r=$busy
|
|
echo "$ts $r f=${freq} noise=${noise}dBm busy=${pct}%"
|
|
done
|
|
sleep "${INT:-2}"
|
|
done
|
|
REMOTE
|