The published docs site had drifted from what actually shipped: - docs/sso/multi-site.md said the no-inbound relay was "designed but not automated" -- it's been automated since earlier this session. Also said "don't combine" multi-site join and LDAP replication -- they're integrated now (join auto-configures LDAP MMR). - docs/sso/replication.md (the actually-published/linked replication page -- distinct from theta-directory's own docs/replication.md, which isn't linked from this site's nav at all) still only described the old fully-manual LDAP_SERVER_ID/LDAP_REPLICATION_HOSTS setup, with zero mention of the new auto-config or CFG_LDAP_MMR_MANUAL. - docs/index.md's feature bullet described multi-site purely as "N-Way Multi-Master LDAP replication" -- the actual master/spoke join feature (the more commonly-used, higher-level mechanism) wasn't mentioned on the homepage at all. - CFG_PUBLIC_DOMAIN (shipped, never documented anywhere an operator would read it) now explained in multi-site.md. - spoke.env now discoverable from quickstart.md, not just multi-site.md. Also documents the real, load-bearing limitation from this session's promotion/LDAP-orphan fix: the master's own replication peer list only updates on its own next setup.sh run, not live the instant a spoke joins or a promotion happens.
7.4 KiB
layout, title, description
| layout | title | description |
|---|---|---|
| default | Quickstart | Step-by-step first run for theta-suite — prerequisites, setup.env, and bringing up the stack with ./setup.sh. |
Quickstart Guide
Prerequisites
- A Linux host with Docker + Docker Compose (you must use the modern
docker composev2 plugin; the olderdocker-composev1 standalone will fail on BuildKit images). - Two hostnames that resolve to the host: one for the SSO UI (your
stack.ssoHost), one for the proxy mgmt UI (yourstack.proxyHost). On a real network add DNS records; for a local try, add them to/etc/hosts. - Port 80 + 443 reachable from the internet if you want Let's Encrypt certs; otherwise the proxy serves a self-signed fallback (browsers warn — expected for LAN use).
1. Clone
git clone --recursive https://github.com/theta42/theta-suite.git
cd theta-suite
--recursive fetches the two submodules (sso-manager-node, proxy) in one
step. If you forgot it:
git submodule update --init --recursive
2. Configure setup.env (enter your domain once)
cp setup.env.example setup.env
$EDITOR setup.env # set CFG_DOMAIN to your domain
Your domain is entered once, as a plain DNS domain. The SSO/proxy
hostnames default to sso.<domain> / proxy.<domain>, and the LDAP base DN
is built from it (any number of labels works — a domain like
myhost.duckdns.org becomes dc=myhost,dc=duckdns,dc=org), so for most
setups CFG_DOMAIN is the only value you set:
setup.env key |
Example | Notes |
|---|---|---|
CFG_DOMAIN |
lab.local |
your domain — required |
CFG_SSO_HOST |
sso.lab.local |
optional, defaults to sso.<domain> |
CFG_PROXY_HOST |
proxy.lab.local |
optional, defaults to proxy.<domain> |
CFG_ADMIN_UID |
admin |
optional, defaults to admin |
CFG_ADMIN_EMAIL |
admin@<proxyHost> |
optional |
CFG_BASE_DN |
dc=lab,dc=local |
advanced: override the derived LDAP base DN |
CFG_JUMP_HOST |
jump.lab.local |
optional, defaults to jump.<domain> (the SSH jump host is installed + started by default) |
JUMP_SSH_PORT |
2222 |
optional: host port for the jump host's SSH (never 22 by default) |
setup.env is used only on the first run to generate ./config/; after
that ./config/*.js are operator-owned and setup.env is ignored. Secrets
(LDAP admin password, JWT, admin password, service-account password) are
generated into ./config/*.js on first run — do not put them in
setup.env. See setup.env.example for the full annotated shape, and
config.example/ + each submodule's secrets.js.example for the generated
file shape.
Migrating from an older
.env-based deployment? If.env/proxy.envexist,./setup.shmigrates them into./config/preserving your existing secrets — no need to write asetup.env.
Joining an existing Theta Directory cluster instead of seeding a fresh one? See Multi-Site (Master/Spoke Join) —
spoke.env.examplehas the join-a-cluster vars split out into their own file, or set them directly insetup.env(which has every option).
3. Run
./setup.sh
The first run reads setup.env, generates ./config/sso-secrets.js +
./config/proxy-secrets.js with your domain filled in everywhere plus random
secrets, then builds and starts the stack in the same run (no edit-and-re-run).
What happens:
- Snapshots state to
./backups/<timestamp>/before rebuilding (a no-op on the very first run). - Builds + starts sso-manager, waits for
/health. - Runs the bootstrap inside the sso-manager container — creates the LDAP
service account, your first admin, and the proxy's OAuth client, and writes
the generated client id + secret into
./config/proxy-secrets.js. - Builds + starts proxy, waits for
/health. - Registers
<SSO_HOST>and<PROXY_HOST>as Host records in the proxy — every hostname the proxy serves, including its own UI and the SSO's, needs one of these or it 404s. Idempotent. - Prints your first-admin login + the public URLs.
The first run builds two Docker images (a few minutes). Subsequent runs are fast.
4. Point DNS at the host
stack.ssoHost and stack.proxyHost (from ./config/sso-secrets.js) must
resolve to the host running the stack. Add DNS records, or for a local try:
echo "127.0.0.1 sso.lab.local proxy.lab.local" | sudo tee -a /etc/hosts
(The proxy needs port 80 reachable for Let's Encrypt; on a LAN without that it serves a self-signed cert — browsers will warn, which is fine for home-lab use.)
5. Log in
Open https://<SSO_HOST> and log in as your bootstrap admin
(bootstrap.adminUid / bootstrap.adminPass). From there you can add users,
groups, and OAuth clients.
The proxy mgmt UI is at https://<PROXY_HOST> (same admin SSO login protects
it). Add the Host records you want to protect with OIDC.
First-run fallbacks (if DNS/TLS isn't ready yet): SSO UI at
http://127.0.0.1:3001, proxy UI at http://127.0.0.1:3000.
Re-running
./setup.sh is idempotent — safe to re-run after editing ./config/, after
a docker compose down, or after restoring from backup. It snapshots state,
then converges the stack to your ./config/ values (LDAP service account + admin
passwords are reset to the config; the OAuth client is kept if proxy-secrets.js
already holds its creds).
Troubleshooting: "A newer version is available" after running setup.sh? If the UI shows this warning immediately after you ran
./setup.sh, the latest GitHub release tag might not yet be merged into the default tracking branch for the submodules, or Docker may have cached theCOPYstep if thepackage.jsondidn't change. You can force a clean rebuild by runningdocker compose build --no-cacheand then re-running./setup.sh.
Direct LDAP for LDAP-native clients and Linux hosts
LDAP-native apps and Linux hosts (PAM/SSSD, sudo, SSH keys) bind LDAP directly over LDAPS:
ldapsearch -x -H ldaps://<host>:636 \
-D "cn=ldapclient,ou=people,dc=lab,dc=local" -W \
-b "ou=people,dc=lab,dc=local" '(objectClass=posixAccount)' cn mail
Use the cn=ldapclient service account (read-only, the bootstrap created it)
or the admin DN. Use LDAPS (636), not plain LDAP.
Backups and restore
./setup.sh auto-snapshots ./config/ + LDAP + both Redis to ./backups/<ts>/
before each rebuild (keeps the last BACKUP_KEEP, default 5). For manual
backups and the full restore runbook (full / Redis-only / LDAP-only, with the
AOF-vs-RDB note), see the Backups and restore section of the
README. Quick LDAP
backup:
docker compose exec sso-manager slapcat -f /etc/openldap/slapd.conf \
-b "<base>" > backup-$(date +%F).ldif
Next steps
- Add users / groups in the SSO UI.
- Add Host records in the proxy UI to protect your apps with OIDC.
- Mint API tokens to drive either app's management API from scripts/CI:
under API Tokens in each UI, mint a personal access token and use it as
Authorization: Bearer sso_…(SSO) orprx_…(proxy). A token authenticates as its creator with their permissions. See each submodule's DEPLOYMENT. - See Architecture for how it all fits together.