Skip to content

Configuration

sysid edited this page Sep 23, 2026 · 6 revisions

Configuration

rsenv uses layered configuration with sensible defaults. Customize behavior through config files or environment variables.

Configuration Precedence

Configuration is loaded in order (later overrides earlier):

  1. Compiled defaults - Built into rsenv
  2. Global config - platform config dir, print it with rsenv config path
  3. Local config - <vault>/.rsenv.toml
  4. Environment variables - RSENV__* prefix, double underscore (override)

Merge Behavior

Layer Transition Arrays Scalars
Defaults → Global REPLACE (global defines baseline) Replace if specified
Global → Local/Vault UNION with negation support Replace if specified
Any → Env vars REPLACE (explicit override) Replace

Rationale: Compiled defaults are just examples. Global config defines the real baseline for your user/organization. Vault config adds project-specific patterns on top.

See Array Merge Semantics for details.

Quick Setup

# Create global config
rsenv config init --global

# View effective config
rsenv config show

# Show config file paths
rsenv config path

Configuration File

Location

The global path is platform-dependent - rsenv config path prints the one in use:

Type Path
Global (Linux) ~/.config/rsenv/rsenv.toml
Global (macOS) ~/Library/Application Support/rsenv/rsenv.toml
Local <vault>/.rsenv.toml

Full Example

# ~/.config/rsenv/rsenv.toml

# Base directory for rsenv (vaults stored in base_dir/vaults)
base_dir = "~/.rsenv"

# Preferred editor
editor = "nvim"

# SOPS encryption settings
[sops]
# GPG key fingerprint
gpg_key = "12A4127E82E218297532FAB6D750B66AE08F3B90"

# Age public key (alternative to GPG)
# age_key = "age1..."

# File extensions to encrypt
file_extensions_enc = [
    "env",
    "envrc",
    "yaml",
    "yml",
    "json",
]

# Re-encrypt the vault before `rsenv vault commit` stages it (default: true).
# Turning this off means a plaintext edit made while swapped in is committed as
# stale ciphertext, because the commit stages whatever .enc happens to exist.
encrypt_on_commit = true

# Specific filenames to encrypt
file_names_enc = [
    "dot_pypirc",
    "dot_pgpass",
    "kube_config",
]

# Extensions to decrypt
file_extensions_dec = ["enc"]

# Specific filenames to decrypt
file_names_dec = []

Configuration Options

Core Settings

Setting Default Description
base_dir ~/.rsenv Base directory (vaults stored in base_dir/vaults)
editor $VISUAL, else $EDITOR, else vim Editor every command spawns (env edit, env edit-leaf, env tree-edit, config edit)

SOPS Settings

Setting Default Description
sops.gpg_key None GPG key fingerprint for encryption
sops.age_key None Age public key (alternative to GPG)
sops.file_extensions_enc ["env", "envrc"] Extensions to encrypt
sops.file_names_enc [] Exact filenames to encrypt
sops.file_extensions_dec ["enc"] Extensions to decrypt
sops.file_names_dec [] Exact filenames to decrypt
sops.encrypt_on_commit true Re-encrypt the vault before rsenv vault commit stages it (--no-encrypt overrides per run)

Environment Variables

Override any setting via environment variables. The separator is a double underscore - both after the RSENV prefix and between nested keys. A single-underscore name such as RSENV_EDITOR is silently ignored.

Config Key Environment Variable
base_dir RSENV__BASE_DIR
editor RSENV__EDITOR
sops.gpg_key RSENV__SOPS__GPG_KEY
sops.age_key RSENV__SOPS__AGE_KEY
sops.file_extensions_enc RSENV__SOPS__FILE_EXTENSIONS_ENC
sops.file_names_enc RSENV__SOPS__FILE_NAMES_ENC
sops.encrypt_on_commit RSENV__SOPS__ENCRYPT_ON_COMMIT

Array Values

For arrays, use comma-separated values:

export RSENV__SOPS__FILE_EXTENSIONS_ENC="env,yaml,json"
export RSENV__SOPS__FILE_NAMES_ENC="dot_pypirc,dot_pgpass"

Examples

# Use custom base directory
export RSENV__BASE_DIR=~/my-rsenv

# Use different GPG key
export RSENV__SOPS__GPG_KEY="ABC123..."

# Add to shell config for persistence
echo 'export RSENV__BASE_DIR=~/my-rsenv' >> ~/.bashrc

Vault-Specific Config

Create .rsenv.toml in your vault directory to override global settings:

# ~/.rsenv/vaults/myproject-abc123/.rsenv.toml

# This vault uses a specific GPG key
[sops]
gpg_key = "VAULT_SPECIFIC_KEY"

Vault config merges with global config using union semantics for arrays. See Array Merge Semantics below.

Commands

Show Effective Configuration

rsenv config show

Output:

Configuration:
  base_dir: /home/user/.rsenv
  editor: nvim

  [sops]
  gpg_key: 60A4127E82E218297532FAB6D750B66AE08F3B90
  file_extensions_enc: ["env", "envrc", "yaml"]
  file_names_enc: ["dot_pypirc"]
  file_extensions_dec: ["enc"]

Show Config Paths

rsenv config path

Output:

Configuration paths:
  Global: /home/user/.config/rsenv/rsenv.toml (exists)
  Local: /home/user/.rsenv/vaults/myproject-abc123/.rsenv.toml (not found)

Initialize Config

# Create global config template
rsenv config init --global

# Create local vault config (requires initialized vault)
rsenv config init

Edit Config

# Edit global config
rsenv config edit --global

# Edit local vault config (requires initialized vault)
rsenv config edit

Opens the configuration file in the editor setting - the config file's own value, else $VISUAL, else $EDITOR, else vim.

If the config file doesn't exist, creates a template first. After editing vault-local config, automatically syncs gitignore patterns.

Defaults

If no configuration exists, rsenv uses these defaults:

base_dir = "~/.rsenv"  # Vaults stored in base_dir/vaults
editor = "$VISUAL"  # Then $EDITOR, then "vim"

[sops]
gpg_key = ""  # Must be set for encryption
age_key = ""
file_extensions_enc = ["env", "envrc"]
file_names_enc = []
file_extensions_dec = ["enc"]
file_names_dec = []

Path Expansion

Paths support:

  • ~ - Home directory
  • $VAR and ${VAR} - Environment variables
base_dir = "~/my-rsenv"
base_dir = "$HOME/my-rsenv"
base_dir = "${XDG_DATA_HOME}/rsenv"

XDG Compliance

rsenv follows the XDG Base Directory spec on Linux:

Purpose Default Location
Config (Linux) ~/.config/rsenv/rsenv.toml
Config (macOS) ~/Library/Application Support/rsenv/rsenv.toml
Base dir ~/.rsenv
Vaults ~/.rsenv/vaults (computed as base_dir/vaults)

On Linux, XDG_CONFIG_HOME moves the config:

export XDG_CONFIG_HOME=~/.myconfig
# Config now at ~/.myconfig/rsenv/rsenv.toml

On macOS it has no effect - the config stays under Application Support, which is what rsenv config path reports. base_dir is ~/.rsenv on both platforms.

Per-Vault Configuration

Vaults can contain a local rsenv.toml:

~/.rsenv/vaults/myproject-abc123/
├── rsenv.toml     # Vault-specific overrides
├── dot.envrc
└── envs/

This is loaded when operating within that vault.

Array Merge Semantics

Arrays use different merge behavior depending on the layer:

Transition Behavior Example
Defaults → Global REPLACE Global ["yaml"] replaces defaults ["env", "envrc"]
Global → Vault UNION Vault ["json"] + Global ["yaml"] = ["json", "yaml"]

Why? Compiled defaults are just examples. Your global config defines the real baseline. Vault config adds project-specific patterns on top.

How Vault → Global Union Works

Global config:  file_extensions_enc = ["env", "envrc"]
Vault config:   file_extensions_enc = ["yaml"]
Result:         file_extensions_enc = ["env", "envrc", "yaml"]  ← UNION

Negation Support

Use the ! prefix to remove items inherited from global/default config:

# Global config (~/.config/rsenv/rsenv.toml)
[sops]
file_extensions_enc = ["env", "envrc"]
file_names_enc = ["dot_pypirc", "dot_pgpass"]

# Vault config (<vault>/.rsenv.toml)
[sops]
# ADDS yaml, REMOVES env from inherited patterns
file_extensions_enc = ["yaml", "!env"]
file_names_enc = ["secrets.txt"]

# Effective result:
# file_extensions_enc = ["envrc", "yaml"]
# file_names_enc = ["dot_pypirc", "dot_pgpass", "secrets.txt"]

Complete Replacement

To completely replace inherited arrays, negate all inherited values:

# Vault config - completely replace with only these values
[sops]
file_extensions_enc = ["!env", "!envrc", "yaml", "json"]
# Result: ["json", "yaml"]

Environment Variables Replace

Environment variables always replace (no merge) because they represent explicit user overrides:

# This REPLACES the entire array, not merges
export RSENV__SOPS__FILE_EXTENSIONS_ENC="yaml,json"

Why This Design?

The layered merge semantics serve different purposes:

  1. Global REPLACES defaults: Defaults are just examples. You define your real baseline in global config.
  2. Vault UNIONS with global: Project-specific additions layer on top without losing your baseline.
  3. Fail-safe: For vault→global, union means encrypting too much (minor inconvenience) rather than too little (security risk).
  4. Negation for removal: Use !pattern in vault config to explicitly remove inherited items when needed.

Troubleshooting

Config not loading

# Check paths
rsenv config path

# Verify file syntax
cat ~/.config/rsenv/rsenv.toml

# Check effective values
rsenv config show

Environment variable not applied

# Verify export
echo $RSENV__BASE_DIR

# Check if it's in the effective config
rsenv config show | grep base_dir

Local config not found

# Local config is in the vault, not the project
echo $RSENV_VAULT
ls -la $RSENV_VAULT/.rsenv.toml

Related

rsenv Documentation

Getting Started
Features
Reference
Upgrading

Clone this wiki locally