First-Run Setup & Administrators

How a fresh Synaplan instance gets its first administrator — and how to switch that step off when your accounts come from somewhere else.

Authoritative reference: docs/CONFIGURATION.md and docs/INSTALLATION.md in the main repository.


The setup wizard

An installation that has no administrator and not a single user serves a short wizard at /setup:

  1. Create the administrator — email and password, signed in immediately. The address counts as verified, so a missing mailer cannot lock you out.
  2. Connect an AI provider (skippable) — the same provider cards as Admin → AI Providers.
  3. Decide who may in — self-registration and guest chat.

While the wizard is pending, every other API route answers 503 SETUP_REQUIRED and the web app sends every route to /setup, so there is no half-configured state to stumble into.

Once any account exists the wizard is closed for good. It does not reopen if every administrator is later deleted or demoted — a wizard that reappears on a running instance would let the next visitor claim it.

The window between the first start and the first administrator is open. The account belongs to whoever fills in that form first, as in Open WebUI, Immich or n8n. On a publicly reachable host, either finish the wizard right after deploying, or create the administrator up front so the window never exists.

Switching the wizard off

SETUP_WIZARD_ENABLED=false

Defaults to true when the variable is unset or empty, so an instance you do not configure keeps the wizard. With it set to false:

  • /setup is gone — the route redirects to the login page, and POST /api/v1/setup/admin is refused with 409 SETUP_WIZARD_DISABLED. Nobody can claim the instance through the browser.
  • The API is not locked down. An installation with an empty user table serves requests normally instead of answering 503 SETUP_REQUIRED, because an empty database is now a legitimate steady state.
  • Getting an administrator is up to you — see the three paths below.

Three ways to get an administrator

Path Where the account comes from Use it when
Setup wizard A browser form on the fresh instance Manual installs; leave SETUP_WIZARD_ENABLED unset
Bootstrap variables BOOTSTRAP_ADMIN_EMAIL + BOOTSTRAP_ADMIN_PASSWORD, applied on container start Automated and platform-managed installs
Identity provider An OIDC role claim promotes the account on sign-in SSO deployments with no local accounts

The bootstrap pair only acts while no administrator exists, and a restart never rotates the account. Set both variables or leave both empty — a container that receives only one of the two refuses to start. Details on the Production Deployment page.

SSO-only instances (no local accounts)

An instance whose users all come from an identity provider does not need the wizard at all: there is no local administrator to create, and everything the wizard writes can come from the environment instead.

SETUP_WIZARD_ENABLED=false
REGISTRATION_ENABLED=false
GUEST_CHAT_ENABLED=false

OIDC_DISCOVERY_URL=https://idp.example.com/realms/main/.well-known/openid-configuration
OIDC_CLIENT_ID=synaplan
OIDC_CLIENT_SECRET=…
OIDC_AUTO_REDIRECT=true

The database stays empty until the first person signs in, and the login page offers the identity provider instead of a wizard. An AI provider key from the environment (GROQ_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY, …) is imported on start, so the provider step is not needed either — the instance is fully configured without a single browser step.

Administrators come from claims. On sign-in, the roles found at OIDC_ROLE_CLAIMS are matched case-insensitively against OIDC_ADMIN_ROLES; a match sets the account to ADMIN, and losing the role sets it back to a normal user. Both variables ship Keycloak-shaped defaults and only need changing for a different provider:

Variable Default Notes
OIDC_ADMIN_ROLES admin,realm-admin,synaplan-admin,administrator Claim values that grant admin
OIDC_ROLE_CLAIMS realm_access.roles,resource_access.{client_id}.roles,groups Dot-notation paths; {client_id} expands to OIDC_CLIENT_ID. Azure AD: roles. Auth0: https://myapp\.com/roles

Two properties are worth knowing before you rely on that mapping:

  • A role change takes effect on the next sign-in. Claims are read during the login callback and on every OIDC bearer request, not on cookie-backed session requests or token refresh. Revoking admin in the identity provider does not end a session that is already open.
  • A token carrying no role claim at all changes nothing. An empty result is treated as "no information", so a mistyped claim path cannot silently demote every administrator. Verify the path once against a real token instead of assuming the default fits your provider.

Recovery never depends on the identity provider: php bin/console app:admin:reset-password --promote still creates or promotes a local administrator if the IdP becomes unavailable.

Reopening the wizard on a development stack

Development and test installs never see the wizard, because their fixtures seed a demo account. To get it back on a running dev stack:

make -C backend setup-reset

That deletes every account and the completion flag; the next page load lands on /setup. It refuses to run outside APP_ENV=dev or test. The reset only holds until the backend container restarts — the entrypoint reloads the demo fixtures into an empty database, and any account closes the wizard again. Start the stack with SEED_DEMO_DATA=false to keep it open across restarts.


Related pages