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:
bootstrap— initialize the database and mint the first superadmin token.migrate— apply pending schema migrations.tenant create— register a tenant.namespace create— register a namespace under an existing tenant.token mint— issue a service token.token list— list tokens by status / scope.token revoke— revoke a token by id.
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
bootstrapagainst 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
| Flag | Default | Description |
|---|---|---|
--db <path> | $EXD_DB_PATH or ./exd.sqlite | SQLite 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-serverbinary 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
| Flag | Default | Description |
|---|---|---|
--db <path> | ./exd.sqlite | DB path. Created if it does not exist. |
--name <name> | bootstrap | Human-readable name attached to the minted token (for audit trails). |
Behavior
- Opens the DB at
<path>, creating it if absent. - Runs the embedded migration set to bring the schema to current.
- 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.
- Mints a
superadmintoken, 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
| Code | Condition |
|---|---|
0 | Token minted. |
| Non-zero | DB 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-serverbinary, before starting the new server. Migrations are append-only and idempotent — runningmigrateagainst 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-devas separate tenants for hard isolation.
Flags
| Flag | Required | Description |
|---|---|---|
--slug <slug> | yes | Tenant slug. Pattern: [a-z][a-z0-9-]*. Immutable. |
--display-name <name> | no | Human-readable label shown in dashboards. |
--login-mode <mode> | no, default email_domain | sso or email_domain. Controls how human users authenticate. |
--sso-provider <name> | when --login-mode sso | SSO IdP identifier. |
--email-domain <domain> | when --login-mode email_domain | Email 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-writetoken (below) for CI; the firstexd manifest pushpopulates the manifest.
Flags
| Flag | Required | Description |
|---|---|---|
--tenant <slug> | yes | Tenant slug. Tenant must already exist. |
--slug <slug> | yes | Namespace slug. Unique per-tenant. Pattern: [a-z][a-z0-9-]*. |
--display-name <name> | no | Human-readable label. |
--description <text> | no | Description. 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
| Scenario | Kind | Scope flags |
|---|---|---|
| Bootstrap an org-wide CI admin | superadmin | (none) |
| Tenant lead administers their own namespaces | tenant-admin | --tenant |
| CI pushes the manifest after a merge | namespace-write | --tenant, --namespace |
| Read-only manifest mirror (e.g. a backup job) | namespace-read | --tenant, --namespace |
| Browser-side public eval for one SPA | namespace-client | --tenant, --namespace, --environment, --allowed-origins |
Scope rules per kind
| Kind | --tenant | --namespace | --environment | --allowed-origins |
|---|---|---|---|---|
superadmin | rejected | rejected | rejected | rejected |
tenant-admin | required | rejected | rejected | rejected |
namespace-read | required | required | rejected | rejected |
namespace-write | required | required | rejected | rejected |
namespace-client | required | required | required | optional |
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-clienttoken to verify the origin allowlists are still correct.
Flags
| Flag | Default | Description |
|---|---|---|
--tenant <slug> | unset | Filter to one tenant. |
--namespace <slug> | unset | Filter to one namespace. Combine with --tenant. |
--status | active | One 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
exd-server— the HTTP service this CLI provisions.- Tokens spec — token format, scopes, and full access model.
- Access-control spec — who can call which endpoint with which token kind.
- Server API spec — the HTTP surface clients consume.