96 lines
5.2 KiB
Bash
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
|