Pre-release. v0.1 is not out yet, so there is nothing to install and no public source to clone — the quickstart builds from a checkout.
Command line
One binary. With no command it is the server; with one it is the operator tool for the thing the
server needs. Onbe.Server below is the server binary — from a checkout,
dotnet run --project src/Onbe.Server -- <command>; in the published image, the container’s
arguments.
| Command | Reads configuration | What it does |
|---|---|---|
| (none) | all of it | Runs the control plane |
keys generate |
nothing | Writes a new signing key |
keys |
all of it, then the signing keys | Prints the public JWKS and the active key id |
keys rotate |
all of it, then the signing keys | Writes the next key and prints the three-deploy rollout |
agent init |
nothing | Writes a registration, a key pair and a public key set |
agent apply |
nothing | Reconciles registration files against a running control plane |
migrate |
all of it, then the database | Applies the schema |
doctor |
all of it | Checks everything the server would fail on, and says which |
audit-verify |
all of it, then the database | Verifies every audit chain |
Which ones read no configuration is not an accident: a key and an agent registration are made
before there is a deployment to configure, so those commands work on a laptop with nothing set.
Every other command loads and validates the whole configuration first, and doctor runs whether
or not it loaded.
keys generate
Section titled “keys generate”Onbe.Server keys generate --out /run/secrets/onbe/active.pem [--kid <name>]Writes an owner-readable PKCS#8 PEM holding a new P-256 key, refuses to overwrite a file that already exists, and prints the key id with the setting that configures it. Nothing private is printed; the PEM exists only in the file. The key id defaults to the RFC 7638 thumbprint, which changes when the key does, so two keys can never share one.
Prints what a running instance would publish: the public JWKS, then the active key id. Useful for confirming a rotation reached the process you think it did.
keys rotate
Section titled “keys rotate”Onbe.Server keys rotate --out /run/secrets/onbe/next.pemLoads the configured keys exactly as the server would, writes a new key beside them, and prints
the rollout: which setting to add, when to move ActiveKid, and how long to wait before
removing the retired key. The wait is computed from the largest max_token_ttl of any
registered agent, disabled ones included; when the registry cannot be read it falls back to
Onbe:Tokens:DefaultTokenTtl and says so. It changes nothing but the new key file.
agent init
Section titled “agent init”Onbe.Server agent init <agent-id> --out agents/Writes three files: the registration as YAML, its public key set as JSON, and the private key, owner-readable. The first two are meant to be committed; the third belongs in a secret store. It refuses to overwrite anything it would have written, and never prints the private key.
The defaults are deliberately useless — sponsor_required: true, a thirty-minute task, a
five-minute token, depth 1, and empty scope and audience lists — so the file cannot become
dangerous by being applied before somebody decides what the agent may do.
agent apply
Section titled “agent apply”ONBE_ADMIN_KEY=… Onbe.Server agent apply agents/*.yaml --server https://onbe.example.com [--dry-run]Reconciles each file against the control plane: creates what is missing, patches what differs,
and does nothing at all when the file already matches — a second run makes one call, the read.
--dry-run prints what it would change and writes nothing. Every change goes through the admin
API, so it lands in the audit ledger exactly as a hand-made request would.
The key comes from ONBE_ADMIN_KEY or standard input; there is no flag for it. --dry-run
needs no key, which is what runs on a pull request. It never deletes a registration and never
changes enabled: both are decisions for the admin API.
Output is one line per file, then a summary:
created jira-triage
1 file(s), 1 changed.migrate
Section titled “migrate”Applies the schema for the configured provider and prints what it applied. Migrations are never applied at startup: a rolling deploy that migrates on boot is a race between instances and a schema change nobody approved. For the embedded provider it also creates the file and its directory, and puts the database in write-ahead logging mode.
doctor
Section titled “doctor”Onbe.Server doctor [--subject-token -]Runs the checks the server would otherwise fail on, in the order that matters — configuration, signing key, identity provider, database, admin API — and prints a line each, with a likely cause and what to check for anything that failed:
ok configuration Loaded.ok signing key Active kid '9Fy-qRMxKnbHaNBF9nwF68GQVxzhgz3miPA15FHHv38', 1 key(s) published.FAIL upstream The discovery document could not be fetched. Likely cause: nothing is serving http://127.0.0.1:8080/realms/main from here, or the request timed out. Check the host is resolvable and reachable from this pod, and that Onbe__UpstreamIdp__Issuer is the realm URL.ok database Reachable (sqlite), schema up to date.ok admin API Enabled; /admin requires the configured key.
5 check(s), 1 failed.Give it a real user’s access token on standard input — printf '%s' "$TOKEN" | Onbe.Server doctor --subject-token - — and it also checks that token the way an exchange would. Reading it
from standard input keeps it out of the process list, and it is never printed.
audit-verify
Section titled “audit-verify”Onbe.Server audit-verify [<seq>:<hash>,...]Walks every chain of the ledger, recomputes every link, and prints one head per chain as a
comma-separated seq:hash list. Pass that list back and it proves each chain still reaches its
head unchanged — the only way to detect a cut tail — so keep the list somewhere the database
cannot reach and run this on a schedule.
Exit codes
Section titled “Exit codes”| Code | Meaning |
|---|---|
0 |
It worked |
1 |
It failed: a check failed, a file could not be written or was refused by the server, there was no key to rotate, or the ledger could not be read |
2 |
The command line was wrong, or a required argument was malformed |
3 |
audit-verify only: a chain is broken |
The server itself exits 1 on invalid configuration, having printed every problem with it.
© 2026 Onbe