README.md

secrets

A tiny wrapper around age for managing a directory of encrypted secrets. Secrets are stored as individual *.age files under a single directory ($SECRETS_DIR, default ~/projects/data), and paths are preserved so you can organize them in a hierarchy and expose one secret under multiple tags using symlinks.

How it works

Requirements

Install

With Nix (from the flake):

nix build git+https://git.kilimanjaro.io/secrets
nix run   git+https://git.kilimanjaro.io/secrets -- list

Or as a flake input in a dev environment:

inputs.secrets.url = "git+https://git.kilimanjaro.io/secrets";
# ...
buildInputs = [ secrets.packages.${system}.default ];

Without Nix, just put the secrets script on your PATH (e.g. symlink it into ~/bin). The script only needs age, age-keygen, and coreutils.

Usage

secrets list                  # list secrets, relative paths, symlinks annotated
secrets encrypt <file|name>   # encrypt a file, or stdin, to $SECRETS_DIR/<path>.age
secrets decrypt <secret>      # decrypt $SECRETS_DIR/<secret>[.age] to stdout

Encrypt

From stdin, into a nested path (openai/api-key.age is created, along with the openai/ directory):

echo "sk-abc123" | secrets encrypt openai/api-key

From an existing file. The file’s path is preserved under $SECRETS_DIR:

secrets encrypt config/rclone.conf
# -> $SECRETS_DIR/config/rclone.conf.age

A leading slash is allowed and just means “root of $SECRETS_DIR”:

echo "hunter2" | secrets encrypt /my/fancy/secret.age
# -> $SECRETS_DIR/my/fancy/secret.age

Encrypt refuses to overwrite an existing secret.

Decrypt

Decryption writes to stdout, so redirect it or pipe it where you need:

secrets decrypt openai/api-key            # -> sk-abc123
secrets decrypt openai/api-key.age        # .age suffix is optional
secrets decrypt /my/fancy/secret.age      # leading slash is optional too
secrets decrypt config/rclone.conf > ~/.config/rclone/rclone.conf

List

$ secrets list
config/rclone.conf.age
my/fancy/secret.age
openai/api-key.age
work/openai -> ../openai/api-key.age

Tagging with symlinks

Store a secret once and link to it from as many tag directories as you like. Symlink names may omit the .age suffix; decrypt falls back to the exact path when <name>.age doesn’t exist.

ln -s ../openai/api-key.age "$SECRETS_DIR/work/openai"
ln -s ../openai/api-key.age "$SECRETS_DIR/personal/openai"

secrets decrypt work/openai       # -> sk-abc123
secrets list                      # shows "work/openai -> ../openai/api-key.age"

Environment

Variable Default Description
SECRETS_DIR ~/projects/data Directory that holds the *.age files