103 lines
6.5 KiB
Bash
103 lines
6.5 KiB
Bash
#!/usr/bin/env bash
|
|
# sites.sh — fleet overview of every UniFi site on the UOS controller + per-site skill readiness.
|
|
# The entry point for "what clients can I run this on". Controller-side scripts (audit-site,
|
|
# live-stats, model-rank, optimize-radios, apply-radio) work on ANY site with ZERO per-client setup
|
|
# (they use the shared controller creds). The AP-side collectors (neighbor-collect, survey-collect,
|
|
# dfs-check, watch-ap) additionally need that client's AP device-auth cred vaulted + L3 reach (VPN).
|
|
#
|
|
# Usage: bash .claude/skills/unifi-wifi/scripts/sites.sh [--ap-cred-glob 'clients/*/unifi-ap-ssh']
|
|
set -uo pipefail
|
|
REPO="$(git rev-parse --show-toplevel 2>/dev/null || echo .)"
|
|
UOS="$REPO/.claude/scripts/uos-mongo.sh"
|
|
# Mandatory skill error logging (skill-creator rule): log GENUINE functional failures only.
|
|
# (Mongo read + vault-listing overview; no external call with an uninstrumented failure branch -> helper for consistency.)
|
|
logerr(){ bash "$REPO/.claude/scripts/log-skill-error.sh" "unifi-wifi/sites" "$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"
|
|
|
|
echo "[INFO] UniFi sites on the UOS controller (controller-side scripts work on ALL of these now):"
|
|
cat <<'JS' | bash "$UOS" 2>&1 | grep -viE 'WARNING|post-quantum|store now|upgraded|openssh'
|
|
var sites={};
|
|
db.site.find({},{name:1,desc:1}).forEach(function(s){ sites[s._id.str]={name:(s.desc||s.name),short:s.name,ap:0,sw:0,gw:0}; });
|
|
db.device.find({},{site_id:1,type:1}).forEach(function(d){ var s=sites[d.site_id]; if(!s)return;
|
|
if(d.type=='uap')s.ap++; else if(d.type=='usw')s.sw++; else if(d.type=='ugw'||d.type=='uxg'||d.type=='udm')s.gw++; });
|
|
function pad(v,n){var s=String(v);while(s.length<n)s=' '+s;return s;} // old mongo JS has no padStart
|
|
var rows=Object.keys(sites).map(function(id){var s=sites[id];return [id,s];}).sort(function(a,b){return b[1].ap-a[1].ap;});
|
|
print("");
|
|
print("site_id APs SW GW name");
|
|
print("------------------------- --- -- -- ----");
|
|
rows.forEach(function(r){ var s=r[1];
|
|
print(r[0]+" "+pad(s.ap,3)+" "+pad(s.sw,2)+" "+pad(s.gw,2)+" "+s.name);
|
|
});
|
|
print("\n# "+rows.length+" sites. Run any controller-side script with the site name/desc or id, e.g.:");
|
|
print("# bash .claude/skills/unifi-wifi/scripts/audit-site.sh <name|id>");
|
|
JS
|
|
|
|
# AP-side readiness: which clients have an AP device-auth cred vaulted (clients/<x>/unifi-ap-ssh)
|
|
echo ""
|
|
echo "[INFO] AP-side collectors (neighbor/survey/dfs/watch-ap) need a per-client AP device-auth cred."
|
|
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)"
|
|
else
|
|
echo " (vault not found at $VROOT - set VAULT_ROOT)"
|
|
fi
|
|
cat <<'EOF'
|
|
To enable AP-side collectors for a new client:
|
|
1) get that site's Device Authentication user/pass (UniFi OS -> Settings -> System ->
|
|
Device Authentication, per site) + L3 reach to its AP mgmt VLAN (site VPN/route).
|
|
2) bash .claude/skills/vault/scripts/vault-helper.sh new clients/<slug>/unifi-ap-ssh \
|
|
--kind generic --name '<Client> UniFi AP device-auth SSH' --tag unifi \
|
|
--set username=<u> --set password=<pw>
|
|
3) pass it as the script's vault-path arg, e.g.:
|
|
bash .../neighbor-collect.sh <site> clients/<slug>/unifi-ap-ssh
|
|
EOF
|
|
|
|
# ---------- Site -> gateway map (which backend gw-audit/gw-control uses per site) ----------
|
|
# A site with a UniFi gateway (ugw/uxg/udm/ucg) is driven via the UniFi REST path; a site with
|
|
# NO UniFi gateway (gw=0) is a pfSense/third-party gateway and is driven via the pfSense SSH backend
|
|
# (pfsense-ssh.sh) when a clients/<slug>/pfsense-firewall cred is vaulted. UOS site name != client
|
|
# slug, so the dispatch takes --pfsense <slug> (or the slug as the site arg) to bind them.
|
|
echo ""
|
|
echo "[INFO] Site -> gateway map (gw-audit/gw-control backend per site):"
|
|
cat <<'JS' | bash "$UOS" 2>&1 | grep -viE 'WARNING|post-quantum|store now|upgraded|openssh'
|
|
var sites={};
|
|
db.site.find({},{name:1,desc:1}).forEach(function(s){ sites[s._id.str]={name:(s.desc||s.name),gw:0,model:''}; });
|
|
db.device.find({type:{$in:['ugw','uxg','udm','ucg']}},{site_id:1,model:1,type:1}).forEach(function(d){
|
|
var s=sites[d.site_id]; if(!s)return; s.gw++; if(!s.model)s.model=(d.model||d.type); });
|
|
var uni=[],third=[];
|
|
Object.keys(sites).forEach(function(id){var s=sites[id]; (s.gw>0?uni:third).push(s);});
|
|
function byname(a,b){return a.name<b.name?-1:(a.name>b.name?1:0);}
|
|
print("");
|
|
print(" UniFi-gateway sites ("+uni.length+") -> UniFi REST backend (no extra setup):");
|
|
uni.sort(byname).forEach(function(s){ print(" [UniFi "+(s.model||"gw")+"] "+s.name); });
|
|
print("");
|
|
print(" No UniFi gateway ("+third.length+") -> pfSense/third-party -> SSH backend (needs a vaulted cred + --pfsense <slug>):");
|
|
third.sort(byname).forEach(function(s){ print(" [3rd-party?] "+s.name); });
|
|
JS
|
|
|
|
# pfSense SSH creds that are vaulted -> the SSH backend can reach these now (with resolved host:port)
|
|
echo ""
|
|
echo " pfSense SSH creds vaulted (clients/*/pfsense-firewall) -> SSH backend ready:"
|
|
if [ -d "$VROOT" ]; then
|
|
any=0
|
|
while IFS= read -r f; do
|
|
[ -n "$f" ] || continue
|
|
slug=$(printf '%s' "$f" | sed -E 's#.*/clients/([^/]+)/.*#\1#')
|
|
host=$(bash "$REPO/.claude/scripts/vault.sh" get-field "clients/$slug/pfsense-firewall" host 2>/dev/null || true)
|
|
port=$(bash "$REPO/.claude/scripts/vault.sh" get-field "clients/$slug/pfsense-firewall" port 2>/dev/null || true)
|
|
case "$port" in ""|null) port=22;; esac
|
|
case "$host" in ""|null) host="(host?)";; esac
|
|
echo " [ready] clients/$slug/pfsense-firewall ($host:$port)"
|
|
any=1
|
|
done < <(find "$VROOT/clients" -name 'pfsense-firewall.sops.yaml' 2>/dev/null | sort)
|
|
[ "$any" = 1 ] || echo " (none yet - vault clients/<slug>/pfsense-firewall to enable the SSH backend for a site)"
|
|
else
|
|
echo " (vault not found at $VROOT - set VAULT_ROOT)"
|
|
fi
|
|
echo " One-off: bind via --pfsense <slug-or-vault-path>, e.g. gw-audit '<site>' --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"
|