Files
claudetools/.claude/skills/unifi-wifi/scripts/gateway-map.sh
Howard Enos eeaaa566d7 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
2026-06-21 13:19:10 -07:00

96 lines
5.2 KiB
Bash

#!/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