The shipped join flow (v2.2.0-v2.3.0) was a one-time snapshot: a spoke's catalog never updated after joining. This adds the two pieces that were explicitly designed but missing: - Live replication: a spoke registers its own endpoint with the master right after joining (POST /api/site/spokes, Bearer join-key), receiving a pushToken. Every successful catalog write on the master now fires a fire-and-forget resync ping (utils/site_replicate.js) at every known spoke, concurrently -- one unreachable spoke never blocks or delays another (wired into the existing write-gate middleware in api_directory_admin.js). The spoke's POST /api/site/resync handler reuses the already-tested export+import path rather than applying a partial diff. - Identical directories: POST /api/site/export now best-effort includes the master's agent-signing key; a spoke adopts it via agent_keys.adopt() on both join and every resync, so every site's sso-manager can validly sign a command for any agent enrolled anywhere -- the accepted tradeoff discussed for this deployment's scale (blast radius for simplicity). New SiteSpoke model tracks registered spokes (endpoint + pushToken); registered it in models/index.js (a real bug the e2e test below caught -- SiteSpoke.list() 500'd with "Cannot read properties of null (reading 'adapter')" until the model was added to initORM's model list). Verified end-to-end against docker-compose.multisite-e2e.yml: mint join key -> join with selfUrl -> write a NEW resource on master post-join -> poll the spoke -> it shows up within a few seconds via the resync push, no manual re-join needed. MULTISITE E2E PASS. Unit tests: nodejs/tests/site_replicate.test.js (concurrent fan-out, one failing spoke doesn't block another, empty-registry and list()-throws edge cases).
Theta Directory
A production-grade, self-hosted OpenID Connect provider, Resource Directory & IAM Engine, and bundled OpenLDAP directory with a modern web console — designed for home-labs and enterprise infrastructure that demand total sovereignty over their identity, secrets, and resource catalog.
It provides a single source of truth for identity (OIDC + LDAP), host/service directory inventory, access control groups, and secrets management running entirely on your own hardware without third-party cloud lock-in.
Theta Directory is deployed as part of Theta Suite,
alongside Theta Proxy and
Theta Gateway — it isn't installed or
run on its own. ./setup.sh wires the whole stack together automatically.
Documentation: https://theta42.github.io/theta-suite/sso/
Screenshots
| Dashboard | Users |
|---|---|
![]() |
![]() |
| Groups | OAuth Apps |
|---|---|
![]() |
![]() |
| Sites & Replication |
|---|
![]() |
| Agent Capabilities & Metrics | Agent Install (Join Key) |
|---|---|
![]() |
![]() |
Features
- OpenID Connect / OAuth 2.0 provider — issue your own access, refresh, and
ID tokens; protect your apps with standard OIDC login. Discovery document at
/.well-known/openid-configuration. - Bundled OpenLDAP directory — users, groups, POSIX accounts
(
posixAccount/inetOrgPerson), SSH public keys, and sudo roles, withmemberOf+ referential-integrity overlays. This is your single source of truth for identity, not a sidecar. - Web management UI — manage users, groups, and OAuth clients from a browser; invite and password-reset flows over email; user self-service for profile and API tokens.
- Direct LDAP binds — Linux hosts (PAM/SSSD login, LDAP-backed
sudorules, SSH public keys via openssh-lpk) and LDAP-native apps (Gitea, Emby, and anything else that speaks LDAP) use LDAPS (636) or StartTLS against the same directory, so you don't maintain a second user database for them. - Personal access tokens — any user can mint a long-lived bearer token to drive the management API from scripts or CI, scoped to their own permissions.
- Directory & Inventory Graph — full host/service/site graph with resource metadata, automatic LDAP group provisioning (
_access/_admin), and Access Request workflows. - Subtype Management & Metrics Drivers Engine — 4-tier resolution engine binding
subTypemetadata (systemd,docker,proxmox,wireguard,postgresql,redis,k8s) to operational telemetry, log streaming, and remote lifecycle control. - Explicit Secret Inheritance Mode — OpenBao KV-v2 integration with strict upward ancestor lineage (
Resource -> Host -> Cluster -> Site), preserving precise secret scoping across services and containers. - Multi-Site Support (Geo-Location Scaling) — built-in support for N-Way Multi-Master OpenLDAP replication across physical sites for HA and low latency.
Secrets
Secrets are loaded from OpenBao at boot via
@simpleworkjs/bao-conf, which
deep-merges secret/sso-manager/conf over the file-loaded config (fail-soft:
if OpenBao is unreachable, boot continues from CONF_SECRETS). The SSO
authenticates to OpenBao with the scoped VAULT_TOKEN (env, policy
sso-broker) — never the root token.
The SSO also acts as the vault broker for the whole stack: it mints
per-user (user-<uid>) and per-admin (sso-admin) tokens through the
sso-broker token role and exposes the personal-secrets UI at Vault → My
Secrets (secret/users/<uid>/*, server-side token injection + path-scope
guard) and an admin Apps tab to mint scoped tokens for external apps
(secret/apps/<name>/*). The old utils/conf_manager.js was replaced by
@simpleworkjs/bao-conf; the admin Configuration UI (/api/conf) now
writes secret/sso-manager/conf through bao-conf.set.
The config/*-secrets.js files are operator-edit seed artifacts (gitignored),
not the authoritative store. For the full architecture, policies, token model,
and rotation procedure, see theta-suite's
Secrets docs.
Architecture
┌─────────────┐
│ Browser / │
│ OIDC apps │
└──────┬──────┘
│ HTTP/HTTPS
▼
┌────────────────────────┐ ┌─────────────┐
│ Theta Directory │◄────►│ Redis │
│ - OIDC provider │ │ - sessions │
│ - web UI (:3001) │ │ - models │
│ - management API │ └─────────────┘
└────────┬───────────────┘
│ ldapi/ldap (localhost)
▼
┌────────────────────────┐
│ OpenLDAP (slapd) │
│ - users / groups │
│ - LDAPS :636 │─── Linux hosts + LDAP apps bind directly
│ - StartTLS :389 │
└────────────────────────┘
Documentation
The nitty LDAP details (overlay setup, the custom theta42Person schema, the
required groups, LDAPS/TLS, direct-bind service accounts) live in:
- DEPLOYMENT.md — Docker + bare metal, the config layers, the
app_*env reference, LDAPS/TLS, backups, troubleshooting. - API.md — the management API.
- docs/ — the same content broken into OAuth/OIDC and LDAP, also published at the unified theta-suite docs site.
- CHANGELOG.md — what changed in each release.
- All of the above is also readable from the running app itself at
/docs— no internet access required.
License
MIT — see LICENSE.






