feat(secrets): env-var resolution for *_file paths and string fields#34
Merged
Conversation
Operators driving rota.yaml from a secret manager (Doppler, Vault
agent, systemd LoadCredentialEncrypted=, plain compose env_file)
no longer have to pre-stage plaintext files on disk. Two patterns:
* Every `*_file:` field accepts the path-prefix sentinel `env:NAME`.
When set, rota reads the named env var as the secret value instead
of opening a file. Existing real paths keep working unchanged.
Applied to namecheap.api_key_file, cloudflare.api_token_file,
email password_file, webhook bearer_token_file, ACME hmac_key_file,
surrealdb audit password_file. Skipped for ACME
account_credentials_file because that is a read-write persistent
state file, not a secret reference.
* Every operator-set String field gets `${VAR}` interpolation
against the process environment at config-load time. Multiple
refs per string are supported. Applied via RotaConfig::resolve_env
to namecheap.username, namecheap.api_user, namecheap.client_ip,
acme.directory_url, acme.contact_email, surrealdb audit
endpoint/namespace/database/username, alert email
smtp_host/username/from/to[], alert webhook url.
An unset referenced variable returns Error::ConfigInvalid at startup
rather than silently letting a downstream API call go out with an
empty value.
Implementation lives alongside existing redaction in rota_core::secrets:
read_secret(path) and expand_env(s). RotaConfig::load calls
resolve_env after serde parse so consumers see fully-resolved values.
Tests: 8 new unit tests in rota-core covering file read with trim,
env-prefix dispatch, missing-env error, ${} interpolation single +
multiple, passthrough, unterminated-brace error.
Docs: new "Secrets and environment variables" section in
book/src/configuration.md showing the Doppler, systemd, Vault, and
plain compose patterns. rota.example.yaml header gains a usage
block plus an alternate namecheap example.
albedosehen
added a commit
that referenced
this pull request
May 9, 2026
The docs workflow had been silently failing at the actions/configure-pages@v5 step on every push to main since PR #31 introduced it (PRs #31, #32, #34 all red on this leg) because Pages was never enabled at the repo level. The site at oneiriq.github.io/rota/ was returning HTTP 404 the whole time despite the docs PRs claiming it was shipping. Manually POSTed to /repos/Oneiriq/rota/pages with build_type=workflow to unblock the current state, and run 25601961989 confirms the workflow now builds and deploys end-to-end (oneiriq.github.io/rota/ returns 200 with the mdBook content). Adding enablement: true here as the durable fix so a future fork or a repo where Pages gets disabled doesn't silently regress to the same hidden 404 state.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Operators driving rota.yaml from a secret manager (Doppler, Vault agent, systemd
LoadCredentialEncrypted=, plain composeenv_file:) no longer have to pre-stage plaintext files on disk.Two patterns
*_file:paths accept the sentinelenv:NAME. When set, rota reads the named env var as the secret value instead of opening a file. Existing real paths keep working unchanged.Applied to:
namecheap.api_key_filecloudflare.api_token_filepassword_filebearer_token_filehmac_key_filepassword_fileNOT applied to ACME
account_credentials_filebecause that's a read-write persistent state file, not a secret reference.Stringfields get${VAR}interpolation at config-load time. Multiple refs per string are supported.Applied via
RotaConfig::resolve_envto all operator-set strings: namecheap (username,api_user,client_ip), acme (directory_url,contact_email), surrealdb audit (endpoint,namespace,database,username), alert email (smtp_host,username,from,to[]), alert webhook (url).Failure mode
Unset referenced variable ->
Error::ConfigInvalidatRotaConfig::load. Misconfigured deploys fail loud at boot rather than silently calling Namecheap with an empty key.Implementation
Lives alongside existing redaction in
rota_core::secrets:read_secret(path)(sync, used at startup; file reads here are <1KB)expand_env(s)(${VAR}interpolation)RotaConfig::loadcallsresolve_envafterserde_yamlparse so backends see fully-resolved values.Tests
8 new unit tests in rota-core: file read with trim, env-prefix dispatch, missing-env error,
${}interpolation (single + multiple), passthrough, unterminated-brace error. Total: rota-core 40 / rota-daemon 106 / rota-cli 5, all passing.Docs
New "Secrets and environment variables" section in
book/src/configuration.mdshowing Doppler, systemd, Vault Agent, and plain compose patterns.rota.example.yamlgains a usage block + alternate namecheap example.CI verified locally
cargo fmt --all --checkcleancargo clippy --workspace --all-targets -- -D warningscleancargo test --workspace --locked151 tests pass