Files
claudetools/.claude/skills/unifi-wifi/references/ROADMAP.md
Howard Enos b3c8ee2828 unifi-wifi: pfSense gateway access via SSH (pfSense-ssh.sh) + pfSense health section; layer OFF HOLD
DECISION (Mike, 2026-06-16): drop the RESTAPI package — VPN + SSH shell reads the same data and makes
changes. Confirmed Cascades pfSense is Plus 25.07-RELEASE (current; the "too old" premise was wrong) and
admin SSH = real shell (no menu). The upgrade/package blocker is moot; compat layer is off hold.

- NEW scripts/pfsense-ssh.sh: audit (version/WAN-media/gateway-events/DHCP-exhaustion/states/DNS/load/NIC),
  dhcp (pool utilization + no-free-leases), run "<cmd>" (arbitrary, incl changes; operator-gated). Cred
  from clients/<slug>/pfsense-firewall; system OpenSSH via askpass. Validated live on Cascades.
- audit report: added "pfSense health check (2026-06-16)" — DHCP NOT exhausted (192.168.0.0/22 pool 270/507,
  0 no-free-leases), DNS up, dual-WAN stable (no gateway flaps), states/load healthy => gateway is NOT a
  WiFi factor; the 2.4 GHz RF work is the sole fix. (Minor: igc3/WAN2 I225 2.5G counter quirk, not a fault.)
- ROADMAP §E + SKILL.md updated to the SSH backend decision; REST pfsense-backend.sh kept dormant/optional.
- Remaining: named gated CONTROL verbs over SSH (easyrule block-ips, pf/fw toggles) + optional gw-* dispatch.
- Closed obsolete coord todo (upgrade-pfSense-for-RESTAPI).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 18:42:54 -07:00

13 KiB
Raw Blame History

unifi-wifi — roadmap

Status as of 2026-06-16. The skill is fleet-generic (every script takes <site>; works on any of the ~49 UOS sites) and WiFi data-gathering is complete. Remaining work is the apply/action side, multi-client enablement, and non-WiFi scope. Build/validate new apply actions against zero-client radios (6 GHz is ~0-client fleetwide) — change → verify → revert, no disruption.

Done (WiFi monitoring + analysis)

  • audit-site.sh — config + foreign-interference flags (Plane 1, Mongo).
  • live-stats.sh — live per-AP utilization/satisfaction/retry% + per-client RSSI/retry%/reason (Plane 2).
  • model-rank.sh / optimize-radios.sh — airtime-reduction + coverage-safe plan. optimize-radios consumes the SNR matrix (NEIGHBOR_JSON) for data-backed disables (roam graph fallback).
  • neighbor-collect.sh — AP-to-AP SNR matrix from /proc/ui_neighbor (+ NBR_JSON adjacency emit).
  • survey-collect.sh — measured per-channel busy%/noise → cleanest-channel input.
  • dfs-check.sh — empirical DFS radar history (dmesg) → is DFS safe at this site.
  • watch-ap.sh — live per-AP RF stream (mca-dump + iw survey).
  • apply-radio.sh — gated config apply. Currently: power, width (ht), channel, min-RSSI (all radio_table PUTs). DRY-RUN default; --apply needs infrastructure/uos-server-network-api-rw.

A. Apply side — make WiFi changes fully executable

  • apply-radio: power (tx_power_mode/tx_power)
  • apply-radio: width (ht 20/40/80/160)
  • apply-radio: channel (manual channel assignment)
  • apply-radio: min-RSSI (min_rssi_enabled + min_rssi)
  • apply-radio: disable/enable a radio — DONE. Disable = tx_power_mode:"disabled" (confirmed 2026-06-16 via UI-toggle + device-JSON diff); enable = auto. Validated full cycle on a 0-client 6 GHz radio. Use --ap <name> to target one AP (the right scope for disables). Still highest-risk (coverage holes) — gate + validate per AP with watch-ap; pair with optimize-radios' disable list.
  • min data rates (kill 111 Mbps; 2.4 floor 12/24) — DONE in apply-wlan.sh (minrate ng|na auto|off|<Mbps>). Requires minrate_setting_preference=manual (handled). Validated on a 0-client WLAN. band-steering: DONE — modern UniFi has no bandsteering_mode; located its replacements (2026-06-16) and wired all into apply-wlan: bandsteer (no2ghz_oui), bands (wlan_bands — 5g/5g6e forces clients up), steer (roaming_assistant_na), bsstm (bss_transition, 802.11v). Validated on a 0-client WLAN. (fast_roaming_enabled/802.11r left out — risky for IoT.)
  • full wlanconf knob set — apply-wlan now also does wlan (enable/disable), dtim, mcast, bcfilter, rrm (802.11k), ftroam (802.11r, warns), isolation, hidessid. dtim apply-validated; rest via the proven gated REST path.
  • device-level lock / controlapply-wlan aps <ap-list> (broadcasting_aps = restrict SSID to APs, the "lock to AP" lever) + apply-wlan macfilter off|allow|deny <macs>; new client-control.sh block|unblock|kick a MAC (validated on a dummy MAC). UniFi has NO native per-client AP pin — these are the real mechanisms.
  • channel-plan apply — DONE: channel-plan.sh ng (1/6/11 graph-color via neighbor matrix) + na (lowest-cost non-DFS = survey busy% + neighbor-collision penalty); reports co-channel before/after, gated apply + rollback. Dry-run validated on Cascades (ng 92->35 pairs; na 20->0 + all off DFS). survey-collect now emits SURVEY_JSON for it.

B. Multi-client enablement (use on any client we manage)

  • Per-client AP device-auth cred: vault clients/<x>/unifi-ap-ssh, pass as the script arg (only Cascades exists today). Keys vaulted per-client as needed.
  • Per-client L3 reach to APs (site VPN / route) for the AP-side collectors (Cascades split-tunnel done).
  • Controller-only mode documented + discoverable — scripts/sites.sh lists all sites + AP-cred readiness; controller-side scripts validated on other clients (Glabman, Sonoran Glass). AP-side scripts print the exact vault command when a client's cred is missing (and note controller-side works).

C. Non-WiFi UniFi (currently WIP / out of scope)

  • Switch/PoE collector — DONE: switch-audit.sh (port up/down, PoE budget+draw, ERRORS, rate-based DROPS, PoE faults, and UNDERSPEED = gig-capable port linked at 10/100 = the FastEthernet issue, now systematically found — ~25 such ports at Cascades). Controller-side, any site.
  • Gateway/WAN + site health — DONE: gw-audit.sh (WAN status/IP/uplink, internet latency/ drops/speedtest, gw CPU/mem/uptime, adoption rollup: adopted/disconnected/pending + firmware-upgradable, with flags). Handles third-party-firewall sites. Validated on a USG site + the pfSense Cascades site.
  • adoption / device remediation — DONE: device-control.sh adopt|restart|provision|locate| unlocate|upgrade a device (cmd/devmgr, gated; validated locate/unlocate on AP 622). Pairs with gw-audit.
  • Port-forwards + WAN firewall control — DONE: gw-control.sh pf-list/disable/enable/delete/ set-ports/set-src, fw-disable/enable, block-ips (RW REST: rest/portforward|firewallrule|firewallgroup, gated/DRY-RUN, rollback). Dry-run validated 2026-06-16 on Grabb & Durando USG-3P (closes the brute-forced PPTP forward 1723→GND-SERVER + its GRE WAN_IN rule). First production apply pending.
  • Deeper VPN policy — gateway-hosted VPN server stand-up (USG L2TP/IPsec remote-user, RADIUS), client DHCP/DNS policy. Config beyond port-forward/WAN-firewall; future. Access layer reaches it.

D. Robustness / ops

  • VPN-flap resilience — AP-side loops now retry per AP (3x, capture-to-var so a failed try never appends partial data); dfs-check distinguishes unreachable from no-events. Validated (74/74). Still foreground.
  • Scheduling / fleet monitoringmonitor-run.sh <site|all> = cron-friendly read-only health digest (gateway + switch/PoE flags + WiFi flag count) per site. Validated. (Cron the all sweep nightly.)
  • Vault read-only infrastructure/uos-server-network-api (least-privilege; RW does double duty now).

E. pfSense gateway support — gateway "compatibility layer" (NEW, proposed 2026-06-16)

Why: a very common ACG topology is UniFi APs/switches (on the UOS controller) behind a pfSense gateway instead of a UniFi gateway (USG/UXG/UCG) — Cascades, our own office, and several other clients. The controller-side WiFi/switch tooling already works at these sites, but the gateway verbs (gw-audit health, gw-control port-forwards / WAN firewall / block-ips, VPN) only speak the UniFi controller REST. Goal: make the same verbs work against a pfSense gateway via a thin driver, so one skill covers WiFi+switch+gateway regardless of who makes the gateway.

Design — a gw driver interface (backend behind the existing verbs), not a parallel skill. Detection is already in hand: gw-audit reports num_gw=0 → "no UniFi gateway (third-party firewall, e.g. pfSense)". That signal selects the pfsense backend instead of the unifi backend. The verbs stay identical; only the driver differs:

verb (UniFi backend) pfSense equivalent
gw-audit (WAN/health/uptime) pfctl -si, gateway status, get_interface_*; CPU/mem/uptime via shell
pf-list / pf-set-ports / pf-disable NAT port-forward objects (<nat> in config.xml / REST firewall/nat/port_forward)
fw-disable / fw-enable filter rules (<filter> / REST firewall/rule)
block-ips easyrule block wan <ip> (cleanest map) or an address alias + WAN block rule
VPN server stand-up (Deeper VPN) pfSense is the better VPN host — OpenVPN / IPsec / WireGuard native (relevant to moving clients off Windows RRAS PPTP, cf. Grabb)

Backend options for pfSense (ranked):

  1. pfSense REST API package (pfSense-pkg-API / built-in API on Plus) — cleanest 1:1 object mapping (NAT, rules, aliases, interfaces, services). Cost: must standardize on installing/enabling it fleet-wide.
  2. SSH + stock CLIeasyrule block/unblock (perfect for block-ips), pfSsh.php playback and php -r for config reads/writes + filter_configure/rc.reload_all to apply. No package needed.
  3. diag_command.php php-exec over the web GUI — the fleet's existing ad-hoc pfSense pattern (Howard). Works but fragile (curl quoting; CSRF; see errorlog 2026-06-16). Good stopgap, poor primitive.
  4. config.xml read/edit + reload — most powerful, highest blast radius; backup-first, atomic reload.

Already have: vaulted pfSense creds for Cascades (clients/cascades-tucson/pfsense-firewall, …/pfsense-openvpn-howard) and our office (infrastructure/pfsense-firewall) — i.e. the auth side exists for at least two sites; per-client pfSense cred vaulting mirrors the AP-SSH-cred pattern.

Open decisions — RESOLVED (Howard 2026-06-16, "try what Mike wanted unless we hit a real roadblock"):

  • Primary backend: REST API package (pfSense-pkg-RESTAPI v2, X-API-Key). SSH easyrule/pfSsh.php kept as the documented fallback for boxes that can't install the pkg.
  • Where the abstraction lives: dispatch inside gw-audit.sh/gw-control.sh (keyed on num_gw=0 + a vaulted clients/<slug>/pfsense-api cred, or --pfsense <slug>), so callers/SKILL docs don't fork. Implemented in scripts/pfsense-backend.sh + dispatch hooks in both gw scripts.
  • Reach + rollback: many pfSense gws are behind the site (need site-VPN reach, like the AP collectors). DONE: writes are --apply-gated and save a per-object rollback to .claude/tmp/, and pfSense firewall/apply is called after each change. config.xml backup-first is the SSH-fallback's job.

DECISION (Mike, 2026-06-16): backend = SSH, NOT the REST API package. "We don't need the RESTAPI — with VPN + SSH we can read the same data and make changes." Confirmed: Cascades pfSense is Plus 25.07-RELEASE (current, not old — the earlier "too old" premise was wrong) and admin SSH drops straight to a shell (no menu gotcha). So the upgrade/package blocker is MOOT and the layer is OFF HOLD.

STATUS: WORKING (read) via scripts/pfsense-ssh.sh — control verbs WIP.

  • pfsense-ssh.sh <slug> audit — version/WAN-media/gateway-events/DHCP-exhaustion/states/DNS/load/NIC-errors.
  • pfsense-ssh.sh <slug> dhcp — pool utilization + "no free leases" check.
  • pfsense-ssh.sh <slug> run "<cmd>" — arbitrary command (reads OR changes; operator-gated, no dry-run).
  • Cred = clients/<slug>/pfsense-firewall (host + admin user/pass), system OpenSSH via askpass. Validated live on Cascades 2026-06-16 (the pfSense-health audit in the unifi-full-audit report came from this).

Remaining build (SSH backend): named, reviewed, gated CONTROL verbs mapping the gw-control contract to SSH primitives — block-ipseasyrule block wan <ip>; pf-list/fw-list → read config.xml / pfSsh.php; toggles → config edit + filter_configure/rc.reload_all; backup config.xml first. Then optionally wire gw-audit/gw-control dispatch to the SSH backend when clients/<slug>/pfsense-firewall exists + num_gw=0.

Superseded/optional: the REST pfsense-backend.sh + clients/<slug>/pfsense-api path stays in-tree as a dormant alternative (works if a site ever installs the pkg) but is no longer the plan.

  • Site→gateway map: record per-site gateway type + access (UOS site_id ↔ pfSense host/cred) so the driver auto-selects. Could live alongside sites.sh output.
  • VPN convergence: the "Deeper VPN — gateway-hosted VPN server" item (C) is easier and better on pfSense (WireGuard/OpenVPN) than on a USG — fold the Grabb-style "retire Windows RRAS PPTP → gateway VPN" play into the pfSense driver from the start.

First validation targets: our office + Cascades (creds already vaulted; gw-audit already classifies them as pfSense). Start read-only (gw-audit parity: WAN/health via easyrule/pfctl), then block-ips via easyrule (lowest-risk write, directly useful for the same brute-force containment as the UniFi side).

Cross-platform notes (baked into the scripts; keep for any new ones)

  • Pass temp paths to python via ARGV (MSYS translates POSIX→Windows for python.exe); $TMP inside a python -c string is NOT translated → fails on Windows.
  • Do NOT inject large data as a JS object literal into the mongo shell (~21 KB crashes SpiderMonkey); precompute + inject a compact flat string.
  • AP-side SSH: sshpass or SSH_ASKPASS fallback; </dev/null in while read loops; foreground.
  • Windows text-mode file writes add CRLF → strip \r after bash read.