sync: auto-sync from HOWARD-HOME at 2026-06-21 13:04:37

Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-21 13:04:37
This commit is contained in:
2026-06-21 13:05:31 -07:00
parent 861893dc33
commit 72bf65ef2f
7 changed files with 60 additions and 29 deletions

View File

@@ -48,6 +48,10 @@ path is Cascades — override with the script's vault-path arg per client.
- **[WORKING] pfSense gateway compatibility layer via SSH** — `scripts/pfsense-ssh.sh <slug> <verb>`.
DECISION (Mike 2026-06-16): **no RESTAPI package needed** — VPN + SSH shell reads the same data and makes
changes. Cred = `clients/<slug>/pfsense-firewall` (host + admin user/pass), system OpenSSH via askpass.
**Cred path (Mike 2026-06-21, option A):** the 1st arg is a **full vault path** if it contains `/`
(any infra-vaulted device, e.g. `pfsense-ssh.sh infrastructure/pfsense-firewall audit`), else a client
slug -> `clients/<slug>/pfsense-firewall`. A path that resolves to no cred fails loud (`[ERROR] no cred
at vault:<path>`). gw-audit/gw-control's `--pfsense` inherits the same convention.
SSH port: `--port N` flag > optional vault `port`/`credentials.port` field > default 22 (e.g. the ACG
office box on 2248 — store `port: 2248` in its vault entry). Validated live on Cascades (pfSense Plus 25.07).
- **Reads (no gate):** `audit` (WAN/DHCP/states/DNS/NIC health), `dhcp` (pool pressure), `pf-list`

View File

@@ -140,6 +140,11 @@ Writes (DRY-RUN default; `--apply` to commit — `write_config` + `filter_config
config history. Cred = `clients/<slug>/pfsense-firewall` (host + admin user/pass), system OpenSSH via askpass.
- [x] **SSH port configurable** (2026-06-21): `--port N` > vault `port`/`credentials.port` field > default 22.
Unblocks non-standard-port boxes like the ACG office gateway (2248) — store `port: 2248` in its vault entry.
- [x] **Cred-path convention = option A** (Mike 2026-06-21): the slug arg is a FULL vault path when it
contains `/` (any infra-vaulted device, e.g. `infrastructure/pfsense-firewall`), else `clients/<slug>/pfsense-firewall`.
No cred duplication; explicit (no implicit fallback ladder). Resolves loud (`[ERROR] no cred at <path>`) on a
bad path. `gw-audit`/`gw-control` `--pfsense` inherits it. So the ACG office box is now reachable:
`gw-audit '<site>' --pfsense infrastructure/pfsense-firewall` (+ `port: 2248` in that entry).
- [x] **Dispatch rewired:** `gw-control.sh` / `gw-audit.sh` now prefer the SSH backend (keyed on
`clients/<slug>/pfsense-firewall`) and route the same verbs to it; dispatch runs BEFORE UOS site resolution so a
pfSense-only slug works. REST path is the dormant fallback.
@@ -154,8 +159,8 @@ 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
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).
- [ ] **Auto-select from the map** (driver picks the pfSense cred for a site WITHOUT `--pfsense`): needs a
UOS-site-name→slug binding + the cred-path convention (PARKED on Mike's clients/ vs infrastructure/ answer).
- [ ] **Auto-select from the map** (driver picks the pfSense cred for a site WITHOUT `--pfsense`): cred-path
convention now settled (option A, above) — remaining blocker is just a persisted UOS-site-name→cred binding.
- [ ] **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.

View File

@@ -103,16 +103,22 @@ PY
# pfSense gateway/WAN/DHCP audit via the backend so one `gw-audit <site>` covers either gateway vendor.
NGW="$(cat "$TMP/ngw" 2>/dev/null || echo 1)"
if [ "$NGW" = "0" ]; then
# PREFERRED: SSH backend (Mike's 2026-06-16 decision), keyed on clients/<slug>/pfsense-firewall.
ssh_slug=""
for s in "$PFARG" "$SITEARG"; do
[ -n "$s" ] || continue
case "$s" in */*) continue;; esac
if [ -n "$(bash "$VAULT" get-field "clients/$s/pfsense-firewall" credentials.password 2>/dev/null)" ]; then ssh_slug="$s"; break; fi
done
if [ -n "$ssh_slug" ]; then
echo; echo "[INFO] pfSense gateway (SSH cred vault:clients/$ssh_slug/pfsense-firewall) -> pfSense gateway audit:"
bash "$REPO/.claude/skills/unifi-wifi/scripts/pfsense-ssh.sh" "$ssh_slug" audit || true
# 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.
ssh_target=""
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 ;;
esac
if [ -z "$ssh_target" ]; then
for s in "$PFARG" "$SITEARG"; do
[ -n "$s" ] || continue
case "$s" in */*) continue;; esac
if [ -n "$(bash "$VAULT" get-field "clients/$s/pfsense-firewall" credentials.password 2>/dev/null)" ]; then ssh_target="$s"; break; fi
done
fi
if [ -n "$ssh_target" ]; then
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
else
# REST fallback (dormant): only if a pfSense-api cred is vaulted.
cands=()

View File

@@ -47,15 +47,22 @@ done
# so the SAME verb (pf-*/fw-*/block-ips) routes to a pfSense backend here. PREFERRED = SSH backend
# (Mike's 2026-06-16 decision: no REST package needed), keyed on a vaulted clients/<slug>/pfsense-firewall
# cred. The REST backend (pfsense-backend.sh, clients/<slug>/pfsense-api) is kept only as a dormant fallback.
ssh_slug=""
for s in "$PFARG" "$SITEARG"; do
[ -n "$s" ] || continue
case "$s" in */*) continue;; esac # SSH backend takes a slug, not a vault path
if [ -n "$(bash "$VAULT" get-field "clients/$s/pfsense-firewall" credentials.password 2>/dev/null)" ]; then ssh_slug="$s"; break; fi
done
if [ -n "$ssh_slug" ]; then
echo "[INFO] pfSense gateway (SSH cred vault:clients/$ssh_slug/pfsense-firewall) -> dispatching '$ACT' to pfsense-ssh.sh"
args=("$ssh_slug" "$ACT"); [ ${#POS[@]} -gt 0 ] && args+=("${POS[@]}")
ssh_target=""
# option A (Mike 2026-06-21): --pfsense may be a FULL vault path (contains '/') for an infra-vaulted
# device, else a client slug -> clients/<slug>/pfsense-firewall. pfsense-ssh.sh resolves both.
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 ;;
esac
if [ -z "$ssh_target" ]; then
for s in "$PFARG" "$SITEARG"; do
[ -n "$s" ] || continue
case "$s" in */*) continue;; esac
if [ -n "$(bash "$VAULT" get-field "clients/$s/pfsense-firewall" credentials.password 2>/dev/null)" ]; then ssh_target="$s"; break; fi
done
fi
if [ -n "$ssh_target" ]; then
echo "[INFO] pfSense gateway (SSH backend, target '$ssh_target') -> dispatching '$ACT' to pfsense-ssh.sh"
args=("$ssh_target" "$ACT"); [ ${#POS[@]} -gt 0 ] && args+=("${POS[@]}")
[ "$APPLY" = "1" ] && args+=(--apply)
exec bash "$REPO/.claude/skills/unifi-wifi/scripts/pfsense-ssh.sh" "${args[@]}"
fi

View File

@@ -37,7 +37,7 @@ GWC_PHP="$HERE/pfsense-gwc.php"
# bad args, site not found). Soft-fails so it never breaks the caller.
SKILL_ID="unifi-wifi/pfsense-ssh"
logerr(){ bash "$REPO/.claude/scripts/log-skill-error.sh" "$SKILL_ID" "$1" --context "${2:-}" >/dev/null 2>&1 || true; }
SLUG="${1:?usage: pfsense-ssh.sh <slug> <action> [args] [--apply]}"
SLUG="${1:?usage: pfsense-ssh.sh <slug-or-vault-path> <action> [args] [--apply]}"
ACT="${2:?action: audit|dhcp|pf-list|fw-list|pf-*|fw-*|block-ips|unblock|showblock|run|shell}"; shift 2 || true
APPLY=0; BLOCK_IF="wan"; PORT=""; POS=()
while [ $# -gt 0 ]; do case "$1" in
@@ -46,7 +46,9 @@ while [ $# -gt 0 ]; do case "$1" in
--port) PORT="${2:?--port needs a number}"; shift 2;;
*) POS+=("$1"); shift;;
esac; done
VP="clients/$SLUG/pfsense-firewall"
# Cred path (Mike 2026-06-21, option A): arg containing '/' = a full vault path (any infra/-vaulted
# device, e.g. infrastructure/pfsense-firewall); else clients/<slug>/pfsense-firewall.
case "$SLUG" in */*) VP="$SLUG" ;; *) VP="clients/$SLUG/pfsense-firewall" ;; esac
HOST="$(bash "$VAULT" get-field "$VP" host 2>/dev/null || bash "$VAULT" get-field "$VP" credentials.host 2>/dev/null || true)"
U="$(bash "$VAULT" get-field "$VP" credentials.username 2>/dev/null || true)"
PP="$(bash "$VAULT" get-field "$VP" credentials.password 2>/dev/null || true)"; export PP
@@ -59,8 +61,12 @@ if [ -z "$PORT" ]; then
done
fi
PORT="${PORT:-22}"
# Fail loud (Mike's add): a wholly-empty resolve = path typo/missing -> clear [ERROR], not a confusing
# SSH failure later. A partial resolve = the entry exists but is missing a field.
if [ -z "$HOST" ] && [ -z "$U" ] && [ -z "$PP" ]; then
echo "[ERROR] no cred at vault:$VP (path not found or empty — check the slug/vault-path)"; exit 2; fi
if [ -z "$HOST" ] || [ -z "$U" ] || [ -z "$PP" ]; then
echo "[BLOCKED] need host + admin creds at vault:$VP (fields: host, credentials.username, credentials.password)"; exit 2; fi
echo "[BLOCKED] incomplete cred at vault:$VP (need fields: host, credentials.username, credentials.password)"; exit 2; fi
if [ "$ACT" = "shell" ]; then echo "ssh -p ${PORT} ${U}@${HOST} # password in vault:$VP"; exit 0; fi
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT

View File

@@ -17,6 +17,8 @@ Categories (the `[type]` tag): _(none)_ = skill/command execution failure ·
<!-- Append entries below this line -->
2026-06-21 | Howard-Home | unifi-wifi/pfsense-ssh | SSH connect/auth failed (rc=255) [ctx: host=192.168.0.1:22 slug=clients/cascades-tucson/pfsense-firewall act=showblock]
2026-06-21 | Howard-Home | unifi-wifi/pfsense-ssh | SSH connect/auth failed (rc=255) [ctx: host=192.168.0.1:9999 slug=cascades-tucson act=showblock]
2026-06-21 | Howard-Home | unifi-wifi/pfsense-ssh | SSH connect/auth failed (rc=255) [ctx: host=192.168.0.1:22 slug=cascades-tucson act=showblock]

View File

@@ -99,11 +99,12 @@ The REST backend (`pfsense-backend.sh`, `clients/<slug>/pfsense-api`) is a dorma
`pfsense-firewall` cred) and run the dispatch BEFORE UOS site resolution, so a pfSense-only client
slug works without a matching UOS site name (pass `--pfsense <slug>` if the names differ).
**THIS office box:** listens on SSH **port 2248** (not 22). The skill supports non-default ports as
of 2026-06-21 — pass `--port 2248`, or (preferred) store `port: 2248` in the box's vault entry and
it's automatic. Cred for it is vaulted at `infrastructure/pfsense-firewall` (verify), but the SSH
backend expects the cred at `clients/<slug>/pfsense-firewall`, so an `infrastructure/`-path cred
would need a slug alias or a small path tweak before `pfsense-ssh.sh` can read it (verify).
**THIS office box:** SSH **port 2248** (not 22). **Fully reachable by the skill as of 2026-06-21.**
Cred vaulted at `infrastructure/pfsense-firewall` (verify) — pass it as a **full vault path**
(option A, Mike 2026-06-21: a 1st arg containing `/` is a vault path, not a client slug), e.g.
`pfsense-ssh.sh infrastructure/pfsense-firewall audit` or
`gw-audit '<site>' --pfsense infrastructure/pfsense-firewall`. Add `port: 2248` to that vault entry
so the non-standard port is automatic (or pass `--port 2248`). No cred duplication needed.
**pfSense PHP gotchas** (baked into the scripts; carry forward to any new helper):
- Bootstrap with `require_once("config.inc")` ONLY — re-requiring util/functions/filter → "cannot