NixOS module + TUI CLI for declarative agenix secret management
Project description
agenix-manager
NixOS module + TUI CLI for declarative agenix secret management.
Secrets are declared in a JSON manifest file (secrets/secrets-manifest.json)
managed by the CLI — never in Nix directly. The Nix module reads the manifest at
eval time and wires up config.age.secrets.* automatically.
Features
- Declarative — secrets declared in a JSON manifest, never in Nix
- Multi-key — encrypt secrets for host keys, user keys, CI keys, or any combination via named key groups
- Automatic wiring —
config.age.secrets.*generated from the manifest - TUI — status overview, create/edit/decrypt/rekey/remove operations via keyboard-driven interface
- CLI — headless operation for scripting and CI
NixOS module
# flake.nix
{
inputs.agenix.url = "github:ryantm/agenix";
inputs.agenix-manager.url = "github:Cairnstew/agenix-manager";
outputs = { agenix, agenix-manager, nixpkgs, ... }: {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
modules = [
agenix.nixosModules.default
agenix-manager.nixosModules.default
{
agenixManager = {
enable = true;
secretsPath = ./secrets;
keys.systems = [ "ssh-ed25519 AAAA...hostkey" ];
keys.users = [ "ssh-ed25519 AAAA...seankey" ];
# Optional custom key groups:
# keyGroups.deployment = cfg.keys.systems ++ [ "ssh-ed25519 AAAA...ci-key" ];
identities = [ "/etc/ssh/ssh_host_ed25519_key" ];
};
}
];
};
};
}
The module automatically adds both the agenix-manager CLI and age to
environment.systemPackages — no extra config needed. Just import and enable.
Secrets are not declared in Nix — they live in the manifest. After adding your first secret (see CLI section below), reference them from other modules as usual:
{ config, ... }: {
users.users.sean = {
passwordFile = config.age.secrets.github-token.path;
};
}
Manifest file
Created and maintained by agenix-manager new. Format:
{
"version": 1,
"secrets": [
{
"name": "github-token",
"scope": "users",
"owner": "root",
"group": "root",
"mode": "0400"
},
{
"name": "db-password",
"scope": "all",
"owner": "postgres",
"group": "postgres",
"mode": "0400"
}
]
}
The manifest contains only metadata (names, scopes, permissions) — never
plaintext values. It is safe to commit to a public repository, but must be
committed before nixos-rebuild switch sees it.
Bootstrap
On a fresh system, the manifest does not yet exist. The Nix module emits a warning and produces an empty secrets list.
If you haven't added agenix-manager to your system packages yet, run it
directly from the flake:
sudo nix run github:Cairnstew/agenix-manager -- new --name my-secret --scope users --stdin <<< "myvalue"
If you already have the CLI installed:
agenix-manager new --name my-secret --scope users --stdin <<< "myvalue"
Interactive wizard (recommended for first use):
sudo nix run github:Cairnstew/agenix-manager -- new
Then commit everything and rebuild:
git add secrets/
nixos-rebuild switch --flake .#myhost
The repository includes a .gitattributes marking *.age files as binary
(they change entirely on every rekey due to the random nonce) and a
secrets/.gitkeep so the directory is present on clone.
Key groups and scopes
Each secret in the manifest has a scope field that accepts either a scope
name or a literal list of SSH public keys.
Scope names resolve to named key groups:
| Scope | Resolution |
|---|---|
"all" |
systems ++ users ++ other |
"systems" |
agenixManager.keys.systems |
"users" |
agenixManager.keys.users |
"other" |
agenixManager.keys.other |
"deployment" |
Custom — defined in agenixManager.keyGroups |
Scope names are resolved to key lists by both the Nix module (at eval time)
and the Python CLI (at manifest load time). The original scope is preserved
in a scope field for display purposes.
CLI
From the flake directly (no install required):
sudo nix run github:Cairnstew/agenix-manager -- new
sudo nix run github:Cairnstew/agenix-manager -- status
sudois needed becauseagenix-managerwrites to/etc/agenix/and reads from the Nix daemon. If your user is in thetrusted-usersset and has write access to the secrets directory,sudocan be omitted.
If installed on your system:
agenix-manager # full TUI
agenix-manager new # interactive TUI wizard
agenix-manager new --name my-secret --scope users --stdin <<< "myvalue"
agenix-manager status # status table only
agenix-manager sync # re-sync secrets.nix without TUI
agenix-manager --config-file config.json # skip nix eval, use JSON file
agenix-manager new
Creates a new secret — the primary entry point for secret management.
Interactive wizard (no flags):
agenix-manager new
A 4-step fully keyboard-driven wizard:
- Secret name — type a name,
Enterto confirm - Key scope — arrow keys to navigate,
Spaceto toggle,Ctrl+Enterto confirm (selecting multiple scopes encrypts for all of them) - Permissions —
Tabbetween owner/group/mode fields,Enterto confirm - Secret value — type or paste multi-line content,
Ctrl+Enterto create
Esc goes back one step at any point.
On completion: writes the manifest, regenerates secrets.nix, encrypts the
value, and prints a reminder to git add secrets/.
Non-interactive (all flags provided):
# From editor (opens $EDITOR):
agenix-manager new --name github-token --scope users
# From stdin (piped, no editor):
echo "mysecret" | agenix-manager new --name github-token --scope users --stdin
Options:
| Flag | Default | Description |
|---|---|---|
--name |
— | Secret name (required for non-interactive) |
--scope |
— | Key scope (all, systems, users, other, or custom) |
--owner |
root |
File owner |
--group |
root |
File group |
--mode |
0400 |
File mode (octal) |
--stdin |
— | Read plaintext from stdin instead of opening editor |
Activation and cache
Every nixos-rebuild switch:
- Writes
secrets.nixto/etc/agenix/ - Writes a JSON CLI cache to
/etc/agenix/agenix-manager-cache.json - Writes a keys snapshot to
/etc/agenix/keys-snapshot.json
The CLI reads from the cache on startup — instant, no nix eval overhead.
Falls back to nix eval if the cache is missing (e.g. before first activation).
TUI
The main interface is a single status screen showing key group counts and the secret table (name, scope, status, owner, mode). All operations are available via hotkeys — no menu navigation.
| Key | Operation | Description |
|---|---|---|
n |
New | Opens the 4-step wizard to create a secret |
e |
Encrypt | Re-encrypt the selected secret via $EDITOR |
d |
Decrypt | Shows decrypted plaintext in an ephemeral viewer |
r |
Rekey | Shows key diff confirmation, then re-encrypts with current keys |
R |
Remove | Deletes the .age file and removes the secret from the manifest |
q |
Quit | Exit the TUI |
The status screen reads from a cache file (/etc/agenix/agenix-manager-cache.json)
on startup for instant loading. If the cache is missing, it falls back to
nix eval to compute the config.
Development
nix develop
# or for a minimal shell:
nix develop .#bootstrap
Tests run via pytest (requires the dev shell or a virtualenv):
.venv/bin/python -m pytest tests/
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file agenix_manager-0.1.0.tar.gz.
File metadata
- Download URL: agenix_manager-0.1.0.tar.gz
- Upload date:
- Size: 135.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
55ae2371e8de66ffb50ba98f9825b5a071a988dce95843958e24e192b63dd136
|
|
| MD5 |
7274cd59e2c88ce7583facf52ce41fd6
|
|
| BLAKE2b-256 |
f2e024ade83aae7f03e36ed7f16dbd5b6c09a7f49f721186339a0b9f20522fb5
|
File details
Details for the file agenix_manager-0.1.0-py3-none-any.whl.
File metadata
- Download URL: agenix_manager-0.1.0-py3-none-any.whl
- Upload date:
- Size: 36.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4dbce6e1ed29ce898b4ef3a9bb4cb517cd85b87c3f0a428546062d0893d73205
|
|
| MD5 |
0cfadcde5eb8dc12110c06f72da04d1d
|
|
| BLAKE2b-256 |
2801dff3f99968d42f3269a602d029d9d89ef634a53b00db2be27717f9987eb7
|