Commit Graph

405 Commits

Author SHA1 Message Date
wmantly 33dfb682f4 Merge pull request #215 from theta42/feat-ldap-mmr-auto-config
feat(multi-site): auto-configure OpenLDAP replication on join/every run
2026-08-10 20:03:04 -07:00
wmantly da60310834 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.
2026-08-10 23:02:01 -04:00
wmantly 20e9c1dfbe Merge pull request #214 from theta42/release-v2.6.0
CI/CD / build-theta-agent (push) Successful in 41s
CI/CD / docker-push (push) Has been skipped
release(v2.6.0): agent server_url/update fixes, real gateway count, dedupe groups
v2.6.0
2026-08-10 19:40:16 -07:00
wmantly 98b2f9f389 release(v2.6.0): agent server_url/update fixes, real gateway count, dedupe groups 2026-08-10 22:39:16 -04:00
wmantly 90a1d19254 Merge pull request #213 from theta42/feat-site-slug-auto
CI/CD / build-theta-agent (push) Successful in 43s
CI/CD / docker-push (push) Has been skipped
feat(multi-site): auto-derive site slug; wire proxy/jump service integrations
2026-08-10 19:32:52 -07:00
wmantly 55d6f0b936 Merge remote-tracking branch 'origin/master' into feat-site-slug-auto 2026-08-10 22:30:17 -04:00
wmantly ba2e905155 Merge pull request #212 from theta42/fix-agent-server-url-and-stale-binary
fix(agent): server_url wiring + install from latest release instead of a stale committed binary
2026-08-10 19:29:40 -07:00
wmantly 455450db1f feat(multi-site): spoke.env.example + CFG_PUBLIC_DOMAIN
A dedicated spoke.env for the join-a-cluster vars (CFG_MASTER_DIRECTORY_URL/
_JOIN_KEY, CFG_SPOKE_NO_INBOUND/_PUBLIC_HOST, CFG_PUBLIC_DOMAIN), split out
of setup.env purely for clarity -- setup.env still has every option and
keeps working as a single file if that's preferred. setup.sh reads both
(setup.env first, spoke.env layered on top so its values win), same
first-run-only rule as setup.env already had.

Also adds CFG_PUBLIC_DOMAIN (documented in MULTI_SITE_SPEC.md §4 but never
actually wired into setup.sh): an inbound spoke/standalone site's own public
web domain, independent of CFG_DOMAIN (the shared LDAP identity namespace,
which must stay identical across every site). Unset behaves exactly as
before -- hostnames derive from CFG_DOMAIN like any standalone install.
2026-08-10 22:22:54 -04:00
wmantly 5ed83a2f59 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).
2026-08-10 22:19:21 -04:00
wmantly ca812bed8e fix(agent): set server_url in agent.yml; install from latest release, not a stale committed binary
Two real bugs found on a live deployment:

1. setup.sh's theta-agent install step sed'd in join_key but never
   touched server_url, so /etc/theta42/agent.yml kept
   agent.yml.example's literal "https://sso.example.com" placeholder
   forever. Fixed for both first install and an already-installed
   agent.yml (self-heals server_url only, never touches
   join_key/auth_token, which may since have been rewritten by the
   agent itself with real issued credentials).

2. `theta-agent update` 404'd downloading
   https://sso.../resources/theta-agent/theta-agent-linux-amd64 --
   that route never existed server-side (only
   /resources/theta-agent/install.sh is static-served); self-update
   itself was already fixed upstream to pull from GitHub Releases, but
   setup.sh was still installing the binary committed in the
   theta-agent submodule checkout, which predated that fix and could
   therefore never self-update out of the bug. Switched setup.sh to
   download the current release binary from GitHub instead (matching
   theta-agent's own install.sh), and bumped the submodule to
   theta-agent's latest commit, which removes the stale committed
   binaries entirely -- this exact "stale committed binary" bug class
   has bitten this repo at least twice before (see theta-agent's
   CHANGELOG v1.5.0 entry).
2026-08-10 22:00:08 -04:00
wmantly 0e59b8dcb4 Merge pull request #211 from theta42/bump-jump-host-v2.1.1
CI/CD / docker-push (push) Failing after 14s
CI/CD / build-theta-agent (push) Successful in 40s
release(v2.5.0): no-inbound relay bootstrap wiring + real mesh-registration fixes
v2.5.0
2026-08-10 18:36:06 -07:00
wmantly 2d862f93c9 release(v2.5.0): no-inbound relay bootstrap wiring + real mesh-registration fixes
Rolls up sso-manager-node v2.5.0 and jump-host v2.1.1.
2026-08-10 21:35:04 -04:00
wmantly e0bdc0df2e Merge pull request #210 from theta42/fix-jump-login-field-name
CI/CD / build-theta-agent (push) Successful in 41s
CI/CD / docker-push (push) Has been skipped
fix(bootstrap): site-relay-register.js used the wrong jump-host login field
2026-08-10 18:21:31 -07:00
wmantly 577c264a6a fix(bootstrap): site-relay-register.js used the wrong login field for jump-host
jump-host's local admin login (via @simpleworkjs/oidc-client's shared
router) expects `username`, not `uid` -- sso-manager-node's own
/api/auth/login (used by site-join.js) is the one that expects `uid`.
Caught live: the script's login call to jump-host silently 401'd with
`uid`. Confirmed against a real jump-host container that `username`
succeeds.
2026-08-10 21:20:33 -04:00
wmantly 4f09354e32 Merge pull request #209 from theta42/feat-no-inbound-relay-bootstrap
CI/CD / build-theta-agent (push) Successful in 42s
CI/CD / docker-push (push) Has been skipped
feat(multi-site): wire no-inbound relay registration into the bootstrap flow
2026-08-10 18:17:14 -07:00
wmantly 744c85f4bf 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.
2026-08-10 21:09:10 -04:00
wmantly d8d811b0ab Merge pull request #208 from theta42/release-v2.4.0
release(v2.4.0): theta-agent v2.2.0 - Windows local-discovery + route pinning
2026-08-10 17:51:16 -07:00
wmantly 42ec5aa208 release(v2.4.0): theta-agent v2.2.0 - Windows local-discovery + route pinning
Rolls up theta-agent v2.2.0: Windows hosts override (CRLF-aware, ipconfig
/flushdns), /32 host-route pinning so the WireGuard tunnel can't swallow the
direct LAN path, and a prompt WS reconnect on apply/revert. Marks Windows
local-discovery shipped in MULTI_SITE_SPEC.md; macOS remains the one unbuilt
piece (in progress on a macOS VM).
2026-08-10 17:49:46 -07:00
wmantly 734c62ac83 Merge pull request #207 from theta42/docs-todo-reorder
CI/CD / build-theta-agent (push) Successful in 44s
CI/CD / docker-push (push) Has been skipped
docs(multi-site): reorder TODO by dependency, note mDNS Windows handoff
2026-08-10 17:41:39 -07:00
wmantly 1d85aa81f3 docs(multi-site): reorder TODO by real dependency, note mDNS Windows handoff
Service-to-service auth is a prerequisite for both cross-component
routing and no-inbound relay automation (both need a real credential
between sso-manager-node and theta-proxy/theta-gateway) -- reordered so
that's not buried as item 5. Also notes that Windows/macOS mDNS is being
built by a separate session rather than silently dropping it with no
explanation.
2026-08-10 20:12:25 -04:00
wmantly 47f7f976ab Merge pull request #206 from theta42/docs-multi-site-website
CI/CD / build-theta-agent (push) Successful in 44s
CI/CD / docker-push (push) Has been skipped
docs(site): publish multi-site + gateway mesh to the docs website
2026-08-10 16:57:09 -07:00
wmantly ace7b441c2 docs(site): publish multi-site + gateway mesh to the actual docs website
Everything shipped this pass (live replication, master/spoke join,
gateway-to-gateway WireGuard mesh) had real spec docs in the repo
(docs/MULTI_SITE_SPEC.md, sso-manager-node's docs/site-join.md) but
nothing on the actual published docs site (theta42.github.io/theta-suite/)
-- a reader landing there would find no mention of it at all beyond a
vague, unlinked "multi-site replication" bullet on the homepage.

- New docs/sso/multi-site.md: the operator-facing master/spoke join guide
  (why, how, promoting a spoke, what replicates, current limits), with an
  explicit section distinguishing it from the pre-existing N-way LDAP MMR
  replication page (replication.html) -- two different mechanisms that
  were at real risk of being conflated with nothing to tell them apart.
- New docs/jump-host/mesh.md: the gateway-to-gateway WireGuard mesh guide,
  linked from a "WireGuard mesh routing" bullet that already existed on
  the jump-host homepage but pointed nowhere.
- docs/sso/index.md, docs/jump-host/index.md: link the new pages from
  each component's Features list.
- docs/index.md: replaced the oversold, unlinked "multi-site replication
  running in seconds" homepage copy with an accurate, linked claim.
2026-08-10 19:54:25 -04:00
wmantly 35c1a476c9 Merge pull request #205 from theta42/chore-bump-theta-agent-v2.1.3
CI/CD / build-theta-agent (push) Successful in 41s
CI/CD / docker-push (push) Has been skipped
chore(submodules): bump theta-agent to v2.1.3
2026-08-10 16:19:13 -07:00
wmantly aeed5b8723 chore(submodules): bump theta-agent to v2.1.3 (fixes v2.1.2 CI failure)
v2.1.2's release build failed on the Windows CI leg (test-only issue --
TestApplyHostsOverride_* didn't skip on non-Linux, where
applyHostsOverride() correctly refuses). v2.1.2's actual code was never
functionally broken, but v2.1.3 is the release whose CI run is actually
green, so that's what theta-suite should point at.
2026-08-10 19:16:22 -04:00
wmantly 6640f8059a Merge pull request #204 from theta42/release-v2.3.0
CI/CD / docker-push (push) Failing after 15s
CI/CD / build-theta-agent (push) Successful in 41s
release(v2.3.0): live replication + gateway-to-gateway WireGuard mesh
v2.3.0
2026-08-10 16:10:10 -07:00
wmantly d6611c7d1b release(v2.3.0): live replication + gateway-to-gateway WireGuard mesh
Rolls up theta-directory v2.4.0, jump-host v2.1.0, theta-agent v2.1.2.

Multi-site directory sync stops being a one-time snapshot (live
fire-and-forget replication, identical agent-signing keys, coordinated
master promotion), and site-to-site networking becomes real
infrastructure (gateway-to-gateway WireGuard mesh, kernel-first with a
userspace wireguard-go fallback, real two-container-verified tunnels)
instead of a documented-but-unbuilt design. Linux mDNS local-discovery
also lands end to end (announcer + agent listener).

See CHANGELOG.md for the full rollup and docs/MULTI_SITE_SPEC.md for
the architecture + explicit TODO list of what's still open.
2026-08-10 19:06:54 -04:00
wmantly 9aaa35fa4e docs(multi-site): mark Linux mDNS local-discovery shipped and verified
Announce (theta-gateway) + discover/apply/revert (theta-agent) confirmed
working end-to-end over real multicast between real containers, including
two real bugs found and fixed along the way (IPv6 query abort, EBUSY on
rename over a bind-mounted /etc/hosts).

Windows/macOS mDNS is now the ONLY unbuilt piece of the original design
this session set out to implement -- and it's blocked on platform access
this environment doesn't have, not on missing design or effort.
2026-08-10 18:10:13 -04:00
wmantly 182787f268 docs(multi-site): add an explicit TODO list, ordered by dependency 2026-08-10 17:31:04 -04:00
wmantly d7698a60e7 docs(multi-site): record no-inbound relay mechanism verification
Confirmed the core idea (master terminates a connection, relays over a
spoke's WG mesh IP to a spoke with zero published/inbound ports of its
own) with a standalone test: an external client hit the master's public
port and got a response that could only have come from the spoke,
which had no reachable port except over the tunnel.

Deliberately did NOT wire this into theta-proxy's actual Lua/Redis
routing engine -- that needs its own dedicated pass to do safely, plus a
real service-to-service credential between sso-manager-node and
theta-proxy/theta-gateway that doesn't exist yet. Recorded as verified
mechanism / unbuilt automation, not conflated with either "done" or
"unknown whether it would even work."
2026-08-10 17:28:45 -04:00
wmantly 484bf0e91d docs(multi-site): reconcile spec with live replication, promotion, WG mesh
Updates the status table and top-of-doc callout to reflect what actually
shipped this pass: live catalog replication, identical-directory signing
key, coordinated master promotion (with two real bugs found + fixed along
the way), and a real, tested gateway-to-gateway WireGuard mesh.

Explicitly calls out what's still NOT true despite all of the above: the
mesh exists as its own transport layer but sso-manager-node's join/
replicate traffic doesn't route over it yet, so the no-inbound-spoke
relay scenario still isn't solved end-to-end. mDNS remains unbuilt.
2026-08-10 17:24:18 -04:00
wmantly f42b69c084 feat(multi-site): wire selfUrl through setup.sh so joins register live
Extends the shipped CFG_MASTER_DIRECTORY_URL/JOIN_KEY join flow with the
selfUrl a spoke needs to register itself for live catalog replication
(theta-directory v2.4.0's POST /api/site/spokes) -- without this, every
spoke was permanently limited to the one-time join snapshot even after
the master gained the ability to push live updates.

setup.sh already computes CFG_SSO_HOST before this point in the script;
passes https://$CFG_SSO_HOST as bootstrap/site-join.js's third argument,
which forwards it as `selfUrl` in the POST /api/site/join body.
2026-08-10 16:50:07 -04:00
wmantly 0367c33542 docs(multi-site): reconcile spec with shipped v1, add mDNS agent handoff spec
MULTI_SITE_SPEC.md described a WireGuard-mesh + live-replication design as
if unbuilt-but-planned; meanwhile theta-directory v2.2.0-v2.3.0 (rolled up
in theta-suite v2.2.0) already shipped a simpler, real join mechanism
(one-time LDIF/catalog export over a site join key, read-only spoke
enforcement, setup.sh wiring) that this doc didn't mention at all. Added a
callout pointing at docs/site-join.md as the actual current behavior, and
corrected the status table so it no longer implies unbuilt features are
implemented.

Also adds AGENT_LOCAL_DISCOVERY_SPEC.md, a standalone handoff spec for the
mDNS "prefer local discovered directory" optimization -- confirmed not
implemented anywhere in theta-agent. Needs Windows/Mac-native investigation
this environment can't do; written so it can be picked up independently.
2026-08-10 15:38:39 -04:00
wmantly ff9c87ff20 Merge pull request #203 from theta42/release-v2.2.0
release(v2.2.0): multi-site join end-to-end
2026-08-10 09:42:04 -07:00
wmantly 423e064147 release(v2.2.0): multi-site join end-to-end (theta-directory v2.3.0 + setup.sh wiring) 2026-08-10 09:40:25 -07:00
wmantly 52c9c30c52 Merge pull request #202 from theta42/feat/multi-site-join-setup
feat(setup): first-run site join via CFG_MASTER_DIRECTORY_URL / CFG_MASTER_DIRECTORY_JOIN_KEY
2026-08-10 09:34:14 -07:00
wmantly ea75e94b3e ci(lint): keep job name 'Syntax check bootstrap.js' for branch protection
Renaming the job broke the master protection rule, which requires a check
named exactly 'Syntax check bootstrap.js'. The job still checks both bootstrap
scripts, just under the protected name.
2026-08-10 09:33:01 -07:00
wmantly 301770321e feat(setup): first-run site join via CFG_MASTER_DIRECTORY_URL / CFG_MASTER_DIRECTORY_JOIN_KEY
Multi-site join wiring (server + UI landed in theta-directory v2.3.0):

- bootstrap/site-join.js: runs inside the sso-manager container (same
  self-contained rule as bootstrap.js); logs in as the bootstrap admin and calls
  /api/site/join. Idempotent: an already-joined node reports 'already a spoke'.
- setup.sh step 5b: if setup.env sets CFG_MASTER_DIRECTORY_URL +
  CFG_MASTER_DIRECTORY_JOIN_KEY, run the join after the bootstrap. Only honored
  on first run (ensure_config reads setup.env once and ignores it once
  ./config/ exists), so an already-populated directory can never be merged.
- setup.env.example documents both vars.
- lint.yml also node --check's site-join.js.
2026-08-10 09:12:47 -07:00
wmantly c6c6f09d48 Merge pull request #201 from theta42/chore/submodules-site-join
chore(submodules): bump theta-directory to v2.2.0
2026-08-10 06:14:21 -07:00
wmantly cccde0792e chore(submodules): bump theta-directory to v2.2.0 (multi-site join endpoints + emoji fix) 2026-08-10 06:12:16 -07:00
wmantly a2afc127c4 Merge pull request #200 from theta42/release-v2.1.1
release(v2.1.1): fresh-install fixes
2026-08-09 20:37:35 -07:00
wmantly ad2f7b7e11 release(v2.1.1): fresh-install fixes (agent silent install, Directory modal/version) 2026-08-09 23:36:19 -07:00
wmantly ec73eae07d Merge pull request #199 from theta42/fix/fresh-install
fix(setup): default org name is Theta Directory; bump theta-agent
2026-08-09 20:29:22 -07:00
wmantly 13aeac059f fix(setup): default org name is Theta Directory; bump theta-agent to the fresh-install fix
- CFG_ORG default was 'SSO Manager', which became the browser tab title / app
  name on fresh installs. Default is now 'Theta Directory' (existing deployments
  keep their operator-owned ./config/sso-secrets.js name).
- Records theta-agent d937f8d (silent-install server_url + tray autostart +
  self-update 404 fixes).
2026-08-09 23:26:33 -07:00
wmantly 349d3d4cd0 Merge pull request #198 from theta42/release-v2.1.0
release(v2.1.0): theta-agent Windows client + theta-directory install commands
2026-08-09 18:45:44 -07:00
wmantly d8725990b3 release(v2.1.0): theta-agent Windows client + theta-directory install commands 2026-08-09 21:44:26 -07:00
wmantly de2d65fe8f Merge pull request #197 from theta42/chore/submodules-theta-agent-v2.1.0
chore(submodules): bump theta-agent to v2.1.0
2026-08-09 18:35:22 -07:00
wmantly e5446b2cfe chore(submodules): bump theta-agent to v2.1.0 (Windows agent: platform ops, WireGuard, IAM, tray, installer, CI) 2026-08-09 20:49:02 -07:00
wmantly 0c87c79f06 release(v2.0.4): bump theta-directory for the OpenLDAP base-image speedup (#196)
Dockerfile.openldap now pulls ghcr.io/theta42/openldap-nestgroup instead of
compiling from source on every build (~5-6min -> ~1.5min per CI matrix run,
and removes the runtime dependency on git.openldap.org).

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-09 17:10:35 -07:00
wmantly c7bf1d0edd release(v2.0.3): bump theta-directory to fix Directory tab managed-filter bug (#195)
Directory tab admitted every kind:'host' resource regardless of promotion
status (every discovery plugin creates its finds as kind:'host'), and
site-status 500'd on a nonexistent Resource.subType column.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-09 16:30:28 -07:00
wmantly a917915037 release(v2.0.2): unify component docs, bump theta-directory/proxy/jump-host (#194)
Unifies the GitHub Pages docs site: the SSO/Proxy/Jump Host pages, their
nav labels, and each component's own README now consistently say Theta
Directory / Theta Proxy / Theta Gateway, drop marketing sections ("Why this
over the alternatives", "Get it", "Related projects") that don't apply to a
suite component, remove every standalone/bare-metal install path, and link
to theta42.github.io/theta-suite/... instead of the old per-repo Pages sites.

Bumps submodules: theta-directory v2.0.2, proxy v2.0.1, jump-host v2.0.1.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-09 16:14:23 -07:00