From 9a2f806c6737304c77f8bfe2c02871a9077219a2 Mon Sep 17 00:00:00 2001 From: Mike Swanson Date: Mon, 15 Jun 2026 18:29:02 -0700 Subject: [PATCH] sync: auto-sync from GURU-5070 at 2026-06-15 18:28:49 Author: Mike Swanson Machine: GURU-5070 Timestamp: 2026-06-15 18:28:49 --- .claude/skills/unifi-wifi/SKILL.md | 7 +++ .claude/skills/unifi-wifi/scripts/watch-ap.sh | 45 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 .claude/skills/unifi-wifi/scripts/watch-ap.sh diff --git a/.claude/skills/unifi-wifi/SKILL.md b/.claude/skills/unifi-wifi/SKILL.md index d20b081..e3884cd 100644 --- a/.claude/skills/unifi-wifi/SKILL.md +++ b/.claude/skills/unifi-wifi/SKILL.md @@ -51,6 +51,13 @@ controller knows and making prioritized, validated changes. Built for any site; ``` Needs a read-only controller admin vaulted at `infrastructure/uos-server-network-api` (the script prints the one-time provisioning steps if it's missing). + - **Per-AP live watch (no controller lag)** — SSH straight into an AP and stream channel-busy% + + noise + clients/retries every ~2s, to watch a targeted change land in real time: + ```bash + bash .claude/skills/unifi-wifi/scripts/watch-ap.sh [interval] + ``` + Uses `mca-dump` + `iw survey` on the AP. Device-auth cred vaulted (`clients/cascades-tucson/unifi-ap-ssh`). + Needs L3 reach to the AP (at Cascades: bring up the site VPN; APs are on 192.168.2.x/3.x) + local `sshpass`. 5. **Interpret** the flags against `methodology.md` (fix order: prune 2.4 -> shrink cells/power -> min data rates -> manual 1/6/11 plan -> min-RSSI + roaming -> steer to 6GHz). 3. **Recommend** a prioritized, per-zone change plan. Roll out per zone, not site-wide at once. diff --git a/.claude/skills/unifi-wifi/scripts/watch-ap.sh b/.claude/skills/unifi-wifi/scripts/watch-ap.sh new file mode 100644 index 0000000..2a291f9 --- /dev/null +++ b/.claude/skills/unifi-wifi/scripts/watch-ap.sh @@ -0,0 +1,45 @@ +#!/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 [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 [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