Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

CLI Reference

secret-manager <COMMAND>

Declarative secret management for Nix store repositories.

Commands

github app — GitHub-issued App credentials

enroll serves a local manifest form and callback on 127.0.0.1. Open the printed URL in an authenticated GitHub browser, register the app, then authorize its selected-repository installation. The conversion response is encrypted before parsing or publication; the CLI never generates an app private key.

secret-manager github app enroll --config review-app.json \
  --store /path/to/canix --transaction /private/state/review-app \
  --identity age/master_identities/master_nitro3c_identity.pub

The transaction’s parent directory must already exist. The CLI creates the transaction directory with mode 0700; files use 0600. Keep it outside tracked source trees. The manifest callback must complete within one hour; the default listener deadline is 900 seconds (--timeout-seconds, maximum 3500). --port chooses a loopback port. Retries reuse the original port and policy/store binding.

The public policy is JSON:

{
  "version": 1,
  "slug": "example-review",
  "owner": "example",
  "repository": "example/project",
  "homepage": "https://github.com/example/project",
  "source": "age/secrets/review-app.age",
  "recipients": ["age1..."],
  "permissions": {
    "contents": "read",
    "pull_requests": "write",
    "checks": "write"
  },
  "keySecretName": "COMMITPERCLIP_KEY",
  "appIdVariable": "COMMITPERCLIP_APP_ID",
  "appSlugVariable": "COMMITPERCLIP_APP_SLUG"
}

Use real public age recipients. The app owner must match the repository owner; permissions must match the declaration, with only GitHub’s automatic metadata: read permitted additionally. The issued slug must match the declared slug. If GitHub assigns a different slug, retain the encrypted checkpoint and import a settings-issued key with a corrected policy in a fresh transaction.

For an already registered app or a settings-issued replacement key:

secret-manager github app import --config review-app.json --store /path/to/canix \
  --transaction /private/state/review-app --app-id 12345 --pem-file /private/key.pem
secret-manager github app installation --config review-app.json \
  --store /path/to/canix --transaction /private/state/review-app
secret-manager github app publish --config review-app.json --store /path/to/canix \
  --transaction /private/state/review-app --identity /private/age-identity
secret-manager github app verify --config review-app.json --store /path/to/canix \
  --transaction /private/state/review-app --identity /private/age-identity

import requires an operator-owned private PEM file and verifies its JWT against GitHub /app. It retains the original file; remove it through your normal private credential handling after encrypted adoption. App-key generation/revocation is a GitHub settings operation; this CLI automates adoption and publication.

If an import stops after saving issued.age but before saving its digest, retry with the same transaction, PEM and age identity. Recovery binds the retained ciphertext to the pending transaction and checks the original app identity and key fingerprint before completing adoption. A recorded digest mismatch or an unbound checkpoint in a completed transaction is refused.

installation prints the authorization URL. With --installation-id, it can add the declared repository to an existing selected installation using the operator’s authenticated gh account. It verifies app/installation ownership and repository administration before mutation, then verifies token scope afterward.

publish verifies the encrypted checkpoint, authenticates the app, checks the installation, mints an exactly-one-repository token and revokes that verification token. It writes the encrypted source and adjacent .app-id / .app-slug public files, then uses gh stdin to set the named repository secret and variables. Failed token revocation refuses publication or a successful verification receipt. If verification and revocation both fail, the diagnostic reports both outcomes. Partial publication is retryable using the same transaction. --replace-key permits an explicit verified rotation and retains the previous ciphertext in previous.age. Publication is serialized per source.

The JSON receipt contains IDs, public-key fingerprint, policy/checkpoint/source hashes, token expiry and successful slot names. It contains no PEM, JWT or token. Its actionsSlotQualified stays false: GitHub does not return stored Actions secret plaintext, so the trusted hosted workflow must prove destination use. verify checks the encrypted source credential, not the installed Actions slot.

If a crash follows manifest exchange, retry enroll with the same transaction: an encrypted response can be recovered. If no ciphertext exists, the consumed code has an ambiguous outcome; inspect GitHub settings and import an issued key in a fresh transaction. Never replay that conversion blindly.

gh must already be authenticated to github.com; age identities use the existing --identity / SECRET_MANAGER_AGE_IDENTITIES contract. Canix hardware identity stubs under age/master_identities/ should be passed explicitly. Declaration of services.secretSync.targets does not schedule execution.

github token — Account tokens through the device flow

Mint a GitHub OAuth token for an account (for example a bot user) and store it as agenix sources without plaintext touching the store:

SECRET_MANAGER_AGE_ENCRYPTOR=$(nix build --no-link --print-out-paths .#agenix-stream-encryptor)/bin/agenix-stream-encryptor \
secret-manager github token --login canikroid \
  --scope repo --scope write:gpg_key --scope admin:public_key \
  --out age/secrets/users/can/canikroid_github_token.age \
  --env-out age/secrets/users/can/canikroid_github_env.age

The command prints a one-time code; authorize it at https://github.com/login/device in a browser signed in as --login. It then verifies the token authenticates as that login with every requested scope (admin:x implies write:x/read:x), encrypts the bare token to --out and <ENV_NAME>=<token> to --env-out, and exits. Nothing is stored on a login or scope mismatch.

gh runs in a private temporary config directory under $XDG_RUNTIME_DIR, removed on exit, so the token never reaches your own gh login or keyring. Existing sources are refused unless --rotate is given. Stage the sources and run agenix rekey afterwards.

FlagDescription
--login LOGINAccount the token must belong to
--scope SCOPERequested and required OAuth scope; repeatable
--out PATHEncrypted source for the bare token
--env-out PATHOptional encrypted ENV_NAME=token source for EnvironmentFile consumers
--env-name NAMEVariable name for --env-out (default GH_TOKEN)
--rotateReplace existing sources
--encryptor PATHlib.mkAgenixStreamEncryptor executable (default SECRET_MANAGER_AGE_ENCRYPTOR)
--timeout-seconds NAuthorization wait, 30–3600 (default 900)

claude token — Claude Code subscription tokens

Store a long-lived Claude Code token for headless runners (CLAUDE_CODE_OAUTH_TOKEN) in one step:

SECRET_MANAGER_AGE_ENCRYPTOR=$(nix build --no-link --print-out-paths .#agenix-stream-encryptor)/bin/agenix-stream-encryptor \
secret-manager claude token --out age/secrets/root/hosts/atlas/paperclip_operations_claude_oauth.age

The command runs native claude setup-token and waits up to 15 minutes for browser authorization. Only browser interaction is needed: both output channels are captured privately in bounded, zeroizing memory, never printed or saved as plaintext. No token copy/paste or controlling terminal is required. Native output must contain one unique, valid sk-ant-oat01- token; changed, ambiguous or malformed output fails closed without storage. The token is encrypted with the store’s agenix master policy, the source is staged, and agenix rekey distributes it. With --secret NAME (the secret’s agenix attribute) only that secret’s rekeyed copies are staged, so unrelated uncommitted rekeyed changes do not block it. Tokens last one year; renew with --rotate.

Native terminal framing outside the token paragraph is ignored. Color styling and private display modes are handled without changing token text; editing controls inside token data, incomplete sequences and ambiguous tokens are rejected.

Encryption/rekey may still require security-key or pinentry approval. A native browser-launch or authorization failure does not fall back to printing a URL or token. If rekey fails after encryption, retain the encrypted source and retry agenix rekey -a, rather than minting again. Host deployment remains separate.

FlagDescription
--out PATHEncrypted source for the token
--rotateReplace an existing source
--claude PATHClaude Code executable (default claude)
--no-rekeySkip agenix rekey; run agenix rekey -a before deploying
--no-stageLeave staging of the source and rekeyed copies to you
--secret NAMEAgenix attribute name; stage only its rekeyed copies, ignoring other dirty ones
--encryptor PATHlib.mkAgenixStreamEncryptor executable (default SECRET_MANAGER_AGE_ENCRYPTOR)

gpg publish — Account signing keys

Register an existing armored public key on authenticated forge accounts:

secret-manager gpg publish nomad.asc --codefloe --github --codeberg
FlagDescription
--codefloeRegister on the Forgejo account at codefloe.com
--githubRegister on the GitHub account at github.com
--codebergRegister on the Forgejo account at codeberg.org
--forgejo HOSTAdd another Forgejo destination; repeatable
--dry-runValidate the public key and print destinations without network calls
--checkRead back registration; fail if any selected account is missing the key
--expected-fingerprint FINGERPRINTRequire this full fingerprint before contacting a forge

Choose at least one destination. The command accepts exactly one public primary key in one ASCII-armored block, rejects private-key packets, and inspects it with GnuPG in an isolated temporary keyring. It does not import into your keyring. Relative file paths resolve from the working directory; no secret store is needed.

Forgejo destinations reuse fj authentication in its existing auth store. Tokens need read:user for listing and write:user for registration. GitHub reuses gh authentication; classic/OAuth tokens need read:gpg_key and write:gpg_key, respectively.

Retries are idempotent: an existing key is accepted only after matching its full fingerprint from the API’s public-key packets. Every destination is attempted even if another fails, and incomplete publication exits nonzero. After a failed POST, a read-back checks whether registration succeeded before reporting failure.

Output reports registration, signing capability, email verification, and Forgejo’s key-ownership verification separately. Uploading a key does not perform Forgejo’s proof-of-possession challenge or verify an account email. Codefloe account registration uses its Forgejo API, independently of Crow CI credentials.

hm — Home-manager secrets

Add an agenix secret for a home-manager target.

Subcommands:

CommandDescription
sshGenerate an SSH key and expose as a home-manager file
passwordGenerate a passphrase and expose as env vars or a file
textEncrypt text from editor/stdin/–from-file as env vars
fileEncrypt a file payload and expose as a home-manager file

Options (shared):

FlagDescription
--name <SLUG>Secret slug
--user <USER>Home-manager user (default: current login user)
--sharedStore under age/secrets/users/shared/
--env <VAR>Environment variable names
--file <PATH>File path within the home profile
--from-file <PATH>Read plaintext from file
--from-age <PATH>Adopt an existing encrypted .age file
--algorithm <ALGO>SSH key algorithm (ed25519, rsa)
--length <N>Passphrase length (32, 48, 64)
--no-rekeySkip agenix rekey -a
--no-stageSkip git add

nixos — NixOS host secrets

Add an agenix secret for a NixOS host target.

Subcommands:

CommandDescription
sshGenerate an SSH key and expose as a NixOS file
passwordGenerate a passphrase for service env vars or a file
textEncrypt text as service env vars
fileEncrypt a file payload and expose as a NixOS file

Options (shared):

FlagDescription
--name <SLUG>Secret slug
--host <HOST>NixOS host name
--env <VAR>Environment variable names
--service <NAME>Systemd service names (required with --env)
--file <PATH>File path on the host
--from-file <PATH>Read plaintext from file
--from-age <PATH>Adopt an existing encrypted .age file
--algorithm <ALGO>SSH key algorithm
--length <N>Passphrase length
--no-rekeySkip agenix rekey -a
--no-stageSkip git add

forgejo — Forgejo Actions secrets

Add an agenix secret for Forgejo Actions or runner credentials.

Subcommands:

CommandDescription
sshGenerate or refresh a Forgejo Actions SSH deploy key
gpg-key-pairGenerate or adopt an OpenPGP private key and derive public metadata
passwordGenerate a passphrase as a runner credential
textEncrypt text as a runner credential
fileEncrypt a file payload as a runner credential

Options (shared):

FlagDescription
--name <SLUG>Secret slug
--cred <NAME>Runner credential name
--instance <INSTANCE>Runner instance (repeatable)
--pubkey-var <VAR>Nix variable name for the generated public key
--module-dir <DIR>Module-secret directory for gpg-key-pair sources
--user-id <UID>OpenPGP user ID for newly generated keys
--rotateForce SSH key rotation
--from-file <PATH>Read plaintext from file
--from-age <PATH>Adopt an existing encrypted .age file
--length <N>Passphrase length
--no-rekeySkip agenix rekey -a
--no-stageSkip git add

list — List secrets

List every *.age file tracked in the repository.

secret-manager list

push — Push to CI secret stores

Decrypt an agenix secret and push it to repository Actions secret stores.

secret-manager push <SECRET.age> \
  --name <SECRET_NAME> \
  --codeberg <OWNER/REPO> \
  --codefloe <OWNER/REPO> \
  --github <OWNER/REPO> \
  --identity <IDENTITY.pub>

sync — Push declared sync targets

Read a collected services.secretSync JSON document and push each declared target to Codeberg/Forgejo, Codefloe, or GitHub Actions scopes, or to Kubernetes Secrets. Codeberg/Forgejo targets support repository, organization, and authenticated-user scopes; Codefloe and GitHub targets are repository-scoped; Kubernetes targets name a kubectl context, namespace and Secret (see Sync Targets). Encrypted secret targets are pushed as Actions secrets; plaintext source targets are pushed as Actions variables. Kubernetes accepts encrypted secret targets only.

secret-manager sync --dry-run
secret-manager sync --identity <IDENTITY.pub>

By default, sync discovers the nearest flake root and evaluates that flake’s secret-manager.lib.collect output over nixosConfigurations. Use --dry-run to print the planned pushes without decrypting or contacting the forge.

For portable scripts, pass an explicit collected JSON document:

secret-manager sync --config sync-targets.json --identity <IDENTITY.pub>

If --config is omitted and stdin is not a terminal, sync reads the JSON document from stdin. Pass --no-flake to disable flake auto-detection and require one of those explicit input paths.

sync records the remote Actions secrets and variables it manages in <secret-store>/.secret-manager/sync-state.toml by default. Pass --state <PATH> to use a different state file. Entries that were previously managed but no longer appear in the collected config are reported as stale. They are only deleted from their configured forge when --prune is passed. --target only narrows which targets are pushed: staleness is always judged against the complete collected config, and --prune cannot be combined with --target.

decrypt — Decrypt to stdout

Decrypt an agenix secret and print plaintext to stdout.

secret-manager decrypt <SECRET.age> \
  --identity <IDENTITY.pub>

wg-keygen — WireGuard keypair

Generate a WireGuard keypair, store the private key as an agenix secret, print the public key.

secret-manager wg-keygen <SECRET.age>

rauthy-env — Rauthy bootstrap

Bootstrap the Rauthy env-file secret end to end.

secret-manager rauthy-env [SECRET.age]

gerrit-cookies — Gerrit cookies

Encrypt a Gerrit .gitcookies payload into an agenix secret.

secret-manager gerrit-cookies <INPUT_FILE> <SECRET.age>

Global flags

FlagDescription
-h, --helpPrint help
-V, --versionPrint version