Skip to main content

exd-server-admin

Operator CLI for exd-server. Initializes the database, manages tenants and namespaces, and mints / revokes service tokens. Every command writes directly to the SQLite database — it does NOT go through the HTTP API.

Synopsis

exd-server-admin <command> [args]

Subcommands:

When to use this CLI vs. the HTTP API

Every command here corresponds to an HTTP endpoint a superadmin or tenant-admin token can reach. Reach for exd-server-admin when:

  • You are bootstrapping a fresh deployment and don't yet have any tokens.
  • You are running schema migrations during a binary upgrade.
  • You need a direct DB recovery path (e.g., a forgotten/lost superadmin token — restore by running bootstrap against the same DB; existing tenants and namespaces are preserved).

Use the HTTP API (and an SDK / curl) for routine day-to-day operations that you want to script through a CI pipeline.


Common flag

FlagDefaultDescription
--db <path>$EXD_DB_PATH or ./exd.sqliteSQLite database file. Must be writable.

bootstrap

Initialize the database and mint the first superadmin token.

Synopsis

exd-server-admin bootstrap [--db <path>] [--name <token-name>]

Use cases

  • First-time setup. Run once when a fresh exd-server binary lands on a new host. Creates the schema, mints the superadmin token, and prints it to stdout — the only time you will see the secret in cleartext.
  • Disaster recovery (lost superadmin token). Rerun against the existing DB. The schema is preserved; a new superadmin token is minted alongside any existing ones. Revoke the old (compromised) tokens afterwards with token revoke.

Flags

FlagDefaultDescription
--db <path>./exd.sqliteDB path. Created if it does not exist.
--name <name>bootstrapHuman-readable name attached to the minted token (for audit trails).

Behavior

  1. Opens the DB at <path>, creating it if absent.
  2. Runs the embedded migration set to bring the schema to current.
  3. Refuses to mint if any active superadmin token already exists — prevents silent over-provisioning. Revoke the old token first, or use a non-conflicting name + an explicit DB path.
  4. Mints a superadmin token, persists the digest, prints the cleartext secret to stdout.

Example

$ exd-server-admin bootstrap --db /var/lib/exd/exd.sqlite --name ops-bootstrap
Minted superadmin token (id: tok_01J5P9XAXXXXX...):

exd_sa_3qkL2nM8...

Store this secret in your password manager. It is shown once.

Exit codes

CodeCondition
0Token minted.
Non-zeroDB unreachable; schema migration failed; an active superadmin token already exists.

migrate

Apply pending schema migrations against the database.

Synopsis

exd-server-admin migrate [--db <path>]

Use cases

  • Binary upgrade. Run after upgrading the exd-server binary, before starting the new server. Migrations are append-only and idempotent — running migrate against an up-to-date DB is a no-op.
  • Pre-flight check in CI. Run as part of the deploy pipeline. Exits non-zero if migrations fail; lets the deploy gate on a successful migration.

Behavior

Walks the embedded migration set and applies any whose version is not already recorded in _sqlx_migrations. Order is fixed at compile time; you cannot reorder or skip migrations.

Example

$ exd-server-admin migrate --db /var/lib/exd/exd.sqlite
Applied migration 0006_namespaces_tenant_scoped
Applied migration 0007_token_environment_slug

If the DB is already current, prints nothing.


tenant create

Register a new tenant.

Synopsis

exd-server-admin tenant create
--slug <slug>
[--display-name <name>]
[--login-mode sso|email_domain]
[--sso-provider <name>]
[--email-domain <domain>]
[--db <path>]

Use cases

  • Onboard a new team or business unit. Each tenant gets its own flag-namespace scope; namespace slugs are unique per-tenant, not globally.
  • Carve out per-environment isolation. A single org may use acme-prod, acme-staging, acme-dev as separate tenants for hard isolation.

Flags

FlagRequiredDescription
--slug <slug>yesTenant slug. Pattern: [a-z][a-z0-9-]*. Immutable.
--display-name <name>noHuman-readable label shown in dashboards.
--login-mode <mode>no, default email_domainsso or email_domain. Controls how human users authenticate.
--sso-provider <name>when --login-mode ssoSSO IdP identifier.
--email-domain <domain>when --login-mode email_domainEmail domain (e.g. acme.com).

Example

$ exd-server-admin tenant create --slug acme --display-name "Acme Inc." \
--login-mode email_domain --email-domain acme.com
Created tenant 'acme'

namespace create

Register a flag namespace under an existing tenant. Note: the flag namespace's manifest lives in a git repo and is populated by the first exd manifest push or git push. This command only reserves the slug.

Synopsis

exd-server-admin namespace create
--tenant <slug>
--slug <slug>
[--display-name <name>]
[--description <text>]
[--db <path>]

Use case

  • Reserve a slug before the first push. Mint a namespace-write token (below) for CI; the first exd manifest push populates the manifest.

Flags

FlagRequiredDescription
--tenant <slug>yesTenant slug. Tenant must already exist.
--slug <slug>yesNamespace slug. Unique per-tenant. Pattern: [a-z][a-z0-9-]*.
--display-name <name>noHuman-readable label.
--description <text>noDescription. Freeform.

Example

$ exd-server-admin namespace create --tenant acme --slug marketing \
--display-name "Marketing flags"
Created namespace 'acme/marketing'

token mint

Mint a service token. Five token kinds, each with its own scope shape — see tokens spec for the full access model.

Synopsis

exd-server-admin token mint
--kind superadmin|tenant-admin|namespace-read|namespace-write|namespace-client
--name <human-name>
[--description <text>]
[--tenant <slug>]
[--namespace <slug>]
[--environment <env>] # namespace-client only
[--allowed-origins <csv>] # namespace-client only
[--db <path>]

The secret is printed once, on stdout. There is no way to recover it later.

Use cases

ScenarioKindScope flags
Bootstrap an org-wide CI adminsuperadmin(none)
Tenant lead administers their own namespacestenant-admin--tenant
CI pushes the manifest after a mergenamespace-write--tenant, --namespace
Read-only manifest mirror (e.g. a backup job)namespace-read--tenant, --namespace
Browser-side public eval for one SPAnamespace-client--tenant, --namespace, --environment, --allowed-origins

Scope rules per kind

Kind--tenant--namespace--environment--allowed-origins
superadminrejectedrejectedrejectedrejected
tenant-adminrequiredrejectedrejectedrejected
namespace-readrequiredrequiredrejectedrejected
namespace-writerequiredrequiredrejectedrejected
namespace-clientrequiredrequiredrequiredoptional

For namespace-client, the named environment must be declared in the namespace's current manifest at mint time. If the manifest does not declare the environment, the mint fails with environment '<env>' is not declared in namespace '<slug>'.

--allowed-origins is a comma-separated list of https://… origins permitted to send the token from a browser. * is rejected. Native (non-browser) callers ignore this list. Set this for any token that will be embedded in a JS bundle.

Example: CI write token

$ exd-server-admin token mint --kind namespace-write \
--tenant acme --namespace marketing \
--name "github-actions-marketing-prod" \
--description "Pushes from .github/workflows/exd-push.yml"
Minted namespace-write token (id: tok_01J5P...):

exd_nw_8KLpQ3v...

Store this secret in your password manager. It is shown once.

Example: browser public-eval token

$ exd-server-admin token mint --kind namespace-client \
--tenant acme --namespace marketing --environment production \
--allowed-origins https://app.acme.com,https://staging.acme.com \
--name "marketing-spa-prod"

token list

List tokens by status and scope.

Synopsis

exd-server-admin token list
[--tenant <slug>]
[--namespace <slug>]
[--status active|revoked|expired|any]
[--db <path>]

Use cases

  • Audit before a credential rotation. List every active token in a tenant to know what to mint replacements for.
  • Compliance. Periodically dump every active namespace-client token to verify the origin allowlists are still correct.

Flags

FlagDefaultDescription
--tenant <slug>unsetFilter to one tenant.
--namespace <slug>unsetFilter to one namespace. Combine with --tenant.
--statusactiveOne of active, revoked, expired, any.

Example

$ exd-server-admin token list --tenant acme --status active
id kind name scope
tok_01J5P9XAXXX... tenant-admin acme-lead tenant=acme
tok_01J5PAY... namespace-write github-actions-marketing-prod tenant=acme namespace=marketing
tok_01J5PBZ... namespace-client marketing-spa-prod tenant=acme namespace=marketing

Secrets are never displayed by list — only mint can show a cleartext secret.


token revoke

Revoke a token by id.

Synopsis

exd-server-admin token revoke --id <token-id> [--db <path>]

Use cases

  • Compromised credential. Rotate immediately: revoke the old token, mint a new one, deploy the new secret, verify, then audit.
  • Decommission. Service or person leaving — revoke their tokens with a one-line command, capture the audit trail.

Behavior

Marks the token as revoked at the current timestamp. The server rejects any subsequent request bearing that token with 401 Unauthorized. A revoked token cannot be un-revoked; mint a new one if needed. Revoking an already-revoked / expired token is a no-op and reports Token <id> was already non-active; nothing changed.

Example

$ exd-server-admin token revoke --id tok_01J5PAY...
Revoked token tok_01J5PAY... at 2026-05-12T18:22:11Z

See also