Skip to main content

Install the platform

This page takes you from a clean machine to a local OktoID deployment that is complete for this version. You set it up in a browser, through the setup wizard, one step at a time. Each step writes something, then proves it with checks before the next step opens. At the end the setup service has stopped, its address answers 410 Gone, and the deployment has an identity provider, an anchor organization, a proven security module, an identifier for the anchor and a trust tree with the anchor at its root.

There is a second way in. pnpm stack:up without --setup takes every answer from the answer file and sets the stack up without asking, which is what Install and sign in uses. A stack is set up one way or the other, never both. Use this page when a person should see and confirm each decision.

Before you start​

You need a machine prepared as the repository's docs/development/getting-started.md describes:

  • Node 24 and pnpm, with pnpm install run in the repository;
  • Docker with Compose;
  • the names *.local.oktoid.app resolving to 127.0.0.1;
  • a browser that trusts the stack's development root (the next section shows where it is).

Run every command on this page from the repository root, as your own user. The installer refuses to run as root (uid 0): the setup files would then belong to root and the setup service would be given the root group.

The stack must never have been set up. If it ever ran, start from an empty one:

pnpm stack:reset

A reset deletes everything, including the development root. Trust the new one after the next start, as Install and sign in shows.

The wizard reads its first answers from the deployment's answer file, infra/environments/local/answers.yaml, and shows them on its screens for you to confirm or change. What you change is stored by the platform, not written back to the file.

Start the stack with the setup wizard​

pnpm stack:up --setup

The first start builds the platform images and takes several minutes. Near the end it says which way the stack is being set up, and why:

setup mode: wizard (no setup exists yet and --setup was given)
bootstrap (the unattended converge) was not run: the wizard converges

It ends with the usual addresses, then two more lines, then the commands that trust the development root:

setup: https://setup.local.oktoid.app/
sign-in code: the file <repository>/infra/compose/.state/local/setup/sign-in-code (mode 0600). It is never printed; read it where you can trust the screen.

The sign-in code is the only thing that lets anyone into setup, and setup can create the deployment's accounts. So the installer never prints it: it writes it to a file that only your user can read. The code works once, and for 24 hours.

Sign in to setup​

Read the code where nobody can see your screen:

cat infra/compose/.state/local/setup/sign-in-code

It is 52 characters, the letters A to Z and the digits 2 to 7.

Open https://setup.local.oktoid.app/. The page is Sign in to setup. Paste the code into Sign-in code from the installer and choose Sign in. Letter case, spaces and hyphens do not matter.

Signing in spends the code and deletes its file. From now on you hold a setup session:

  • it ends after 30 minutes without activity, and after 4 hours however active you are. Two minutes before the idle limit, the page asks whether to stay signed in;
  • when it ends, make a new code with pnpm stack:setup-code and sign in again. Setup opens at the step where it stopped, and nothing you saved is lost.

Five wrong codes stop setup. The fifth is answered with Too many wrong codes. Setup has stopped, and the service exits. From then on the setup address shows Setup is not running, and reloading it does not start the service. Run pnpm stack:setup-code, as that page says, or pnpm stack:up again: either writes a new code and starts the service, and nothing you saved is lost. If the five attempts were not yours, someone else can reach this address; stop and find out who before going on.

If the page says the code has already been used and you did not use it, someone else did. Stop, and tell whoever is responsible for the machine's security.

How every step works​

The wizard's steps carry the full setup's numbers, 1 to 12. This version has 1 to 5, 8, 9, 10 and 12; the list on the left says which numbers later versions add, and the numbers never change.

Each screen has the same shape:

  • The form shows what the step will write, filled in from the answer file. A secret is never typed: a secret field takes a reference to the secret store, such as secret://oktoid/mail/smtp, and setup refuses a value.
  • The action (its label differs per step) writes the step and runs its checks. The checks appear one by one as they finish.
  • The checks each prove one thing and show Passed, Failed, Needs attention, Waiting, Not run or, when a run was cut off, Interrupted. When a check fails, a banner names it, the row opens with what setup expected, what it got, the cause, and what to do. The checks after it show Not run.
  • Continue opens only when every check of the step has passed. Step 12 is the exception, and says why.

A finished step is read-only. You can open it again from the list on the left to read it, but setup will not change what it wrote.

Step 1: verify the identity provider​

The page is Verify the identity provider. On a local stack the provider is Keycloak, and the connection panel says Keycloak, deployed by the installer. This step only reads: nothing is written to the provider or to the platform.

Choose Run the checks. Two checks run:

CheckWhat it proves
The identity provider answered, and accepted the administrator credentialThe platform's own network reaches the provider, and the credential setup was given works
What setup may do in the provider was readWhich of the four management permissions the credential holds: with all four, setup creates everything itself

On a stack that was never set up, the second check passes and says that the credential may create the realm, which does not exist yet: what it may do inside the realm is checked when step 2 creates it. That is the expected result here, not a warning.

When both pass, choose Continue.

If the provider does not answer, the cause is provider_unreachable. Setup reaches the provider from inside the stack, not from your browser, so an address that opens in your browser can still be closed to the platform. Check the provider's log with pnpm stack:logs keycloak and run the checks again.

Step 2: claims, mail, clients and the root account​

The page is Claims, mail, clients and the root account. This is the first step that writes, and it writes only to the identity provider: the realm, the claim mappings, the mail settings, the six clients and the root account, the person who runs this deployment.

Check the root account's Full name and Email address (root@local.oktoid.app on the local stack). The box One person runs and governs this stack is ticked, because the local answer file gives the root account the SOLE_ADMIN bundle. Only a development stack offers this box; under staging and production it does not exist and two people are required.

Choose Apply this step. Four checks run: the two from step 1 again, then:

CheckWhat it proves
The realm, the claims, the mail settings and the clients were written and read backEverything this step wrote is at the provider as setup wrote it
The root account was created and its invitation sentThe account exists, and the mail server took the invitation

The invitation goes to Mailpit, which catches every message on a local stack: http://127.0.0.1:18025. You do not need to follow it now. Step 12 reads whether the root account has signed in, and setup can be completed before it has; Install and sign in, steps 2 and 3, shows how to follow the invitation.

If the root account exists and the invitation was not sent, the page says so and offers Send the invitation again. Everything else in the step was written, and you can continue.

Choose Continue.

Step 3: name the deployment and create the anchor​

The page is Name the deployment and create the anchor. The anchor is the organization at the root of the deployment's registry: every other organization sits beneath it. This step writes it, the root account's grant on it, and a grant for each machine client that acts for it. Nothing is published yet.

FieldOn the local stack
Deployment nameoktoidPermanent. Lowercase letters, digits and hyphens: it becomes the first part of every path and part of every certificate name
Domainlocal.oktoid.appPermanent. It is inside every credential the deployment issues
Legal name of the anchor organizationOktoID LocalShown as the trust anchor's name. Its spelling can be corrected later
This deployment runs with no connection to outside networksnot tickedAir-gapped. Step 8 then refuses a public ledger

The deployment name and the domain cannot be changed after this step without reissuing everything made from them. Read them twice.

Choose Create the anchor. A dialog asks Create the anchor with these permanent values? Type the deployment name, oktoid, and choose Create the anchor again. Two checks run:

CheckWhat it proves
The deployment's answers are complete and validThe answers as a whole pass the answer file's rules
The anchor organization, the root account's grant and the clients' grants were writtenThe registry holds them

Choose Continue.

If a run is cut off, for example because the service restarted, the checks that were being made show Interrupted, not Failed: they have no result. Choose Run the step again; nothing is written twice. The name and domain stay fixed on that second run, because the organization may already exist.

Step 4: create the first accounts​

The page is Create the first accounts. It shows the root account, read-only: it was created at step 2. It may create one more account, the governing account: the trust anchor administrator, who admits organizations to the trust tree, sets policy and signs statements, and has no access to infrastructure.

On a deployment that keeps duties apart, the governing account is required, and it must be a different person from the root account, with their own address. The root account runs the platform and cannot govern it; a permission check refuses it.

On the local stack the root account also governs, so the panel says Optional on this stack. Choose one:

  • Continue without one. The root account governs. The step's audit record says the governing account was skipped, and in which environment.
  • Give a Full name and Email address and choose Create the governing account. Three checks run: somebody is named who can govern; the governing account was created and granted; its invitation was sent. The invitation arrives in Mailpit.

Then choose Continue.

Step 5: connect the security module​

The page is Connect the hardware security module. The checks run as soon as the page opens. On the local stack the module is SoftHSM, a software module that stands in for hardware.

What this step proves is the module that credential (the service that will hold the deployment's signing keys) signed in to when it started: setup sends credential a request, and credential answers from its own session. The setup service holds no module PIN and opens no session of its own. So this step needs credential running.

CheckWhat it proves
The PKCS#11 library was loadedcredential loaded the module's library
A session was opened and signed incredential holds a signed-in session on its token
The module's identification was readThe module reports its manufacturer and model
A test key was generated, used to sign, verified and destroyedThe module can make a key and sign with it, and the test key is gone afterwards

The module shows the PKCS#11 library path and the slot, the token's label as credential's configuration names it. Under the checks, Recorded about the module shows the Identification the module reported and the Key store as tested and available, and Partitions lists the stack's three tokens (oktoid, oktoid-agent, oktoid-audit) with what each holds. Found means the module listed that token. Not looked for means the library credential loaded does not see it, which is normal for SoftHSM, and says nothing about whether it exists. Setup cannot change the partitions in this version.

When all four pass, choose Continue.

If the session check fails because the PIN was refused, the page offers no Run the checks again: credential signs in only when it starts, and never sends the PIN again for a check, so repeated checks cannot lock the module. Correct the PIN reference in credential's configuration and restart credential; the checks then run again when you reopen the page.

Step 8: verifiable data registries​

The page is Verifiable data registries. It decides how identifiers are published, and it runs no checks: Continue saves the choices, and step 9 applies them.

  • The anchor's identifier uses did:webvh, which keeps a signed history of every change. It is fixed once created: every organization's trust chains to this identifier, so a change of its key must be provable. Setup offers no other method for the anchor.
  • Identifier methods for organizations decides what organizations admitted later may choose. did:webvh is always on and preselected. did:web is also on when the step opens, unless the answer file's identifiers.methods leaves it out; the local answer file does not set it. Read the did:web card before you keep it: it keeps no history and has no key pre-rotation, so nobody can tell a key change the organization made from one made by someone who took over its domain. To turn it off, clear the did:web card before you choose Continue. Turned off, did:web is refused for every organization that asks for it; identifiers that already exist keep their method.
  • Base address for identifiers is inside every identifier minted here.
  • Public ledgers (cheqd, Hedera) arrive in a later version and are refused until then.

The base address and the anchor's method become permanent at step 9, and the methods card becomes read-only then. Choose Continue.

Step 9: bring up the anchor's root agent​

The page is Bring up the anchor's root agent. It shows what it takes from earlier steps, how wallets are stored (one store with a profile for each organization, set once for the deployment), and what it creates: the anchor's place in the shared agent, its identifier keys, generated in the module and never exported, and the anchor's identifier document, published on your domain with its history, and also as did:web for verifiers that read only that form.

Three values become permanent here: the identifier method, the base address and how wallets are stored. Choose Bring up the agent, type the deployment name, oktoid, and choose Bring up the agent again. Four checks run:

CheckWhat it proves
The identifier methods and registries are declared, and the anchor's method is permittedStep 8's answers are complete and consistent
The shared agent was registered, or found registeredThe agent that holds the anchor's keys is known to the platform
A test object was written to each registry and read backEach registry takes a write and serves it
The permitted methods were stored, and the anchor's identifier publishedThe identifier exists and is published

The last check's detail names the methods organizations may use: methods webvh, web when did:web was left on at step 8, methods webvh when it was turned off.

The page then shows the anchor's identifier as published: did:webvh:<scid>:oktoid.identity.local.oktoid.app. Setup resolved it with the platform's own resolver. For a check by a resolver OktoID did not write, run this on the server, with the identifier the page shows:

pnpm oktoid resolve-identifier did:webvh:<scid>:oktoid.identity.local.oktoid.app

Create an identifier explains what it prints.

Choose Continue.

Step 10: found the trust tree​

The page is Found the trust tree. It admits the anchor to its own trust tree and publishes version 1 of the governance framework: the document that says who governs the deployment and what each role must meet to be admitted. It comes after step 9 because the framework names the anchor by the identifier step 9 created; setup fills the identifier in, and it is never typed.

What the anchor publishes about itself:

  • Contact mailbox: at least one, at most eight. Each is published as written, so use a mailbox for a role, never a person's address. The local stack's is trust@local.oktoid.app.
  • Where a dispute is taken: an https address, published in the framework.

The governance framework, version 1: on the local stack the form is filled in from the framework the answer file names, infra/environments/local/framework.v1.yaml. When the answer file names none, setup proposes a first version from the anchor's name and its first contact; edit it to say what your governing authority has agreed.

Version 1 is published once and never edited. A change is a new version, made outside setup.

Choose Found the trust tree. Three checks run:

CheckWhat it proves
The anchor's contacts, dispute route and framework are all declaredThey are given together, as the framework requires
The framework was read and is a valid frameworkIt passes the framework's schema
The anchor was admitted to its own trust tree, and framework version 1 publishedThe anchor's signed statement about itself and the framework are public

Choose Continue.

Step 12: publish and self-test​

The page is Publish and self-test. Each earlier step checked its own work when it finished; this one reads what setup made once more, from outside where it can, and then records the configuration as applied. Until that record is written the step is open and setup cannot be completed.

Choose Run the self-test. Six checks run:

CheckWhat it proves
The stored answers are the configuration's authorityThe answers setup holds, not the answer file, are what the deployment converges from
The stored configuration is the answers setup holdsThe newest stored configuration version is what you confirmed on the screens
The anchor's trust statement was fetched through its published address and verifiedFetched the way another organization would fetch it, and its chain and governance framework verified. It is self-asserted: the anchor is its own trust anchor
The anchor's identifier was resolved by the platform, from the registry it publishes fromThe identifier resolves. This is the platform checking itself; the page names the independent check, as step 9 does
The root account signed in for the first timeRead from the platform. It shows Waiting until the root account accepts its invitation
The configuration was recorded as applied, and its audit record acknowledgedThe deployment holds this configuration, and the audit trail took the record

The last check runs only when none before it failed. Two results do not hold the step open:

  • The root account's sign-in shows Waiting until the person the step 2 invitation names sets a password and a second factor and signs in once. Setup cannot do that part. How the invitation is accepted, under the row, lists what that person does. You can complete setup without it; after they sign in, Run the checks again reads the sign-in back. That second run writes nothing.
  • The trust statement shows Needs attention when its address did not answer. That says nothing about whether it verifies. Run the checks again once the edge answers, or run the command the banner names on the server, before you complete setup.

When a check fails, the configuration is not recorded as applied and the step stays open. For a failure of the trust statement or of the identifier, the banner offers Open step 10 or Open step 9. Fix the cause and choose Run the self-test again.

Once the step is done, Published lists the anchor's trust statement, its identifier document, and the same document as did:web, each Live when this run read it back. Step 12 is the last screen, so it has no Continue: its main action is now Complete setup.

Complete setup​

Choose Complete setup on step 12. The dialog Complete setup for oktoid? says that the deployment becomes operational for what this version has, and that setup closes: this address stops answering. When the root account has not signed in yet, the dialog says so too: nobody can run the deployment until that person accepts the invitation and signs in, and once setup closes this page can no longer send the invitation again. If the invitation may need sending again, choose Not yet and do it from step 2 first; after completion, pnpm stack:root-invite sends it again from the server. Otherwise choose Complete setup.

The page Setup is complete for this version lists what was set up and the step that did each, what later versions add (the certificate authority and the key ceremony, the document signer certificate, the credential catalog, a separate platform operator account, the full self-test and the seal), and what happens next. It is shown once: reloading it shows only that setup is closed.

Completion happens in a fixed order, and the first two are what close setup:

  1. The platform records that setup is complete.
  2. The setup service refuses every request from then on, the sign-in included.
  3. It writes a marker for the edge, then exits.
  4. The edge answers 410 Gone on the setup address.

Check both from the terminal:

curl -sS -o /dev/null -w '%{http_code}\n' \
--cacert infra/compose/.state/local/ca/root.pem https://setup.local.oktoid.app/
docker ps -a --filter name=oktoid-local-setup --format '{{.Names}} {{.Status}}'

The first prints 410. The second shows the setup container as Exited (0).

Then check the whole stack:

pnpm stack:verify

If its line http://api.local.oktoid.app/ redirects to HTTPS fails with no answer on port 80, the edge container is the cause, not setup. pnpm stack:up checks ports 80 and 443 and restarts the edge once when one does not answer, so look back at its output for a WARNING: port ... still does not answer line. The repository's docs/development/troubleshooting.md has the entry, under TLS, with the restart to run by hand.

From now on pnpm stack:up does not start the setup service. It says so:

setup mode: unattended (setup is complete; the stored configuration is converged and the setup host is not started)

and converges the stored configuration, not the answer file. To change that configuration as a file, export it and import the edited copy, as the repository's docs/development/local-stack.md describes under The setup service. pnpm stack:up --setup is now refused with exit 2: reopening setup is its own command.

When a later version adds a step​

Setup opens again only when you install a version of the platform whose setup has a step this deployment has not done, and only by a command you run:

pnpm stack:setup-reopen

The command compares the version that completed setup with the version you installed. If the installed one adds nothing, it refuses with exit 78, names both versions, and writes nothing. Otherwise it records the reopening in the audit trail first, then writes a new sign-in code and prints only its file's path:

sign-in code: <repository>/infra/compose/.state/local/setup/sign-in-code (mode 0600; installer run <id>)

Sign in as in Sign in to setup. The page is Setup was reopened at step n: the steps done before are listed as done and are not changed; the new steps are marked New in this version. The deployment stays operational while they are open. Complete setup again when they pass.

When something goes wrong​

You seeIt meansDo this
setup is complete (the marker ... exists) and exit 2 from pnpm stack:up --setup, at onceSetup is completeNothing, or pnpm stack:setup-reopen after installing a version that adds a step
stack:up prints sign-in code: not changedA code was used and setup is not complete, so no new code is minted while someone may be mid-wizardIf your session ended, pnpm stack:setup-code
Your setup session ended after 30 minutes without activity, or reached its 4-hour limitThe session is over and the code is spentpnpm stack:setup-code, then sign in again
Too many wrong codes. Setup has stopped, or Setup is not running at the setup addressFive wrong codes stopped the servicepnpm stack:setup-code or pnpm stack:up for a new code; either starts the service again
This code has expiredIt is more than 24 hours oldpnpm stack:up for a new code
This code was not issued by an installer commandThe code file was not written by the installerDo not use it. Run pnpm stack:up, and if you did not put that file there, tell your security contact
This step is not open yetA step before it is not doneSetup returns you to the step to do now
This step is done and cannot be changed hereA finished step is read-only in setupExport the stored configuration, edit it, and import it (docs/development/local-stack.md)
Another run is still in progressA step is runningWait; the page shows its checks as they finish
stack:setup-code refused with exit 2No setup was started, or it is completepnpm stack:up --setup to start one; pnpm stack:setup-reopen to reopen one

What to do next​