sync: auto-sync from HOWARD-HOME at 2026-06-21 13:18:04
Author: Howard Enos Machine: HOWARD-HOME Timestamp: 2026-06-21 13:18:04
This commit is contained in:
@@ -63,11 +63,14 @@ path is Cascades — override with the script's vault-path arg per client.
|
|||||||
`pf-set-src <cidr|any>` (port-forwards + associated filter rule) — built, **live-verify pending** (no
|
`pf-set-src <cidr|any>` (port-forwards + associated filter rule) — built, **live-verify pending** (no
|
||||||
box with forwards available yet). Filter rules match by `tracker` (the `id` field is empty on 25.07) or
|
box with forwards available yet). Filter rules match by `tracker` (the `id` field is empty on 25.07) or
|
||||||
exact `descr`. Each write backs up `config.xml` first; writes drive `scripts/pfsense-gwc.php`.
|
exact `descr`. Each write backs up `config.xml` first; writes drive `scripts/pfsense-gwc.php`.
|
||||||
- **Dispatch:** `gw-audit.sh`/`gw-control.sh` auto-route the SAME verbs to this SSH backend when a
|
- **Dispatch + auto-select:** `gw-audit.sh`/`gw-control.sh` route the SAME verbs to this SSH backend three
|
||||||
`clients/<slug>/pfsense-firewall` cred is vaulted (dispatch runs before UOS site resolution, so a
|
ways: (1) explicit `--pfsense <slug-or-vault-path>`; (2) the site arg is itself a client slug with a vaulted
|
||||||
pfSense-only slug works; pass `--pfsense <slug>` if the UOS site name differs). The REST
|
cred; (3) **auto-select** — the resolved UOS site is bound in `references/site-gateways.tsv`, so no
|
||||||
`pfsense-backend.sh` (`clients/<slug>/pfsense-api`) remains a **dormant fallback** only. Design/verb-map +
|
`--pfsense` is needed (`gw-control Cascades fw-list` just works). Manage the map with
|
||||||
pfSense PHP gotchas: `references/ROADMAP.md` §E.
|
`scripts/gateway-map.sh` (`lookup`/`list`/`validate`/`suggest`); `suggest` lists no-UniFi-gateway sites
|
||||||
|
not yet bound + vaulted creds not yet referenced. The REST `pfsense-backend.sh`
|
||||||
|
(`clients/<slug>/pfsense-api`) remains a **dormant fallback** only. Design/verb-map + pfSense PHP gotchas:
|
||||||
|
`references/ROADMAP.md` §E.
|
||||||
- **Per-client requirement:** `watch-ap`/`neighbor-collect`/`survey-collect`/`dfs-check` default the
|
- **Per-client requirement:** `watch-ap`/`neighbor-collect`/`survey-collect`/`dfs-check` default the
|
||||||
AP device-auth SSH cred to `clients/cascades-tucson/unifi-ap-ssh`; for another client, vault its
|
AP device-auth SSH cred to `clients/cascades-tucson/unifi-ap-ssh`; for another client, vault its
|
||||||
own `clients/<x>/unifi-ap-ssh` and pass it as the script's vault-path arg.
|
own `clients/<x>/unifi-ap-ssh` and pass it as the script's vault-path arg.
|
||||||
|
|||||||
@@ -159,8 +159,12 @@ as a dormant alternative (works if a site ever installs the pkg) but is no longe
|
|||||||
classified UniFi-gateway (model) vs no-UniFi-gateway (pfSense/third-party candidate), plus the list of
|
classified UniFi-gateway (model) vs no-UniFi-gateway (pfSense/third-party candidate), plus the list of
|
||||||
vaulted `clients/*/pfsense-firewall` creds with resolved host:port (SSH-backend-ready). Generated live
|
vaulted `clients/*/pfsense-firewall` creds with resolved host:port (SSH-backend-ready). Generated live
|
||||||
(always current), not a static file. Today: 12 UniFi-gw / 36 no-UniFi-gw sites; 1 pfSense cred (Cascades).
|
(always current), not a static file. Today: 12 UniFi-gw / 36 no-UniFi-gw sites; 1 pfSense cred (Cascades).
|
||||||
- [ ] **Auto-select from the map** (driver picks the pfSense cred for a site WITHOUT `--pfsense`): cred-path
|
- [x] **Auto-select from the map** (2026-06-21): `gw-audit`/`gw-control` now route a site to its pfSense
|
||||||
convention now settled (option A, above) — remaining blocker is just a persisted UOS-site-name→cred binding.
|
cred automatically (no `--pfsense`) when it's bound in `references/site-gateways.tsv` (keyed on the resolved
|
||||||
|
24-hex UOS site_id; carries an optional port). Manage with `scripts/gateway-map.sh`
|
||||||
|
(`lookup`/`list`/`validate`/`suggest` — suggest cross-refs no-UniFi-gw controller sites + vaulted creds to
|
||||||
|
find unmapped bindings). Validated: `gw-control Cascades fw-list` and `gw-audit Cascades` both auto-dispatch
|
||||||
|
to the pfSense SSH backend. Seeded with Cascades; add rows via `gateway-map.sh suggest`.
|
||||||
- [ ] **VPN convergence:** the "Deeper VPN — gateway-hosted VPN server" item (C) is *easier and better* on
|
- [ ] **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"
|
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.
|
play into the pfSense driver from the start.
|
||||||
|
|||||||
95
.claude/skills/unifi-wifi/scripts/gateway-map.sh
Normal file
95
.claude/skills/unifi-wifi/scripts/gateway-map.sh
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# gateway-map.sh — manage + query the persisted UOS-site -> pfSense-cred map (references/site-gateways.tsv).
|
||||||
|
# This is the "auto-select" half of ROADMAP §E: gw-audit/gw-control call `lookup` to pick the pfSense SSH
|
||||||
|
# backend for a site automatically (no --pfsense). Humans use list/validate/suggest to keep the map honest.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# gateway-map.sh lookup <site_id|site_name> # print "<cred_path>\t<port>" for a mapped site (exit 1 if none)
|
||||||
|
# gateway-map.sh list # show the map (with cred-resolves check)
|
||||||
|
# gateway-map.sh validate # check every row: 24-hex site_id + cred resolves in vault
|
||||||
|
# gateway-map.sh suggest # no-UniFi-gw UOS sites NOT yet mapped + unbound pfSense creds
|
||||||
|
set -uo pipefail
|
||||||
|
REPO="$(git rev-parse --show-toplevel 2>/dev/null || echo .)"
|
||||||
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
UOS="$REPO/.claude/scripts/uos-mongo.sh"; VAULT="$REPO/.claude/scripts/vault.sh"
|
||||||
|
MAP="$HERE/../references/site-gateways.tsv"
|
||||||
|
# Mandatory skill error logging (skill-creator rule): log GENUINE functional failures only.
|
||||||
|
logerr(){ bash "$REPO/.claude/scripts/log-skill-error.sh" "unifi-wifi/gateway-map" "$1" --context "${2:-}" >/dev/null 2>&1 || true; }
|
||||||
|
VROOT="${VAULT_ROOT:-$(jq -r '.vault_path // empty' "$REPO/.claude/identity.json" 2>/dev/null)}"
|
||||||
|
[ -n "$VROOT" ] || VROOT="$REPO/../vault"
|
||||||
|
|
||||||
|
ACT="${1:-list}"; ARG="${2:-}"
|
||||||
|
[ -f "$MAP" ] || { echo "[ERROR] map not found: $MAP"; exit 2; }
|
||||||
|
is_hex24(){ printf '%s' "$1" | grep -qE '^[0-9a-f]{24}$'; }
|
||||||
|
cred_ok(){ [ -n "$(bash "$VAULT" get-field "$1" credentials.password 2>/dev/null || bash "$VAULT" get-field "$1" password 2>/dev/null)" ]; }
|
||||||
|
|
||||||
|
# iterate data rows -> calls `row <site_id> <cred> <port> <name>`
|
||||||
|
each_row(){ while IFS=$'\t' read -r sid cred port name; do case "${sid:-}" in ''|'#'*) continue;; esac; "$1" "$sid" "$cred" "${port:-}" "${name:-}"; done < "$MAP"; }
|
||||||
|
|
||||||
|
case "$ACT" in
|
||||||
|
lookup)
|
||||||
|
[ -n "$ARG" ] || { echo "[ERROR] lookup needs <site_id|site_name>" >&2; exit 2; }
|
||||||
|
found=""
|
||||||
|
while IFS=$'\t' read -r sid cred port name; do
|
||||||
|
case "${sid:-}" in ''|'#'*) continue;; esac
|
||||||
|
if [ "$sid" = "$ARG" ]; then found="$cred ${port:-}"; break; fi
|
||||||
|
if ! is_hex24 "$ARG"; then
|
||||||
|
ln=$(printf '%s' "${name:-}" | tr 'A-Z' 'a-z'); lk=$(printf '%s' "$ARG" | tr 'A-Z' 'a-z')
|
||||||
|
[ -n "$ln" ] && case "$ln" in *"$lk"*) found="$cred ${port:-}"; break;; esac
|
||||||
|
fi
|
||||||
|
done < "$MAP"
|
||||||
|
[ -n "$found" ] && { printf '%s\n' "$found"; exit 0; } || exit 1
|
||||||
|
;;
|
||||||
|
|
||||||
|
list)
|
||||||
|
echo "[INFO] gateway map ($MAP):"
|
||||||
|
printf " %-26s %-42s %-5s %s\n" "site_id" "cred_path" "port" "site_name / cred-check"
|
||||||
|
printf " %-26s %-42s %-5s %s\n" "-------" "---------" "----" "---------"
|
||||||
|
_row(){ local sid="$1" cred="$2" port="$3" name="$4" tag
|
||||||
|
if cred_ok "$cred"; then tag="[ok]"; else tag="[MISSING CRED]"; fi
|
||||||
|
printf " %-26s %-42s %-5s %s %s\n" "$sid" "$cred" "${port:--}" "$name" "$tag"; }
|
||||||
|
each_row _row
|
||||||
|
;;
|
||||||
|
|
||||||
|
validate)
|
||||||
|
rc=0
|
||||||
|
_row(){ local sid="$1" cred="$2" port="$3" name="$4"
|
||||||
|
is_hex24 "$sid" || { echo "[BAD] site_id not 24-hex: '$sid' ($name)"; rc=1; return; }
|
||||||
|
if cred_ok "$cred"; then echo "[ok] $name ($sid) -> $cred"; else echo "[MISSING CRED] $name ($sid) -> $cred"; rc=1; fi; }
|
||||||
|
each_row _row
|
||||||
|
[ $rc -eq 0 ] && echo "[OK] all rows valid" || echo "[WARNING] some rows need attention"
|
||||||
|
exit $rc
|
||||||
|
;;
|
||||||
|
|
||||||
|
suggest)
|
||||||
|
echo "[INFO] Cross-referencing controller (no-UniFi-gateway sites) + vault (pfSense creds) vs the map..."
|
||||||
|
# mapped site_ids
|
||||||
|
mapped="$(grep -vE '^\s*#|^\s*$' "$MAP" | cut -f1 | tr '\n' ' ')"
|
||||||
|
# no-UniFi-gateway sites from the controller (site_id<TAB>name)
|
||||||
|
third="$(cat <<'JS' | bash "$UOS" 2>/dev/null | grep -viE 'WARNING|post-quantum|store now|upgraded|openssh'
|
||||||
|
var s={}; db.site.find({},{name:1,desc:1}).forEach(function(x){ s[x._id.str]={n:(x.desc||x.name),gw:0}; });
|
||||||
|
db.device.find({type:{$in:['ugw','uxg','udm','ucg']}},{site_id:1}).forEach(function(d){ if(s[d.site_id])s[d.site_id].gw++; });
|
||||||
|
Object.keys(s).forEach(function(id){ if(s[id].gw===0) print(id+"\t"+s[id].n); });
|
||||||
|
JS
|
||||||
|
)"
|
||||||
|
if [ -z "$third" ]; then echo "[WARNING] no controller data (UOS unreachable?)"; logerr "UOS query returned no sites (suggest)" "uos=$UOS"; fi
|
||||||
|
echo
|
||||||
|
echo " No-UniFi-gateway sites NOT yet in the map (candidates to bind):"
|
||||||
|
printf '%s\n' "$third" | while IFS=$'\t' read -r id nm; do
|
||||||
|
[ -n "$id" ] || continue
|
||||||
|
case " $mapped " in *" $id "*) ;; *) echo " [unmapped] $id $nm";; esac
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
echo " Vaulted pfSense creds (clients/*/pfsense-firewall) and whether a map row references them:"
|
||||||
|
if [ -d "$VROOT" ]; then
|
||||||
|
find "$VROOT/clients" -name 'pfsense-firewall.sops.yaml' 2>/dev/null | sort | while IFS= read -r f; do
|
||||||
|
slug=$(printf '%s' "$f" | sed -E 's#.*/clients/([^/]+)/.*#\1#'); cp="clients/$slug/pfsense-firewall"
|
||||||
|
if grep -qF " $cp " "$MAP" 2>/dev/null || grep -qF " $cp" "$MAP" 2>/dev/null; then echo " [bound] $cp"; else echo " [UNBOUND] $cp (add a row binding a site_id to it)"; fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
echo " To bind: add a TAB-separated row to $MAP: <site_id>\\t<cred_path>\\t<port|->\\t<name>"
|
||||||
|
;;
|
||||||
|
|
||||||
|
*) echo "usage: gateway-map.sh <lookup|list|validate|suggest> [arg]"; exit 1;;
|
||||||
|
esac
|
||||||
@@ -105,20 +105,29 @@ NGW="$(cat "$TMP/ngw" 2>/dev/null || echo 1)"
|
|||||||
if [ "$NGW" = "0" ]; then
|
if [ "$NGW" = "0" ]; then
|
||||||
# PREFERRED: SSH backend (Mike 2026-06-16). Target = full vault path if --pfsense contains '/'
|
# PREFERRED: SSH backend (Mike 2026-06-16). Target = full vault path if --pfsense contains '/'
|
||||||
# (option A, Mike 2026-06-21), else a client slug -> clients/<slug>/pfsense-firewall.
|
# (option A, Mike 2026-06-21), else a client slug -> clients/<slug>/pfsense-firewall.
|
||||||
ssh_target=""
|
ssh_target=""; ssh_port=""
|
||||||
|
# 1. explicit --pfsense full vault path (option A)
|
||||||
case "$PFARG" in
|
case "$PFARG" in
|
||||||
*/*) if [ -n "$(bash "$VAULT" get-field "$PFARG" credentials.password 2>/dev/null || bash "$VAULT" get-field "$PFARG" password 2>/dev/null)" ]; then ssh_target="$PFARG"; fi ;;
|
*/*) if [ -n "$(bash "$VAULT" get-field "$PFARG" credentials.password 2>/dev/null || bash "$VAULT" get-field "$PFARG" password 2>/dev/null)" ]; then ssh_target="$PFARG"; fi ;;
|
||||||
esac
|
esac
|
||||||
if [ -z "$ssh_target" ]; then
|
# 2. explicit --pfsense client slug
|
||||||
for s in "$PFARG" "$SITEARG"; do
|
if [ -z "$ssh_target" ] && [ -n "$PFARG" ]; then case "$PFARG" in
|
||||||
[ -n "$s" ] || continue
|
*/*) ;; *) [ -n "$(bash "$VAULT" get-field "clients/$PFARG/pfsense-firewall" credentials.password 2>/dev/null)" ] && ssh_target="$PFARG";; esac; fi
|
||||||
case "$s" in */*) continue;; esac
|
# 3. AUTO-SELECT from the gateway map (no --pfsense needed): bind by resolved site name/id
|
||||||
if [ -n "$(bash "$VAULT" get-field "clients/$s/pfsense-firewall" credentials.password 2>/dev/null)" ]; then ssh_target="$s"; break; fi
|
if [ -z "$ssh_target" ] && mapres="$(bash "$REPO/.claude/skills/unifi-wifi/scripts/gateway-map.sh" lookup "$SITEARG" 2>/dev/null)"; then
|
||||||
done
|
IFS=$'\t' read -r ssh_target ssh_port <<< "$mapres"
|
||||||
|
[ -n "$ssh_target" ] && echo; [ -n "$ssh_target" ] && echo "[INFO] site in gateway map -> auto-selected pfSense cred '$ssh_target'"
|
||||||
fi
|
fi
|
||||||
|
# 4. SITEARG itself a client slug
|
||||||
|
if [ -z "$ssh_target" ]; then case "$SITEARG" in
|
||||||
|
*/*) ;; *) [ -n "$(bash "$VAULT" get-field "clients/$SITEARG/pfsense-firewall" credentials.password 2>/dev/null)" ] && ssh_target="$SITEARG";; esac; fi
|
||||||
if [ -n "$ssh_target" ]; then
|
if [ -n "$ssh_target" ]; then
|
||||||
echo; echo "[INFO] pfSense gateway (SSH backend, target '$ssh_target') -> pfSense gateway audit:"
|
echo; echo "[INFO] pfSense gateway (SSH backend, target '$ssh_target') -> pfSense gateway audit:"
|
||||||
bash "$REPO/.claude/skills/unifi-wifi/scripts/pfsense-ssh.sh" "$ssh_target" audit || true
|
if [ -n "$ssh_port" ] && [ "$ssh_port" != "-" ]; then
|
||||||
|
bash "$REPO/.claude/skills/unifi-wifi/scripts/pfsense-ssh.sh" "$ssh_target" audit --port "$ssh_port" || true
|
||||||
|
else
|
||||||
|
bash "$REPO/.claude/skills/unifi-wifi/scripts/pfsense-ssh.sh" "$ssh_target" audit || true
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
# REST fallback (dormant): only if a pfSense-api cred is vaulted.
|
# REST fallback (dormant): only if a pfSense-api cred is vaulted.
|
||||||
cands=()
|
cands=()
|
||||||
|
|||||||
@@ -86,6 +86,17 @@ if [[ "$SITEARG" =~ ^[0-9a-f]{24}$ ]]; then SITE="$SITEARG"; else
|
|||||||
[ -n "$SITE" ] || { echo "[ERROR] site not found: $SITEARG"; exit 1; }
|
[ -n "$SITE" ] || { echo "[ERROR] site not found: $SITEARG"; exit 1; }
|
||||||
MONGO_CLEAN='grep -viE pq.html|post-quantum|store now|server may need'
|
MONGO_CLEAN='grep -viE pq.html|post-quantum|store now|server may need'
|
||||||
|
|
||||||
|
# ---------- AUTO-SELECT (ROADMAP §E): resolved site is in the gateway map -> pfSense SSH backend ----------
|
||||||
|
# No --pfsense needed: if site-gateways.tsv binds this site_id to a pfSense cred, route the verb there.
|
||||||
|
if mapres="$(bash "$REPO/.claude/skills/unifi-wifi/scripts/gateway-map.sh" lookup "$SITE" 2>/dev/null)"; then
|
||||||
|
IFS=$'\t' read -r mcred mport <<< "$mapres"
|
||||||
|
echo "[INFO] site '$SITEARG' is in the gateway map -> pfSense SSH backend (cred:$mcred) for '$ACT'"
|
||||||
|
args=("$mcred" "$ACT"); [ ${#POS[@]} -gt 0 ] && args+=("${POS[@]}")
|
||||||
|
[ -n "$mport" ] && [ "$mport" != "-" ] && args+=(--port "$mport")
|
||||||
|
[ "$APPLY" = "1" ] && args+=(--apply)
|
||||||
|
exec bash "$REPO/.claude/skills/unifi-wifi/scripts/pfsense-ssh.sh" "${args[@]}"
|
||||||
|
fi
|
||||||
|
|
||||||
# ---------- READ-ONLY listers (Mongo, no cred) ----------
|
# ---------- READ-ONLY listers (Mongo, no cred) ----------
|
||||||
pf_list() {
|
pf_list() {
|
||||||
cat <<JS | bash "$UOS" 2>&1 | grep -viE 'pq.html|post-quantum|store now|server may need'
|
cat <<JS | bash "$UOS" 2>&1 | grep -viE 'pq.html|post-quantum|store now|server may need'
|
||||||
|
|||||||
@@ -96,5 +96,7 @@ if [ -d "$VROOT" ]; then
|
|||||||
else
|
else
|
||||||
echo " (vault not found at $VROOT - set VAULT_ROOT)"
|
echo " (vault not found at $VROOT - set VAULT_ROOT)"
|
||||||
fi
|
fi
|
||||||
echo " Bind a no-UniFi-gateway site to its pfSense cred via --pfsense <slug>, e.g.:"
|
echo " One-off: bind via --pfsense <slug-or-vault-path>, e.g. gw-audit '<site>' --pfsense <slug>."
|
||||||
echo " bash .claude/skills/unifi-wifi/scripts/gw-audit.sh '<UOS site name>' --pfsense <slug>"
|
echo " Persistent AUTO-SELECT (no --pfsense): add a row to references/site-gateways.tsv —"
|
||||||
|
echo " bash .claude/skills/unifi-wifi/scripts/gateway-map.sh suggest # shows unmapped sites + creds"
|
||||||
|
echo " bash .claude/skills/unifi-wifi/scripts/gateway-map.sh list|validate"
|
||||||
|
|||||||
Reference in New Issue
Block a user