Status: mostly implemented (Phase 1.5). This captures the "anyone installs it and it just works" goal and the setup flow that gets us there.
A new operator (k3s + ESS already running) should go from zero to a working MatrixCtrl with one command and no manual secret/OIDC fiddling:
helm install matrixctrl oci://ghcr.io/bxnnyg/charts/matrixctrl --namespace matrixctrl --create-namespaceEverything that can be derived or generated must be — the operator should never type a DB password, JWT secret, or hand-register an OIDC client.
- DB password + JWT secret: auto-generated. Helm chart generates them
(
lookup+randAlphaNum,resource-policy: keep); the app also self-generates and persists the JWT key ininstance_settingsif no env is set. - In-cluster deploy: chart deploys app + Postgres sidecar + RBAC + ingress.
The painful manual steps today are not the secrets. They are:
- MAS OIDC client registration — generating a ULID client_id, adding it to
policy.data.admin_clients, settingclient_secret_basic, patching the MAS ConfigMap, restarting MAS. This is the step that makes "just works" fail. - ESS discovery — knowing the namespace, release name, Synapse/MAS URLs.
- Config seed — getting the current ESS values into the config repo.
MatrixCtrl can register its own MAS client via the MAS Admin API. The same
client_credentials + urn:mas:admin flow we use for the admin-login check can
also create/manage OAuth clients. This means client registration can be automated
instead of hand-patched.
A one-shot bootstrap, runnable as a Helm post-install Job or kubectl exec:
matrixctrl setup
1. Discover ESS:
- scan namespaces for a Helm release whose chart is matrix-stack
- read its values (helm get values) → serverName, MAS/Synapse service URLs
2. Seed config:
- write the live release values into the config-repo as the base slices
3. Register OIDC client in MAS:
- obtain an admin token (operator provides a one-time MAS admin client cred,
OR we use an existing admin OAuth session)
- create the MatrixCtrl client (redirect_uri from the ingress host)
- add it to admin_clients policy
- store client_id/secret in the matrixctrl Secret
4. Generate remaining secrets (done by chart already)
5. Verify: discovery, DB, k8s RBAC, MAS reachability → print a health summary
- Bootstrapping the MAS admin token: registering a client needs admin access to MAS. Chicken-and-egg. Options: (a) operator pastes a short-lived MAS admin token once; (b) MatrixCtrl is granted a MAS admin client cred via ESS values at install; (c) first-run uses the existing bootstrap (bcrypt) login, then the admin links MAS interactively. Leaning towards (a) for the CLI and (c) for the UI.
- Idempotency: re-running setup must detect an existing client and not dupe.
- Non-ESS / generic Matrix: v1 targets ESS; keep discovery pluggable.
If MatrixCtrl is meant to deploy ESS from scratch, it cannot depend on MAS for login during that first deployment — MAS doesn't exist yet. OIDC-admin login is a post-ESS capability. So setup must handle two distinct states:
State A — GREENFIELD (no ESS yet):
- MatrixCtrl runs in BOOTSTRAP auth (local bcrypt admin + JWT, already implemented;
auto-generated admin password printed on first start). OIDC is OFF.
- k8s + Helm operations DO NOT need MAS — so MatrixCtrl can fully deploy ESS in
this state: pick ESS version → seed an initial config (wizard / chart defaults)
→ helm install ess.
- This is the bootstrap; the operator logs in with the local admin password.
State B — POST-ESS (MAS now running):
- Run `matrixctrl setup` (or a UI "Connect Matrix login" flow):
register the MatrixCtrl OIDC client in MAS via the Admin API,
write OIDC config, flip oidc.enabled=true.
- MatrixCtrl restarts into OIDC/admin-only mode; bootstrap login auto-disables
(router already drops /bootstrap/login when OIDC is configured).
Implication for the code today: the OIDC-or-bootstrap switch already exists, but the transition (B) must be runtime-reconfigurable without hand-editing env/secrets, and greenfield (A) needs an "initial ESS config" wizard (seed the section files from the chart's default values.yaml, not from a live release). Both are setup-phase work.
Greenfield config seed: State A can't helm get values (no release). Instead seed
the section files from the ESS chart's bundled values.yaml (pull chart → split via
the existing migrator) so the user starts from the documented defaults and edits down.
This is Phase 1.5 — between Phase 1 (config + helm + OIDC, done) and Phase 2 (user/room management). It's the difference between "works for me" and "works for anyone", so it's high-leverage but not a Phase-1 blocker. See ROADMAP.md.
Setup-phase task list:
- ✅ Greenfield deploy flow —
POST /api/v1/setup/deploy-ess(helm.Install) + /setup wizard. - ✅ Initial-config seed from the chart's default values.yaml —
config.Store.SeedSections. - ✅ Auto-register OIDC client —
POST /api/v1/setup/connect-oidcwrites the client + admin_clients into the matrixAuthenticationService config and helm-upgrades ESS (we register via the config we manage, NOT the MAS Admin API, because admin_clients is static MAS policy the API can't change). - ✅ Runtime bootstrap→OIDC switch — DB-backed OIDC config + AuthHandler.ReloadOIDC hot-reload; bootstrap login always-registered but 403s once OIDC is active.
- ✅ ESS discovery for the "manage existing" path —
helm.Discover()(all-namespace scan for matrix-stack) + startup auto-discovery +POST /api/v1/setup/adopt(seed config fromhelm get values). LIVE-tested: found ess/ess 26.5.1. - ⏳ End-to-end greenfield live test — discovery + adopt building blocks live-validated; the full greenfield install (deploy-ess) + connect-oidc happy-path still needs a fresh cluster.
A pre-existing instance (ESS already deployed + env-OIDC configured) can't exercise the greenfield/connect happy-path — the guards short-circuit. Discovery/adopt data-gathering IS proven live.