Run TOVIO on a headless host or CI runner
There is no on-disk fallback when the OS keychain is missing — here is what to set, and the fail-closed rules around it.
Written By Dustin
Last updated About 3 hours ago
TOVIO seals a repository's identity secret on disk, inside the repository, at .tovio/identity/default.key. The 32-byte wrapping key that unseals it lives in the operating system keychain — Apple Keychain Services, the Windows Credential Manager, or the Linux secret service — all under the service name tovio. On a server, a container, or a CI runner there is usually no keychain daemon at all.
The thing to correct first
There is no on-disk fallback keystore. TOVIO does not detect a missing secret service and quietly write a wrapped key somewhere else instead; no such backend exists. A silent downgrade of key storage on a build runner would be a security surprise, so the behaviour is deliberate: with no keychain and no configuration, key operations fail with TVO-KEY-002, exit class 11.
The simplest headless setup: don't need keys
A Simple (Tier 0 Solo) repository has no identity ceremony and no keychain requirement. If your CI job only clones, builds and reads, this is the whole answer:
tovio init --mode simpleNote that tovio init with no --mode in a non-interactive shell already picks simple rather than hanging on the prompt.
When you do need an identity: inject a wrapping key
Supply a 32-byte wrapping key as 64 hexadecimal characters, either inline or from a file:
TOVIO_KEYSTORE_KEY=<64 hex chars>TOVIO_KEYSTORE_KEY_FILE=/run/secrets/tovio-keystore-keyTOVIO_KEYSTORE_KEY wins if both are set. The file form takes the same 64 hex characters, and surrounding whitespace is ignored. This value is a CI secret — treat it exactly as you would a signing key. Once it is set, every TOVIO edge on that host — the CLI, the Node bindings, the MCP server — unseals under the same key.
Give each host its own identity, not a copy of yours
The obvious move is to export your identity and import it on the runner. Resist it. tovio key export and tovio key import are the backup-and-restore path for a single principal's root device key — they are explicitly not how you add a second machine. Copying the root onto every runner makes every runner the same principal: indistinguishable in the audit chain, and unrevocable without rotating the root itself.
Use the per-device path instead — tovio device enroll followed by tovio device approve — which gives each machine its own key under one identity, so a compromised runner can be revoked on its own. Reach for key export/key import only when you are genuinely restoring the root device, and remember the export is passphrase-encrypted, so the runner would need that passphrase as a second secret alongside the wrapping key.
Forcing a backend
TOVIO_KEYSTORE=osTOVIO_KEYSTORE=headlessUnset (or auto) is the default: headless if a valid injected key is present, the OS keychain otherwise. os forces the OS keychain and ignores any injected key. headless forces the injected key, and is a hard error if one is absent or malformed.
The fail-closed rules
A misconfigured selection does not fail at startup — it fails loudly at the first real key use, so a command that never touches key material still behaves normally.
Other environment variables worth setting in CI
TOVIO_NO_SYNC suppresses the opportunistic auto-sync persistently, the way --no-sync does for a single command. Add --json to the commands your pipeline parses, and branch on the process exit code rather than on message text — the exit code is the stable contract.