Repository navigation
Add void platform keys to save the generated platform keys #430
Description
Problem
Since #424, an interactive void platform install generates the JWT signing key and the project encryption key, and keeps them only in this computer's encrypted recovery data. Installs with email also generate the email signing key there. No command prints these keys. Cloudflare does not show Worker secret values after they are set, so the platform cannot give them back either.
Operators need the keys later:
| Scenario | Today |
|---|---|
| Upgrade or repair from CI after an interactive install | The job fails: missing … VOID_PLATFORM_JWT_SECRET … |
| A second operator or a new computer | Same error |
config auth recover after the installing computer is lost |
Recovery needs the saved keys, so it cannot run |
| Email-enabled install upgraded from CI | Already fails today: the email key was always generated |
docs/guide/platform/installation/maintenance.md asks users to keep the keys for disaster recovery, but users who let Void generate them have no way to get a copy.
Decision
Add one read-only command, and let config auth recover read the keys from the environment.
installing computer CI / another computer
recovery.enc (keychain key)
│ void platform keys team | pbcopy
└──▶ password manager / protected CI env ──▶ VOID_PLATFORM_JWT_SECRET
VOID_PLATFORM_PROJECT_SECRET_KEY
VOID_PLATFORM_EMAIL_SIGNING_SECRET
├─▶ upgrade / repair / enable (already read env)
└─▶ config auth recover (new: reads env too)
Design
void platform keys [id]
- No flags.
--plan, other flags and a second argument are errors, as foruseandstatus. - Selection works like the lifecycle commands. The picker shows only when stdin and stdout are both terminals. With stdout piped and more than one installation, the command reuses
platform: Multiple local installations exist; specify an installation id. - It stops before it reads anything when stdin is not a terminal:
platform: Run \void platform keys` in an interactive terminal. Automated jobs read these keys from their secret store.` This guard stops accidents in CI logs and agent shells. It is not access control (code comment). - It reads
readInstallationRecoverySecrets(manifest), which is the pending copy while an install is unfinished. If the install has not created its keys yet:platform: <id> has not created its keys yet. Continue with \void platform install --resume --name `.`
stdout: only dotenv lines, which util.parseEnv reads back exactly.
VOID_PLATFORM_JWT_SECRET=<value>VOID_PLATFORM_PROJECT_SECRET_KEY=<value>for a single v1 key (or a legacy saved key). For a multi-version keyring:VOID_PLATFORM_PROJECT_SECRET_KEYS_JSON='<json>'andVOID_PLATFORM_PROJECT_SECRET_ACTIVE_KEY_VERSION=<v>.VOID_PLATFORM_EMAIL_SIGNING_SECRET=<value>, only when it is saved.- Values with only
[A-Za-z0-9+/=_.:-]print bare; others are single-quoted. A supplied value with'or a line break stops withplatform: The saved <name> cannot be printed as a dotenv line. Use the copy you supplied when installing. - It never prints the database URL, Cloudflare, R2, GitHub, setup or app-access tokens, or the recovery key. The user supplied those during setup.
stderr (written with process.stderr.write only, so the pipe stays clean)
Anyone with these keys can act as <Display Name>'s internal services, and Void cannot replace the signing or encryption key if a copy leaks. Save them only in your password manager or a protected CI environment.- Missing keys:
This computer has no saved <JWT signing key|project encryption key>. Use the copy you supplied when installing. - None saved (exit 1):
platform: This computer has no saved keys for <id>. Run this command on the computer that installed the platform.
Help and picker
void platform --help:keys [id]—Print the signing and encryption keys to save a copy, afterdiscover. Add the same entry to the interactive platform picker.void platform keys --helpexamples:void platform keys team | pbcopy(copy to the clipboard) andvoid platform keys team | gh secret set -f - --env team(save as secrets of a protected GitHub environment). Do not show a repository-widegh secret set: every collaborator with write access can use repository secrets.
Install output
- The generated-keys info line adds
Save a copy with \void platform keys `.` - When stdin is a terminal, every successful install (also
--resume) printsSave the platform keys in your password manager: \void platform keys ``.
Errors that point to the command
- The missing-Worker-secret error (
platform-installer-secrets.ts) addsOn the computer that installed the platform, \void platform keys ` prints the signing and encryption keys.` when a JWT, project or email key is missing. config auth recoveruses saved values first, thenVOID_PLATFORM_JWT_SECRETand the project key variables, with the installer's existing checks. The project-key decrypt check stays, so a wrong key fails before any change. New error:platform: Recovery needs this installation's signing and encryption keys. Run it on the computer that installed the platform, or set the VOID_PLATFORM_* lines printed by \void platform keys`.If the JWT came from the environment, print one line before the browser opens: a login failure after this step usually means a wrongVOID_PLATFORM_JWT_SECRET`.config auth protectionkeeps its behavior. Its error says:platform: Change protection on the computer that installed the platform; it uses that computer's saved Access setup.
Docs
docs/reference/cli/platform-installation.md: newvoid platform keyssection.credentials.md(signing and encryption keys): "Save a copy after installing withvoid platform keys <id>" becomes the main path. The openssl steps stay for non-interactive installs. Set these variables only for that installation's job, not in a shell profile.maintenance.md,ci.md,administration/access.md: point to the command.packages/void/skills/void/SKILL.md: agents never print platform keys; they ask the user to runvoid platform keys.
Not doing
- Print keys once at install: install output ends up in bug reports, and a missed print is gone for good.
- Encrypted bundle export/import: much larger, and adds a passphrase.
- Read the keys back from Cloudflare: Worker secret values are not readable after they are set.
- Allow it without a terminal: headless installs supply their own JWT and project keys, and CI logs would keep the output.
- Go back to asking for the keys: brings back two openssl steps for every interactive install.
Known limits
- A leaked JWT or project key cannot be replaced with Void today. This needs its own issue.
- On a computer with no saved recovery data,
VOID_PLATFORM_EMAIL_SIGNING_SECRETin the environment counts as a rotation, and nothing checks that it matches the live value. Use one CI environment per installation.
Tests
- v1 keyring → exactly the JWT and project key lines;
util.parseEnv(stdout)returns the saved values. Multi-version keyring and legacy key forms round-trip too. - The email line appears only when saved.
- Marker values for every other recovery field and the recovery key never appear in stdout or stderr.
- stdin not a terminal → error before any recovery read, nothing on stdout.
- Two installations with stdout piped and no id → the "specify an installation id" error, nothing on stdout.
- Nothing saved → exit 1. Only the email key saved → its line, plus stderr naming the missing keys. Unfinished install → the resume message.
- A supplied JWT with
'→ the cannot-print error. - Argument parsing, help pages, the install success line (TTY only, also on resume), and the missing-Worker error naming the command only for key variables.
config auth recover: saved values win over env; env single key becomes{v1}; a half-set keyring pair is rejected; no keys and no env → the new error before any database connection.
Release
#424 makes interactive installs generate keys. Ship this command in the same release. If that release goes out first, its notes say: "If you installed a platform with this version, run void platform keys <id> on that computer and save the output."
🤖 Generated with Claude Code
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Activity
fix: use the Void Cloudflare login in dev and unblock self-hosted pla…