Running Bullpen CLI headlessly (24/7 bots)¶
Bullpen CLI's auth flow is RFC 8628 device-auth — it requires an interactive
browser on first login. For 24/7 bots that don't have a browser, the CLI can
use a portable auth-set workflow only when doctor deploy-auth reports
that the copied credential profile is supported: log in once on a workstation,
copy credentials.json.enc, credential_salt.bin, and keys/ as one set to
your bot host, use the same absolute BULLPEN_HOME, use the same
BULLPEN_USERNAME_OVERRIDE, keep the Bullpen home writable, and require a
green doctor deploy-auth check before unattended operation. The copied bot
profile can refresh while the refresh token remains valid and credential saves
succeed, but there is no guaranteed 30-day unattended window.
If bullpen doctor deploy-auth --output json reports
portability.supported_deployment_profile_available=false, treat the copied
bundle as unsupported for unattended deployment and contact Bullpen support
instead of assuming the bot host can decrypt or refresh it reliably. A scoped
service-account/headless auth surface is still planned.
This page is about running normal CLI commands unattended with portable
credentials. The separate bot runtime and TUI operator controls are preview
features behind the agent-preview build feature, which is off in the default
official release artifact.
Use one stable credential directory on both machines:
The examples below use /var/lib/bullpen-bot. If that path is not writable on
your workstation, choose another persistent absolute path and use that exact
same value everywhere on both hosts.
What gets copied¶
~/.bullpen/credentials.json.enc — AES-256-GCM-encrypted. The encryption
key is derived from a stable embedded secret shared across official CLI
builds, the OS username, the BULLPEN_HOME path, and the per-installation
salt stored in credential_salt.bin.
For the file to decrypt on a different host, the username and home
path components of the key derivation must match, and the matching
credential_salt.bin must be copied with the encrypted files. Two knobs
control the username and path components:
BULLPEN_USERNAME_OVERRIDE— overrides the OS username in the key derivation. Set this to a stable identifier (e.g.botops) on both the workstation and the bot host.BULLPEN_HOME— overrides the default~/.bullpendirectory. Set this to the same path on both hosts (e.g./var/lib/bullpen-bot).
Use an absolute BULLPEN_HOME for bots, containers, cron, systemd, and CI.
Relative values are rejected because they can change under supervisors and
create a different Bullpen profile than you intended. The supported headless
deployment targets for the credential-locking guarantees are macOS, Linux, and
Windows through WSL2. Native Windows PowerShell or Command Prompt is not the
supported unattended auth path yet.
Keep this directory writable for the running bot user. Current builds fail closed if a refreshed session cannot be saved durably, so the command that encounters the storage problem exits with a typed local-auth error instead of letting a later bot process discover stale credentials.
First-time local Turnkey keypair creation is serialized on supported Unix-style
filesystems so concurrent bot startup processes share the same persisted
keypair. Do not place
BULLPEN_HOME on a cloud-sync or network filesystem unless you have validated
locking and rename durability for that environment.
Without these overrides, the credential file is bound to your workstation's OS user and home dir and will fail to decrypt on the bot host.
Setup¶
1. Workstation (interactive login)¶
export BULLPEN_USERNAME_OVERRIDE=botops
export BULLPEN_BOT_HOME=/var/lib/bullpen-bot
export BULLPEN_HOME="$BULLPEN_BOT_HOME"
mkdir -p "$BULLPEN_HOME"
chmod 700 "$BULLPEN_HOME"
bullpen login # opens browser, completes device-auth
bullpen doctor auth # confirm Token + Refresh Token both Valid
bullpen doctor deploy-auth --output json # confirm supported deployment profile
2. Bot host (headless)¶
# Match the workstation's overrides exactly.
export BULLPEN_USERNAME_OVERRIDE=botops
export BULLPEN_BOT_HOME=/var/lib/bullpen-bot
export BULLPEN_HOME="$BULLPEN_BOT_HOME"
mkdir -p "$BULLPEN_HOME"
chmod 700 "$BULLPEN_HOME"
# Copy the encrypted credential file, its salt, and the Turnkey keypair over.
# If you changed BULLPEN_BOT_HOME, use that same path in these source paths.
scp workstation:/var/lib/bullpen-bot/credentials.json.enc "$BULLPEN_HOME/"
scp workstation:/var/lib/bullpen-bot/credential_salt.bin "$BULLPEN_HOME/"
scp -r workstation:/var/lib/bullpen-bot/keys "$BULLPEN_HOME/"
# Verify decryption + token health.
bullpen doctor auth
bullpen doctor deploy-auth --output json
If doctor auth shows Token: Valid and Refresh Token: expires in N
days, and doctor deploy-auth --output json reports
portability.supported_deployment_profile_available=true, the copied bundle is
decryptable and usable for this deployment profile. Continue monitoring the
refresh-token lifetime as described below.
In the deploy-auth JSON, also inspect these fields:
home.absolutemust betrue.home.canonical_pathshould match the intended Bullpen home.home.writablemust betrue;home.writability_checkexplains that this is derived from filesystem metadata, not from a write probe.home.filesystem_checkis conservative. Current builds do not prove NFS, cloud-sync, Docker volume, or host lock semantics here; use a local persistent disk for unattended bots unless your deployment validates locking and rename durability separately.
3. Refresh-token rotation¶
The bot's refresh token has a ~30-day TTL. Authenticated commands refresh the
access token as needed. During refresh, the server may rotate the refresh token
by returning a protobuf refresh_token response field; when present, the CLI
persists that rotated value. When the response field is empty, the CLI keeps
the existing stored refresh token.
That means rotation is not guaranteed on every refresh. For unattended bots,
keep credentials.json.enc writable and run at least one authenticated command
regularly so any server-provided rotated token can be saved. Use doctor auth
to monitor the remaining refresh-token lifetime and re-login before expiry if
the expiry is not extending. The source of truth is
crates/bullpen-core/src/auth/token_session.rs::refresh_access_token.
Check days remaining at any time:
If the refresh token does expire, repeat the workstation step (run
bullpen login interactively) and re-copy the full encrypted auth set:
credentials.json.enc, credential_salt.bin, and keys/.
Security tradeoffs¶
The portable credential file gives the bot host full account power — it can place trades, withdraw funds (within the existing CLI's restrictions), and access the same surface as the human operator.
- Lock down file permissions: the credential file is
0o600by default.credential_salt.binandkeys/are also part of the portable auth set. Make sureBULLPEN_HOMEis on a filesystem only readable by the bot's OS user. - Don't share
BULLPEN_USERNAME_OVERRIDE-bound files between distinct bots. Each independent bot identity should run a separatebullpen logincycle with its own override identifier. - Don't commit the credential file to git. It's encrypted, but the embedded secret is shared across all CLI users — anyone who can also guess your username override + bullpen home can decrypt it.
- Rotate the override + re-login if you suspect the file has leaked.
A scoped service-account/headless auth surface is planned so you can mint scoped, revocable credentials from a workstation and use them headlessly without copying any files. Until then, the portable credential workflow is the recommended path.
Troubleshooting¶
Bundle: NOT decryptable — keypair mismatch detected→ theBULLPEN_USERNAME_OVERRIDEorBULLPEN_HOMEdiffers between the workstation and the bot host. Double-check both env vars and use an absoluteBULLPEN_HOMEon both machines.Token: expired; refresh token expired; run: bullpen login→ the refresh token TTL elapsed. Re-run the workstation login + re-copy. If diagnostics mentioncredentials.json.encorcredential_salt.bin, preservebullpen doctor auth --output jsonfirst and do not reset local auth state until diagnostics or support confirm it is unrecoverable.bullpen doctor authreportsCredentials: Not found→ the credential file didn't make it across, orBULLPEN_HOMEis pointing somewhere else.
End.