feat(multi-site): auto-configure OpenLDAP replication on join/every run

Removes LDAP_SERVER_ID/LDAP_REPLICATION_HOSTS as vars an operator has
to hand-set and keep in sync across every site. bootstrap/
site-ldap-register.js (new) asks sso-manager-node's new
GET /api/site/ldap-peers (spoke) or GET /directory-admin/
ldap-replication-config (master) for this node's assigned ServerID +
current peer list, persists it to /config/ldap-replication.env, and
restarts sso-manager only when the computed config actually changed
(OpenLDAP's static slapd.conf is only read at process start). Runs on
every setup.sh invocation -- both master (peer list grows as spokes
join) and spoke.

CFG_LDAP_MMR_MANUAL=true skips the automatic step entirely, for a
topology outside this theta-suite cluster the script can't derive on
its own -- without this escape hatch, an operator's hand-set
LDAP_SERVER_ID/LDAP_REPLICATION_HOSTS would get silently overwritten
on the next run, since every fresh install starts as a master (the
automatic step always runs by default).

Bumps sso-manager-node to pick up the new endpoints + SiteSpoke.ldapServerId.
This commit is contained in:
2026-08-10 23:02:01 -04:00
parent 20e9c1dfbe
commit da60310834
5 changed files with 197 additions and 8 deletions
+43
View File
@@ -1060,6 +1060,15 @@ info "Starting bao-renewer (service-token renewal sidecar)..."
SSO_GIT_COMMIT="$(git -C sso-manager-node rev-parse --short HEAD 2>/dev/null || echo unknown)"
export SSO_GIT_COMMIT
env_upsert SSO_GIT_COMMIT "$SSO_GIT_COMMIT"
# OpenLDAP multi-master replication (docs/replication.md, auto-configured --
# see step 7e below and bootstrap/site-ldap-register.js): pick up whatever
# LDAP_SERVER_ID/LDAP_REPLICATION_HOSTS a PRIOR run already computed, so a
# restart doesn't silently drop back to standalone (no LDAP_SERVER_ID env at
# all). A truly fresh install has no file yet -- that's fine, it just starts
# standalone until step 7e computes and applies real values. Skipped under
# CFG_LDAP_MMR_MANUAL=true so a manually hand-set LDAP_SERVER_ID/
# LDAP_REPLICATION_HOSTS in setup.env isn't clobbered by a stale auto file.
[[ "${CFG_LDAP_MMR_MANUAL:-false}" != "true" && -f "$CONFIG_DIR/ldap-replication.env" ]] && parse_kv_file "$CONFIG_DIR/ldap-replication.env"
info "Building + starting sso-manager (first run builds the image; this takes a while)..."
"${COMPOSE[@]}" up -d --build sso-manager
@@ -1322,6 +1331,40 @@ if [[ "${CFG_SPOKE_NO_INBOUND:-false}" == "true" ]]; then
fi
fi
# ── 7e. OpenLDAP multi-master replication auto-config (every run) ────────────
# docs/replication.md: the master assigns each spoke a unique LDAP_SERVER_ID
# and derives every site's LDAP URL automatically (bootstrap/
# site-ldap-register.js) instead of an operator hand-maintaining
# LDAP_SERVER_ID/LDAP_REPLICATION_HOSTS. Runs on every invocation -- both
# master (its peer list grows as spokes join) and spoke -- and restarts
# sso-manager only when the computed config actually changed, since
# OpenLDAP's static slapd.conf is only read at process start.
#
# CFG_LDAP_MMR_MANUAL=true skips this entirely -- every fresh install starts
# as a master, so without this escape hatch an operator's own hand-set
# LDAP_SERVER_ID/LDAP_REPLICATION_HOSTS (a topology outside this theta-suite
# cluster this script can't derive) would get silently overwritten.
if [[ "${CFG_LDAP_MMR_MANUAL:-false}" == "true" ]]; then
info "CFG_LDAP_MMR_MANUAL=true — skipping automatic LDAP replication config."
LDAP_REG_OUT=""
else
LDAP_REG_OUT=$("${COMPOSE[@]}" exec -T sso-manager node /bootstrap/site-ldap-register.js "https://$CFG_SSO_HOST" 2>&1) || warn "LDAP replication config check failed — check: ${COMPOSE[*]} exec sso-manager node /bootstrap/site-ldap-register.js https://$CFG_SSO_HOST"
echo "$LDAP_REG_OUT" | sed 's/^/[setup] /'
fi
if echo "$LDAP_REG_OUT" | grep -q '^LDAP_CONFIG_CHANGED=yes'; then
info "LDAP replication config changed — restarting sso-manager to apply it..."
[[ -f "$CONFIG_DIR/ldap-replication.env" ]] && parse_kv_file "$CONFIG_DIR/ldap-replication.env"
"${COMPOSE[@]}" up -d --force-recreate sso-manager
info "Waiting for sso-manager to be healthy again..."
for i in $(seq 1 60); do
if docker exec sso-manager wget -q -O- http://localhost:3001/health >/dev/null 2>&1; then
info "sso-manager is healthy."; break
fi
if (( i == 60 )); then warn "sso-manager did not become healthy in 120s after the LDAP config restart. Check: ${COMPOSE[*]} logs sso-manager"; break; fi
sleep 2
done
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}"