feat(multi-site): wire no-inbound relay registration into the real bootstrap flow

sso-manager-node/jump-host already had the relay-automation mechanism
(noInbound/meshIp/publicHost -> theta-proxy route via proxy_client.js,
GET /api/mesh/self on jump-host) but nothing in the actual operator
bring-up flow could ever reach it -- setup.sh, bootstrap/site-join.js,
and setup.env.example had zero wiring for it.

Add bootstrap/site-relay-register.js: reads this spoke's own role from
/config/site.json, logs into the local jump-host as its bootstrap
admin to discover its mesh IP, and registers it with the master. Mesh
peering itself stays a manual step (mint/paste a join token, same
pattern as the site join key), so this runs on every setup.sh
invocation via CFG_SPOKE_NO_INBOUND/CFG_SPOKE_PUBLIC_HOST and is a
no-op ("not meshed yet") until an operator has actually meshed the two
jump-hosts.

Also updates MULTI_SITE_SPEC.md's status table/TODO and the published
mesh.md docs page, which still described this as "designed but not
automated" after the API-level work had already shipped.
This commit is contained in:
2026-08-10 21:09:10 -04:00
parent d8d811b0ab
commit 744c85f4bf
5 changed files with 176 additions and 17 deletions
+19
View File
@@ -1279,6 +1279,25 @@ NODEEOF
)
echo "$JUMP_HOSTS_OUT" | sed 's/^/[setup] /'
# ── 7b2. No-inbound relay registration (first-run *and* every re-run) ─────────
# CFG_SPOKE_NO_INBOUND: this site has no public IP, so the master relays to it
# over the gateway-to-gateway WireGuard mesh (MULTI_SITE_SPEC.md §5.2). The
# mesh peering itself is a manual, out-of-band step on both jump-hosts (mint a
# join token on the master's jump-host, paste it into this site's jump-host
# "Join a mesh" UI action) -- it can't run unattended here, and it commonly
# happens AFTER this first setup.sh run finishes. So this step runs on every
# invocation, not just first-run: it discovers this jump-host's mesh IP and
# (re-)registers it with the master, and is a no-op until meshing is done.
if [[ "${CFG_SPOKE_NO_INBOUND:-false}" == "true" ]]; then
if [[ -z "${CFG_SPOKE_PUBLIC_HOST:-}" ]]; then
warn "CFG_SPOKE_NO_INBOUND=true but CFG_SPOKE_PUBLIC_HOST is unset — skipping relay registration."
else
info "Checking no-inbound relay registration (CFG_SPOKE_PUBLIC_HOST=${CFG_SPOKE_PUBLIC_HOST})..."
"${COMPOSE[@]}" exec -T sso-manager node /bootstrap/site-relay-register.js \
"https://$CFG_SSO_HOST" "$CFG_SPOKE_PUBLIC_HOST" || warn "relay registration did not complete — check: ${COMPOSE[*]} exec sso-manager node /bootstrap/site-relay-register.js https://$CFG_SSO_HOST $CFG_SPOKE_PUBLIC_HOST"
fi
fi
# ── 7c. Install theta-agent on the host ──────────────────────────────────────
# Controlled by CFG_THETA_AGENT_ENABLE (default: 1 = enabled)
CFG_THETA_AGENT_ENABLE="${CFG_THETA_AGENT_ENABLE:-1}"