diff --git a/.claude/skills/unifi-wifi/scripts/apply-radio.sh b/.claude/skills/unifi-wifi/scripts/apply-radio.sh index 1dbe1f9..38591a9 100644 --- a/.claude/skills/unifi-wifi/scripts/apply-radio.sh +++ b/.claude/skills/unifi-wifi/scripts/apply-radio.sh @@ -77,7 +77,7 @@ print("REST PUT /proxy/network/api/s//rest/device/ body: { radio_table JS if [ "$APPLY" != "1" ]; then - echo; echo "[dry-run] no changes made. Add --apply to write (needs the RW admin vaulted — see below)." + echo; echo "[dry-run] no changes made. Add --apply to write (needs the RW admin vaulted - see below)." exit 0 fi diff --git a/.claude/skills/unifi-wifi/scripts/apply-wlan.sh b/.claude/skills/unifi-wifi/scripts/apply-wlan.sh index e1f6b37..bd5ddda 100644 --- a/.claude/skills/unifi-wifi/scripts/apply-wlan.sh +++ b/.claude/skills/unifi-wifi/scripts/apply-wlan.sh @@ -65,7 +65,7 @@ case "$ACT" in rrm) SVAL="${1:?rrm }"; shift # 802.11k neighbor reports (roaming assist) case "$SVAL" in on) FIELDS="{\"rrm_enabled\":true}";; off) FIELDS="{\"rrm_enabled\":false}";; *) echo "rrm: on|off"; exit 1;; esac ;; ftroam) SVAL="${1:?ftroam }"; shift # 802.11r fast roaming - case "$SVAL" in on) FIELDS="{\"fast_roaming_enabled\":true}"; echo "[WARNING] 802.11r can break legacy/medical/IoT clients — test on a scoped SSID first.";; off) FIELDS="{\"fast_roaming_enabled\":false}";; *) echo "ftroam: on|off"; exit 1;; esac ;; + case "$SVAL" in on) FIELDS="{\"fast_roaming_enabled\":true}"; echo "[WARNING] 802.11r can break legacy/medical/IoT clients - test on a scoped SSID first.";; off) FIELDS="{\"fast_roaming_enabled\":false}";; *) echo "ftroam: on|off"; exit 1;; esac ;; isolation) SVAL="${1:?isolation }"; shift # L2 client isolation case "$SVAL" in on) FIELDS="{\"l2_isolation\":true}";; off) FIELDS="{\"l2_isolation\":false}";; *) echo "isolation: on|off"; exit 1;; esac ;; hidessid) SVAL="${1:?hidessid }"; shift diff --git a/.claude/skills/unifi-wifi/scripts/audit-site.sh b/.claude/skills/unifi-wifi/scripts/audit-site.sh index ce79ad2..6f0db65 100644 --- a/.claude/skills/unifi-wifi/scripts/audit-site.sh +++ b/.claude/skills/unifi-wifi/scripts/audit-site.sh @@ -45,7 +45,7 @@ print(" 2.4GHz widths: "+JSON.stringify(ng_w)+" (want only 20)"); print(" 2.4GHz power: "+JSON.stringify(ng_pwr)+" (want low/medium/custom in density)"); print(" 2.4GHz min_rssi OFF on "+ngOffRssi+" radios"); print(" 5GHz radios: "+na_used+" widths: "+JSON.stringify(na_w)+" (want 40 in density, not 80/160)"); -print(" 6GHz radios active: "+sixe_used+" (steer 6E-capable clients here — usually the clean band)"); +print(" 6GHz radios active: "+sixe_used+" (steer 6E-capable clients here - usually the clean band)"); print("\n==== NEIGHBOR-DENSITY MAP (rogue = co-channel interference) ===="); print(" 2.4GHz:"); db.rogue.aggregate([{\$match:{site_id:SITE,band:'ng'}},{\$group:{_id:'\$channel',n:{\$sum:1}}},{\$sort:{n:-1}},{\$limit:6}]).forEach(function(d){print(" ch"+d._id+": "+d.n+" neighbor BSSIDs")}); diff --git a/.claude/skills/unifi-wifi/scripts/dfs-check.sh b/.claude/skills/unifi-wifi/scripts/dfs-check.sh index 6706639..8fe5fe3 100644 --- a/.claude/skills/unifi-wifi/scripts/dfs-check.sh +++ b/.claude/skills/unifi-wifi/scripts/dfs-check.sh @@ -83,8 +83,8 @@ while IFS=$'\t' read -r name ip ch dfs; do done < "$TMP/aps.tsv"; echo "" >&2 echo "" if [ "$hits" -eq 0 ]; then - echo "[OK] No real radar/DFS events found in any AP's dmesg → DFS appears low-risk at this site." + echo "[OK] No real radar/DFS events found in any AP's dmesg -> DFS appears low-risk at this site." echo " (dmesg is bounded; re-run periodically. Absence over long uptime = strong signal DFS is usable.)" else - echo "[WARNING] $hits AP(s) logged radar/DFS events → DFS is being hit; prefer non-DFS (UNII-1 36-48 + UNII-3 149-165)." + echo "[WARNING] $hits AP(s) logged radar/DFS events -> DFS is being hit; prefer non-DFS (UNII-1 36-48 + UNII-3 149-165)." fi diff --git a/.claude/skills/unifi-wifi/scripts/monitor-run.sh b/.claude/skills/unifi-wifi/scripts/monitor-run.sh index a065671..bc11bd5 100644 --- a/.claude/skills/unifi-wifi/scripts/monitor-run.sh +++ b/.claude/skills/unifi-wifi/scripts/monitor-run.sh @@ -31,7 +31,7 @@ sweep_one(){ # $1 = site id, $2 = display name } if [ "$ARG" = all ]; then - echo "[INFO] fleet health sweep — $(date '+%Y-%m-%d %H:%M') — all UOS sites (controller-side, read-only)" + echo "[INFO] fleet health sweep - $(date '+%Y-%m-%d %H:%M') - all UOS sites (controller-side, read-only)" bash "$UOS" --sites 2>/dev/null | clean | grep -E '^[0-9a-f]{24}' | while read -r sid rest; do sweep_one "$sid" "$rest" done @@ -39,6 +39,6 @@ else if [[ "$ARG" =~ ^[0-9a-f]{24}$ ]]; then SID="$ARG"; NM=""; else line="$(bash "$UOS" --sites 2>/dev/null | clean | grep -i "$ARG" | head -1)"; SID="${line%% *}"; NM="${line#* }"; fi [ -n "$SID" ] || { echo "[ERROR] site not found: $ARG"; exit 1; } - echo "[INFO] health sweep — $(date '+%Y-%m-%d %H:%M') — $NM" + echo "[INFO] health sweep - $(date '+%Y-%m-%d %H:%M') - $NM" sweep_one "$SID" "$NM" fi diff --git a/.claude/skills/unifi-wifi/scripts/neighbor-collect.sh b/.claude/skills/unifi-wifi/scripts/neighbor-collect.sh index 76b5e08..090dc68 100644 --- a/.claude/skills/unifi-wifi/scripts/neighbor-collect.sh +++ b/.claude/skills/unifi-wifi/scripts/neighbor-collect.sh @@ -162,7 +162,7 @@ if OUTJSON!='NONE': for (src,b),nbrs in edges.items(): adj.setdefault(src,{})[b]=nbrs json.dump(adj, open(OUTJSON,'w')) - print(f"\n[INFO] wrote adjacency JSON -> {OUTJSON} ({len(adj)} APs) — feed to optimize-radios.sh via NEIGHBOR_JSON") + print(f"\n[INFO] wrote adjacency JSON -> {OUTJSON} ({len(adj)} APs) - feed to optimize-radios.sh via NEIGHBOR_JSON") PY echo "" echo "[next] feed the redundancy list to optimize-radios.sh; validate per-zone with watch-ap.sh before any --apply." diff --git a/.claude/skills/unifi-wifi/scripts/optimize-radios.sh b/.claude/skills/unifi-wifi/scripts/optimize-radios.sh index 6eee683..6307cf7 100644 --- a/.claude/skills/unifi-wifi/scripts/optimize-radios.sh +++ b/.claude/skills/unifi-wifi/scripts/optimize-radios.sh @@ -80,7 +80,7 @@ st.stat_hourly.find({o:'ap',site_id:SITE,time:{\$gte:since}}).forEach(function(d }); // tx_retries is a COUNT, not a %, so normalize by tx attempts; satisfaction averaged over its own samples. for(var k in prof){var p=prof[k];['cu','intf','self','sta'].forEach(function(f){p[f]/=p.n;}); - p.retrPct=p.att>0?Math.min(100,100*p.retr/p.att):0; p.sat=p.satN>0?p.sat/p.satN:100;} + p.retrPct=p.att>0?Math.min(100,100*p.retr/p.att):0; p.sat=p.satN>0?p.sat/p.satN:100; void 0;} // void 0: stop the mongo REPL echoing this for-loop's numeric completion value // directional roam edges + RSSI var dir={}; // "A>B"->count ; rs["A>B"]->[rssi to B] var rs={}; @@ -153,9 +153,9 @@ var save=0;disabled.forEach(function(d){save+=prof[d.ap].intf;}); print("Phase A: POWER-DOWN the busy/thrashing radios to ~Low (smaller cells cut mutual cu_interf for the whole zone). Do this FIRST."); var pz=byZone(powerdown,function(a){return a;}); Object.keys(pz).sort().forEach(function(z){print(" ["+z+"] "+pz[z].length);pz[z].slice(0,6).forEach(function(a){print(" "+fmt(a));}); if(pz[z].length>6)print(" ...(+"+(pz[z].length-6)+")");}); -print("\nPhase B: re-measure (live-stats.sh) after power-down settles — cu_interf should drop, headroom appears."); +print("\nPhase B: re-measure (live-stats.sh) after power-down settles - cu_interf should drop, headroom appears."); print("\nPhase C: DISABLE these only-when-redundant radios (each has >="+REDUN+" bidirectional good neighbors WITH headroom to absorb its load). Est. interference airtime removed: "+save.toFixed(0)+"."); -if(!disabled.length) print(" (none clear the capacity+coverage bar yet — expected when every neighbor is saturated; revisit after Phase A.)"); +if(!disabled.length) print(" (none clear the capacity+coverage bar yet - expected when every neighbor is saturated; revisit after Phase A.)"); var dz=byZone(disabled,function(d){return d.ap;}); Object.keys(dz).sort().forEach(function(z){print(" ["+z+"]");dz[z].forEach(function(d){print(" "+fmt(d.ap)+" -> covered by: "+d.cover.slice(0,3).join(', '));});}); print("\nKEEP: "+keep.length+" radios (isolated-essential or already efficient). Apply per ZONE; never >"+ZPCT+"% disabled/zone; validate before+after."); diff --git a/.claude/skills/unifi-wifi/scripts/pfsense-backend.sh b/.claude/skills/unifi-wifi/scripts/pfsense-backend.sh index cee97f3..64223cb 100644 --- a/.claude/skills/unifi-wifi/scripts/pfsense-backend.sh +++ b/.claude/skills/unifi-wifi/scripts/pfsense-backend.sh @@ -34,7 +34,7 @@ while [ $# -gt 0 ]; do case "$1" in *) POS+=("$1"); shift;; esac; done setup_msg(){ cat < System > Package Manager > Available Packages: install 'RESTAPI' 2) System > REST API > Settings: enable; Auth Method = API Key 3) System > REST API > Keys: create a key (READ-ONLY for audit; a write-capable key for pf-/fw-/block-ips) @@ -42,7 +42,7 @@ setup_msg(){ cat < pfSense REST API' --tag pfsense \\ --set url=https:// --set apikey= - (No package / can't install? SSH 'easyrule' + config.xml fallback is the planned alt backend — ROADMAP §E.) + (No package / can't install? SSH 'easyrule' + config.xml fallback is the planned alt backend - ROADMAP section E.) EOF } [ "$ACT" = "setup" ] && { setup_msg; exit 0; } @@ -77,7 +77,7 @@ def err(e): try: body=e.read().decode('utf-8','replace')[:300] except Exception: pass print(f"[FAIL] HTTP {getattr(e,'code','?')}: {body or e}") - print("[hint] if 404/endpoint-not-found, the installed REST API version uses a different path — " + print("[hint] if 404/endpoint-not-found, the installed REST API version uses a different path - " "check System > REST API > Documentation and adjust pfsense-backend.sh.") sys.exit(1) def save_rollback(tag,obj): @@ -119,7 +119,7 @@ try: s=data(call('GET',ep)) if isinstance(s,dict): print(" "+", ".join(f"{k}={s.get(k)}" for k in list(s)[:8])); break except urllib.error.HTTPError: continue - else: print(" (system endpoint not found — verify path)") + else: print(" (system endpoint not found - verify path)") print("\n== DHCP scopes (pool pressure) ==") try: ds=data(call('GET','/services/dhcp_server')) diff --git a/.claude/skills/unifi-wifi/scripts/sites.sh b/.claude/skills/unifi-wifi/scripts/sites.sh index 35938dc..9947089 100644 --- a/.claude/skills/unifi-wifi/scripts/sites.sh +++ b/.claude/skills/unifi-wifi/scripts/sites.sh @@ -36,9 +36,9 @@ echo "[INFO] AP-side collectors (neighbor/survey/dfs/watch-ap) need a per-client echo " Vaulted AP creds found (clients/*/unifi-ap-ssh):" if [ -d "$VROOT" ]; then found=$(find "$VROOT/clients" -name 'unifi-ap-ssh.sops.yaml' 2>/dev/null | sed -E 's#.*/clients/([^/]+)/.*# [ready] clients/\1/unifi-ap-ssh#' | sort) - [ -n "$found" ] && echo "$found" || echo " (none yet — only the controller-side scripts will work until you vault one)" + [ -n "$found" ] && echo "$found" || echo " (none yet - only the controller-side scripts will work until you vault one)" else - echo " (vault not found at $VROOT — set VAULT_ROOT)" + echo " (vault not found at $VROOT - set VAULT_ROOT)" fi cat <<'EOF' To enable AP-side collectors for a new client: diff --git a/.claude/skills/unifi-wifi/scripts/survey-collect.sh b/.claude/skills/unifi-wifi/scripts/survey-collect.sh index d71aaea..d2c0498 100644 --- a/.claude/skills/unifi-wifi/scripts/survey-collect.sh +++ b/.claude/skills/unifi-wifi/scripts/survey-collect.sh @@ -100,7 +100,7 @@ for ln in open(sys.argv[1],encoding='utf-8',errors='replace'): elif 'channel active time' in ln: m=re.search(r'(\d+) ms',ln); rec['act']=int(m.group(1)) if m else 0 elif 'channel busy time' in ln: m=re.search(r'(\d+) ms',ln); rec['busy']=int(m.group(1)) if m else 0 flush() -print(f"\n==== MEASURED RF OCCUPANCY — cleanest channels per AP ({len(data)} APs) ====") +print(f"\n==== MEASURED RF OCCUPANCY - cleanest channels per AP ({len(data)} APs) ====") print("(in-use channel busy%, then 3 lowest-busy NON-DFS channels measured; * = DFS)\n") for ap in sorted(data): print(f"{ap}:") @@ -120,7 +120,7 @@ if OUT!='NONE': for busy,c,inuse,noise in data[ap][b]: j.setdefault(ap,{}).setdefault(b,{})[str(c)]=busy # busy% per channel json.dump(j,open(OUT,'w')) - print(f"\n[INFO] wrote survey JSON -> {OUT} ({len(j)} APs) — feed to channel-plan.sh via SURVEY_JSON") + print(f"\n[INFO] wrote survey JSON -> {OUT} ({len(j)} APs) - feed to channel-plan.sh via SURVEY_JSON") PY echo "" echo "[next] use the cleanest-channel data for a manual 1/6/11 (2.4) + non-DFS (5GHz) plan; apply via apply-radio.sh per zone."