feat(multi-site): auto-derive site slug; wire proxy/jump service integrations

Two real gaps found while fixing the Directory's Multi-Site modal:

1. SITE_SLUG was never set anywhere -- site_config.js's own fallback
   ("site-default") was all a fresh master could ever show, since
   nothing in setup.sh/docker-compose.yml passed it a value and
   bootstrap.js never generated one. Derived from CFG_SITE_NAME (same
   source jump-host's default exit node name already uses) with the
   same slugify rule bootstrap.js's own site Resource slug uses,
   formatted to match site_config.js's own "site-default" convention.
   Only a first-run default -- a real join/promote's persisted
   site.json value always wins.

2. PROXY_INTERNAL_URL and JUMP_INTERNAL_URL -- the env vars
   utils/proxy_client.js (no-inbound relay automation) and the new
   utils/jump_client.js (real gateway-mesh count on the modal) read to
   find each service -- were never actually set anywhere in
   docker-compose.yml. Both features existed in sso-manager-node's
   code but were completely unreachable in every real deployment,
   always hitting their "not configured" fallback. Wired both to the
   docker network hostnames.

Also documents how to mint + store the two integration API tokens
those features need (self-service tokens each app already has, not a
new credential type -- same reasoning as the relay automation).
This commit is contained in:
2026-08-10 22:19:21 -04:00
parent 0e59b8dcb4
commit 5ed83a2f59
3 changed files with 35 additions and 0 deletions
+11
View File
@@ -71,8 +71,19 @@ services:
# setup.sh (policy sso-broker) — NOT the root token. # setup.sh (policy sso-broker) — NOT the root token.
- NODE_ENV=production - NODE_ENV=production
- NODE_PORT=3001 - NODE_PORT=3001
# Only a first-run default (site_config.js's envDefaults()) -- a real
# join/promote persists its own value to /config/site.json afterward,
# which always wins. Derived by setup.sh from CFG_SITE_NAME.
- SITE_SLUG=${SITE_SLUG:-}
- LDAP_SERVER_ID=${LDAP_SERVER_ID:-} - LDAP_SERVER_ID=${LDAP_SERVER_ID:-}
- LDAP_REPLICATION_HOSTS=${LDAP_REPLICATION_HOSTS:-} - LDAP_REPLICATION_HOSTS=${LDAP_REPLICATION_HOSTS:-}
# utils/proxy_client.js (no-inbound relay automation) and
# utils/jump_client.js (real mesh-gateway count on the Multi-Site
# modal) both no-op/skip without these -- neither was ever actually
# wired into the compose environment before, so both features were
# unreachable in every real deployment despite existing in code.
- PROXY_INTERNAL_URL=http://proxy:3000
- JUMP_INTERNAL_URL=http://jump-host:3002
- VAULT_ADDR=http://openbao:8200 - VAULT_ADDR=http://openbao:8200
- VAULT_TOKEN=${SSO_VAULT_TOKEN:-} - VAULT_TOKEN=${SSO_VAULT_TOKEN:-}
# Optional upstream HTTP(S) proxy for outbound calls (SMTP, etc.) at # Optional upstream HTTP(S) proxy for outbound calls (SMTP, etc.) at
+14
View File
@@ -78,6 +78,20 @@ CFG_DOMAIN=example.com
#CFG_SPOKE_NO_INBOUND=true #CFG_SPOKE_NO_INBOUND=true
#CFG_SPOKE_PUBLIC_HOST=sso-branch2.master-domain.example.com #CFG_SPOKE_PUBLIC_HOST=sso-branch2.master-domain.example.com
# Two service-to-service integrations the Directory uses (both reuse each
# app's existing self-service API token system -- see MULTI_SITE_SPEC.md's
# "service-to-service auth" note -- not a new credential type each):
# - No-inbound relay automation (above) needs a theta-proxy API token so
# sso-manager can create/update the relay Host route on its own.
# - The Multi-Site modal's real gateway-mesh count needs a jump-host API
# token (minted by a jump-admin user) to read GET /api/mesh/gateways.
# Neither is required for the rest of the stack to work -- both features
# just report "not configured" until you mint a token in each app's own web
# UI (Settings -> API Tokens) and store it in OpenBao, from inside the
# sso-manager container (VAULT_ADDR/VAULT_TOKEN are already set there):
# docker compose exec sso-manager node -e "require('@simpleworkjs/bao-conf').set('integrations/theta-proxy', {token: 'prx_...'})"
# docker compose exec sso-manager node -e "require('@simpleworkjs/bao-conf').set('integrations/theta-jump', {token: 'jmp_...'})"
# ── Optional outbound HTTP(S) proxy ────────────────────────────────────────── # ── Optional outbound HTTP(S) proxy ──────────────────────────────────────────
# For an isolated/offline/corporate-network test host that only reaches the # For an isolated/offline/corporate-network test host that only reaches the
# internet through an upstream HTTP proxy — NOT the theta42 "proxy" app. # internet through an upstream HTTP proxy — NOT the theta42 "proxy" app.
+10
View File
@@ -543,6 +543,16 @@ BAOEOF
CFG_SSO_HOST="${CFG_SSO_HOST:-sso.$CFG_DOMAIN}" CFG_SSO_HOST="${CFG_SSO_HOST:-sso.$CFG_DOMAIN}"
CFG_PROXY_HOST="${CFG_PROXY_HOST:-proxy.$CFG_DOMAIN}" CFG_PROXY_HOST="${CFG_PROXY_HOST:-proxy.$CFG_DOMAIN}"
CFG_SITE_NAME="${CFG_SITE_NAME:-local}" CFG_SITE_NAME="${CFG_SITE_NAME:-local}"
# Multi-site identity (site_config.js's `siteSlug`, shown on the Directory's
# Multi-Site modal) -- without this it's never set anywhere and every fresh
# master shows the module's own literal fallback, "site-default", forever.
# Derived from CFG_SITE_NAME with the same slugify rule bootstrap.js uses
# for the site Resource's own slug (site_$(slugify), underscore prefix --
# this is hyphenated to match site_config.js's own "site-default" format).
# site.json overrides this after first bring-up (join/promote write real
# values there), so this only ever matters for a fresh install.
SITE_SLUG="site-$(echo "$CFG_SITE_NAME" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+|-+$//g')"
export SITE_SLUG
CFG_ORG="${CFG_ORG:-Theta Directory}" CFG_ORG="${CFG_ORG:-Theta Directory}"
CFG_ADMIN_UID="${CFG_ADMIN_UID:-admin}" CFG_ADMIN_UID="${CFG_ADMIN_UID:-admin}"
CFG_ADMIN_EMAIL="${CFG_ADMIN_EMAIL:-admin@$CFG_PROXY_HOST}" CFG_ADMIN_EMAIL="${CFG_ADMIN_EMAIL:-admin@$CFG_PROXY_HOST}"