mit einem Klick
agenix
Agenix Skill
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Menü
Agenix Skill
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Basierend auf der SOC-Berufsklassifikation
Read, write, search and reorganise notes in the local Obsidian vaults. Use for anything touching ~/Documents/R3_vault, ~/Documents/Synechron or ~/Documents/My_Obsidian_Vault/Privat — finding notes, adding or editing content, renaming/moving notes without breaking links, and daily notes. Triggers on "my notes", "my vault", "Obsidian", "write this up in my notes", or a request to look something up in personal documentation.
Manage DNS records for the user's GoDaddy-registered domains. Add/upsert, delete, list, look up specific records, verify a stored record matches an expected value, and check public DNS resolution via dig. Triggers on `/dns`, requests to add/change/check A, AAAA, CNAME, MX, TXT, NS, SRV records on the user's domains, debugging DNS propagation, or auditing what's set at GoDaddy vs what's resolving in the wild.
Operate Google Workspace from the terminal via the `gog` CLI (gogcli). Use for checking and replying to Gmail, managing Google Tasks, reading and creating Calendar events, Google Chat (spaces/DMs/messages), Meet spaces, Contacts, Drive/Docs/Sheets, and more. Triggers on `/gog`, `/gog mail`, `/gog tasks`, `/gog events`, `/gog chat`, `/gog meet`, or any request to check/read/reply/send email, list or add tasks, see today's agenda, or message someone on Chat for the user's Google account.
cargo2nix Skill
COSMIC Desktop Environment Skill
DankMaterialShell (DMS) Skill
| name | agenix |
| version | 1 |
| description | Agenix Skill |
agenix is a lightweight, SSH-based secrets management solution for NixOS that uses age encryption to securely store and deploy sensitive information. It provides a CLI tool for encrypting secrets and a NixOS/Home Manager module for automated decryption and deployment.
nixos-rebuild switch using host keys$EDITOR, auto-encrypt on saveProblem: Storing secrets (passwords, API keys, certificates) in NixOS configurations is challenging because:
builtins.readFile for secretsSolution: agenix encrypts secrets with SSH/age public keys, stores them in your Nix configuration, and automatically decrypts them on target systems using SSH private keys during activation.
agenix -e secret.age to create/edit encrypted secrets locally.age files to your Git repositoryage.secrets.<name>.filenixos-rebuild switch - secrets decrypt to /run/agenix/<name>Add agenix to your flake.nix:
{
description = "NixOS configuration with agenix";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
agenix.url = "github:ryantm/agenix";
# Optional: Pin to specific version
# agenix.url = "github:ryantm/agenix/0.15.0";
};
outputs = { self, nixpkgs, agenix, ... }:
{
nixosConfigurations.hostname = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./configuration.nix
agenix.nixosModules.default
];
};
};
}
Install the CLI tool in your system:
# configuration.nix
{ pkgs, agenix, ... }:
{
environment.systemPackages = [
agenix.packages.x86_64-linux.default
];
}
Or run the CLI without installing:
nix run github:ryantm/agenix -- --help
nix run github:ryantm/agenix -- -e secret.age
Add the agenix channel:
sudo nix-channel --add https://github.com/ryantm/agenix/archive/main.tar.gz agenix
sudo nix-channel --update
Import in configuration.nix:
{
imports = [ <agenix/modules/age.nix> ];
environment.systemPackages = [
(import <agenix>).default
];
}
For hermetic builds without channels:
{
imports = [
"${builtins.fetchTarball "https://github.com/ryantm/agenix/archive/main.tar.gz"}/modules/age.nix"
];
environment.systemPackages = [
(import (builtins.fetchTarball "https://github.com/ryantm/agenix/archive/main.tar.gz")).default
];
}
Pin to specific commit:
let
agenixCommit = "298b235f664f925b433614dc33380f0662adfc3f";
agenixSha256 = "0000000000000000000000000000000000000000000000000000";
in {
imports = [
"${builtins.fetchTarball { url = \"https://github.com/ryantm/agenix/archive/${agenixCommit}.tar.gz\"; sha256 = agenixSha256; }}"/modules/age.nix"
];
}
Add agenix as a dependency:
niv add ryantm/agenix
Import in configuration.nix:
{
imports = [
"${(import ./nix/sources.nix).agenix}/modules/age.nix"
];
environment.systemPackages = [
(import (import ./nix/sources.nix).agenix).default
];
}
For user-level secrets with Home Manager:
# home.nix
{ inputs, ... }:
{
imports = [
inputs.agenix.homeManagerModules.default
];
age = {
identityPaths = [ "~/.ssh/id_ed25519" ];
secrets = {
personal-token.file = ../secrets/personal-token.age;
};
};
}
mkdir -p secrets
cd secrets
Define which public keys can decrypt each secret:
# secrets/secrets.nix
let
# User SSH keys
alice = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0idNvgGiucWgup/mP78zyC23uFjYq0evcWdjGQUaBH alice@laptop";
bob = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJkbfFtJRq+6u/zcZWQRHqNLJoJN0UCT5qqRkUGBQnWo bob@desktop";
# System SSH host keys
server1 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPJDyIr/FSz1cJdcoW69R+NrWzwGK/+3gJpqD1t8L2zE root@server1";
server2 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKj7H3K8JdQTTULTUi5L9l5JjqQNLq7JCqX5DWQR5sKj root@server2";
in
{
# API keys accessible by both users and server1
"api-key.age".publicKeys = [ alice bob server1 ];
# Database password only for server1
"db-password.age".publicKeys = [ alice server1 ];
# SSH private key for deployment
"deploy-key.age".publicKeys = [ alice server1 server2 ];
# User-specific secret
"alice-token.age".publicKeys = [ alice ];
}
Finding Public Keys:
# From local SSH keys
cat ~/.ssh/id_ed25519.pub
# From remote host
ssh-keyscan hostname
# From GitHub user
curl https://github.com/username.keys
# From NixOS configuration
ssh root@hostname "cat /etc/ssh/ssh_host_ed25519_key.pub"
# Set EDITOR if not already set
export EDITOR=vim
# Create and encrypt a secret
agenix -e api-key.age
This opens your editor. Type the secret content, save, and exit. The file is encrypted with the public keys defined in
secrets.nix.
# configuration.nix
{ config, ... }:
{
age.secrets.api-key = {
file = ./secrets/api-key.age;
mode = "440";
owner = "myservice";
group = "myservice";
};
# Use the secret in a service
systemd.services.myservice = {
script = ''
export API_KEY=$(cat ${config.age.secrets.api-key.path})
${pkgs.myapp}/bin/myapp
'';
serviceConfig = {
User = "myservice";
Group = "myservice";
};
};
}
nixos-rebuild switch
The secret decrypts to /run/agenix/api-key with the specified permissions.
# Uses your SSH key for decryption
agenix -e api-key.age
# Or specify identity explicitly
agenix -e api-key.age -i ~/.ssh/id_ed25519
{ config, ... }:
{
age.secrets.alice-password.file = ./secrets/alice-password.age;
users.users.alice = {
isNormalUser = true;
hashedPasswordFile = config.age.secrets.alice-password.path;
};
}
Create the hashed password:
mkpasswd -m sha-512 | agenix -e alice-password.age
{ config, ... }:
{
age.secrets.deploy-key = {
file = ./secrets/deploy-key.age;
mode = "600";
owner = "deploy";
};
users.users.deploy = {
isNormalUser = true;
openssh.authorizedKeys.keyFiles = [ ./deploy-key.pub ];
};
# Use for SSH connections
programs.ssh.extraConfig = ''
Host production
IdentityFile ${config.age.secrets.deploy-key.path}
'';
}
{ config, pkgs, ... }:
{
age.secrets.postgres-password = {
file = ./secrets/postgres-password.age;
owner = "postgres";
group = "postgres";
};
services.postgresql = {
enable = true;
ensureUsers = [{
name = "myapp";
# Password set via secret file
}];
};
# Application reads password
systemd.services.myapp = {
script = ''
export DATABASE_URL="postgresql://myapp:$(cat ${config.age.secrets.postgres-password.path})@localhost/myapp"
${pkgs.myapp}/bin/myapp
'';
};
}
{ config, ... }:
{
age.secrets = {
github-token.file = ./secrets/github-token.age;
openai-api-key.file = ./secrets/openai-api-key.age;
aws-credentials.file = ./secrets/aws-credentials.age;
};
# Service with environment file
systemd.services.backup = {
script = ''
export GITHUB_TOKEN=$(cat ${config.age.secrets.github-token.path})
${pkgs.backup-script}/bin/backup
'';
};
}
{ config, ... }:
{
age.secrets = {
"tls-cert.pem" = {
file = ./secrets/tls-cert.pem.age;
owner = "nginx";
group = "nginx";
mode = "440";
};
"tls-key.pem" = {
file = ./secrets/tls-key.pem.age;
owner = "nginx";
group = "nginx";
mode = "400";
};
};
services.nginx = {
enable = true;
virtualHosts."example.com" = {
enableACME = false;
sslCertificate = config.age.secrets."tls-cert.pem".path;
sslCertificateKey = config.age.secrets."tls-key.pem".path;
};
};
}
{ config, ... }:
{
age.secrets.app-config = {
file = ./secrets/app-config.yaml.age;
path = "/etc/myapp/config.yaml";
mode = "440";
owner = "myapp";
group = "myapp";
};
systemd.services.myapp = {
script = ''
${pkgs.myapp}/bin/myapp --config /etc/myapp/config.yaml
'';
serviceConfig = {
User = "myapp";
Group = "myapp";
};
};
}
{ config, ... }:
{
age.secrets.wireguard-private = {
file = ./secrets/wireguard-private.age;
mode = "400";
};
networking.wireguard.interfaces.wg0 = {
privateKeyFile = config.age.secrets.wireguard-private.path;
ips = [ "10.0.0.2/24" ];
peers = [{
publicKey = "server-public-key";
endpoint = "vpn.example.com:51820";
allowedIPs = [ "10.0.0.0/24" ];
}];
};
}
Type: path
Required: Yes
Path to the encrypted .age file.
{
age.secrets.api-key.file = ./secrets/api-key.age;
}
Type: string
Default: /run/agenix/<name>
Path where the decrypted secret will be available.
{
age.secrets.monitrc = {
file = ./secrets/monitrc.age;
path = "/etc/monitrc";
};
}
Type: string
Default: "0400"
File permissions in chmod format (octal).
{
age.secrets.nginx-htpasswd = {
file = ./secrets/nginx.htpasswd.age;
mode = "0440"; # Owner and group can read
};
}
Common modes:
"0400": Owner read-only (most secure)"0440": Owner and group read"0600": Owner read/write"0640": Owner read/write, group readType: string
Default: "root"
Username of the file owner.
{
age.secrets.postgres-password = {
file = ./secrets/postgres-password.age;
owner = "postgres";
};
}
Type: string
Default: "root"
Group name of the file.
{
age.secrets.nginx-cert = {
file = ./secrets/nginx-cert.age;
owner = "nginx";
group = "nginx";
};
}
Type: boolean
Default: true
Whether to use a symlink or copy the file.
{
age.secrets.elasticsearch-conf = {
file = ./secrets/elasticsearch.conf.age;
symlink = false; # Copy instead of symlink
};
}
Note: Symlinks are recommended for security (automatic cleanup). Disable only if an application cannot follow symlinks.
Type: string
Default: <attribute name>
Custom filename for the decrypted secret.
{
age.secrets.monit = {
name = "monitrc";
file = ./secrets/monitrc.age;
};
# Decrypts to /run/agenix/monitrc instead of /run/agenix/monit
}
Type: list of strings
Default: SSH host keys from config.services.openssh.hostKeys
Paths to private keys used for decryption.
{
age.identityPaths = [
"/var/lib/persistent/ssh_host_ed25519_key"
"/var/lib/persistent/ssh_host_rsa_key"
];
}
Important: Use strings, not Nix paths, to prevent copying private keys to the Nix store.
Type: string
Default: /run/agenix
Directory where secret symlinks are created.
{
age.secretsDir = "/run/keys";
}
Type: string
Default: /run/agenix.d
Directory for generation-specific secrets (internal use).
{
age.secretsMountPoint = "/run/secret-generations";
}
Type: string
Default: "${pkgs.age}/bin/age"
Path to the age binary.
{
# Use rage instead of age
age.ageBin = "${pkgs.rage}/bin/rage";
}
Home Manager options are similar to NixOS with these differences:
Type: list of strings
Required: Yes (no default)
Must be explicitly configured:
{
age.identityPaths = [ "~/.ssh/id_ed25519" ];
}
Default: $XDG_RUNTIME_DIR/agenix (Linux) or temporary directory (Darwin)
{
age.secretsDir = "$HOME/.secrets";
}
# Edit secret (creates if doesn't exist)
agenix -e secret.age
# Edit with specific identity
agenix -e secret.age -i ~/.ssh/id_ed25519
# Edit with custom rules file
RULES=./my-secrets.nix agenix -e secret.age
Re-encrypt all secrets when public keys change:
# Rekey all secrets
agenix --rekey
# Rekey with specific identity
agenix --rekey -i ~/.ssh/id_ed25519
# Rekey with custom rules
RULES=./my-secrets.nix agenix --rekey
Output decrypted content to stdout:
# Decrypt to stdout
agenix -d secret.age
# Decrypt with specific identity
agenix -d secret.age -i ~/.ssh/id_ed25519
# Decrypt to file
agenix -d secret.age > /tmp/secret.txt
-e, --edit FILE: Edit FILE using $EDITOR-r, --rekey: Re-encrypt all secrets with updated recipients-d, --decrypt FILE: Decrypt FILE to stdout-i, --identity PATH: Private key path for decryption-v, --verbose: Enable verbose output-h, --help: Show help messageEDITOR: Editor used for -e flag (defaults to cp /dev/stdin in non-interactive mode)RULES: Path to secrets.nix file (defaults to ./secrets.nix)# Create a new secret
export EDITOR=vim
agenix -e database-password.age
# Edit existing secret with nano
EDITOR=nano agenix -e api-key.age
# View secret content
agenix -d api-key.age | less
# Rekey after adding new host
agenix --rekey
# Use custom secrets.nix location
RULES=~/nixos/secrets/rules.nix agenix -e secret.age
# Non-interactive secret creation
echo "my-secret-value" | agenix -e secret.age
# Create secret from file
cat secret.txt | agenix -e secret.age
Support both ED25519 and RSA keys:
# secrets.nix
let
server1-ed25519 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPJDyIr...";
server1-rsa = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC5...";
in
{
"secret.age".publicKeys = [
server1-ed25519
server1-rsa
];
}
# secrets.nix
let
# Import keys from separate files
admins = import ./admin-keys.nix;
servers = import ./server-keys.nix;
# Shared secrets
allKeys = admins ++ servers;
in
{
"shared-api-key.age".publicKeys = allKeys;
"admin-password.age".publicKeys = admins;
"server-cert.age".publicKeys = servers;
}
# admin-keys.nix
[
"ssh-ed25519 AAAAC3... alice@example.com"
"ssh-ed25519 AAAAC3... bob@example.com"
"ssh-ed25519 AAAAC3... charlie@example.com"
]
# secrets.nix
let
production = import ./keys/production.nix;
staging = import ./keys/staging.nix;
development = import ./keys/development.nix;
in
{
"prod-db-password.age".publicKeys = production;
"staging-db-password.age".publicKeys = staging;
"dev-db-password.age".publicKeys = development;
# Shared across environments
"shared-api-key.age".publicKeys = production ++ staging ++ development;
}
Enable Base64 PEM format for better diff readability:
# secrets.nix
{
"armored-secret.age" = {
publicKeys = [ user1 system1 ];
armor = true; # Use ASCII armor format
};
}
Benefits:
Use rage (Rust implementation) instead of age:
{ pkgs, agenix, ... }:
{
environment.systemPackages = [
(agenix.packages.x86_64-linux.default.override {
ageBin = "${pkgs.rage}/bin/rage";
})
];
age.ageBin = "${pkgs.rage}/bin/rage";
}
For systems with impermanence or ephemeral root:
{ config, ... }:
{
# Persist SSH host keys
environment.persistence."/persist" = {
files = [
"/etc/ssh/ssh_host_ed25519_key"
"/etc/ssh/ssh_host_ed25519_key.pub"
"/etc/ssh/ssh_host_rsa_key"
"/etc/ssh/ssh_host_rsa_key.pub"
];
};
# Point agenix to persistent keys
age.identityPaths = [
"/persist/etc/ssh/ssh_host_ed25519_key"
];
}
For unlocking encrypted disks:
{ config, ... }:
{
age.secrets.disk-encryption-key = {
file = ./secrets/disk-key.age;
};
boot.initrd.secrets = {
"/crypto_keyfile.bin" = config.age.secrets.disk-encryption-key.path;
};
boot.initrd.luks.devices.cryptroot = {
device = "/dev/sda2";
keyFile = "/crypto_keyfile.bin";
};
}
Use both agenix and sops-nix in the same system:
{ config, pkgs, ... }:
{
imports = [
inputs.agenix.nixosModules.default
inputs.sops-nix.nixosModules.sops
];
# agenix for simple secrets
age.secrets.api-key.file = ./secrets/api-key.age;
# sops for complex secrets with multiple formats
sops.secrets."database/password" = {};
}
Generate ED25519 keys (recommended):
ssh-keygen -t ed25519 -C "your-email@example.com"
Or RSA with 4096 bits:
ssh-keygen -t rsa -b 4096 -C "your-email@example.com"
# ✅ Good: Use strings to avoid store
{
age.identityPaths = [ "/var/lib/ssh/host_key" ];
}
# ❌ Bad: Nix path copies to store
{
age.identityPaths = [ /var/lib/ssh/host_key ];
}
{
age.secrets.sensitive-data = {
file = ./secrets/sensitive.age;
mode = "0400"; # Read-only for owner
owner = "service-user";
group = "service-group";
};
}
# ❌ WRONG: Puts plaintext in world-readable Nix store
{
services.myapp.apiKey = builtins.readFile config.age.secrets.api-key.path;
}
# ✅ CORRECT: Read at runtime
{
systemd.services.myapp = {
script = ''
export API_KEY=$(cat ${config.age.secrets.api-key.path})
${pkgs.myapp}/bin/myapp
'';
};
}
# Update secret
agenix -e secret.age
# Deploy to all systems
nixops deploy
# Or use CI/CD
git commit -m "Rotate API keys"
git push
# secrets.nix
{
"prod-db.age".publicKeys = [ prod-admin prod-server ];
"dev-db.age".publicKeys = [ dev-admin dev-server ];
}
# Log secret access
{
systemd.services.audit-secrets = {
script = ''
${pkgs.inotify-tools}/bin/inotifywait -m /run/agenix/ -e access |
while read path action file; do
echo "Secret accessed: $file at $(date)" >> /var/log/secret-access.log
done
'';
};
}
# Auto-cleanup old secrets
{
systemd.tmpfiles.rules = [
"d /run/agenix 0755 root root 30d"
];
}
# .gitignore - Don't ignore encrypted secrets
# *.age # DON'T DO THIS
# DO ignore decrypted secrets
secrets/*.txt
secrets/*.key
!secrets/*.age # But commit encrypted ones
Create environment files for services:
{ config, pkgs, ... }:
let
# Generate environment file from secrets
mkEnvFile = secrets: pkgs.writeScript "load-env" ''
#!${pkgs.bash}/bin/bash
${pkgs.lib.concatMapStringsSep "\n" (s:
"export ${s.name}=$(cat ${s.path})"
) secrets}
'';
in {
age.secrets = {
api-key.file = ./secrets/api-key.age;
db-password.file = ./secrets/db-password.age;
};
systemd.services.myapp = {
script = ''
source ${mkEnvFile [
{ name = "API_KEY"; path = config.age.secrets.api-key.path; }
{ name = "DB_PASSWORD"; path = config.age.secrets.db-password.path; }
]}
${pkgs.myapp}/bin/myapp
'';
};
}
Generate config files with secrets:
{ config, pkgs, ... }:
{
age.secrets = {
db-password.file = ./secrets/db-password.age;
api-key.file = ./secrets/api-key.age;
};
systemd.services.myapp = {
preStart = ''
cat > /etc/myapp/config.yaml <<EOF
database:
password: $(cat ${config.age.secrets.db-password.path})
api:
key: $(cat ${config.age.secrets.api-key.path})
EOF
chmod 600 /etc/myapp/config.yaml
'';
};
}
Different secrets per host:
{ config, lib, ... }:
{
age.secrets = lib.mkMerge [
# Common secrets for all hosts
{
shared-api-key.file = ./secrets/shared-api-key.age;
}
# Production-specific secrets
(lib.mkIf (config.networking.hostName == "prod-server") {
prod-db-password.file = ./secrets/prod-db.age;
})
# Development-specific secrets
(lib.mkIf (config.networking.hostName == "dev-server") {
dev-db-password.file = ./secrets/dev-db.age;
})
];
}
Create reusable secret modules:
# modules/secrets.nix
{ config, lib, ... }:
with lib;
{
options.myorg.secrets = {
enable = mkEnableOption "organization secrets";
environment = mkOption {
type = types.enum [ "production" "staging" "development" ];
description = "Deployment environment";
};
};
config = mkIf config.myorg.secrets.enable {
age.secrets = {
api-key.file = ./secrets/${config.myorg.secrets.environment}/api-key.age;
db-password.file = ./secrets/${config.myorg.secrets.environment}/db-password.age;
};
};
}
Usage:
{
imports = [ ./modules/secrets.nix ];
myorg.secrets = {
enable = true;
environment = "production";
};
}
Check identity paths:
{
# Verify paths are correct
age.identityPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
}
Verify key exists:
ls -l /etc/ssh/ssh_host_ed25519_key
Check public key matches:
# Get public key from private key
ssh-keygen -y -f /etc/ssh/ssh_host_ed25519_key
# Compare with secrets.nix
Test decryption manually:
age -d -i /etc/ssh/ssh_host_ed25519_key secret.age
Check file permissions:
ls -l /run/agenix/
Verify owner/group:
{
age.secrets.mySecret = {
file = ./secrets/mySecret.age;
owner = "myuser"; # Make sure user exists
group = "mygroup"; # Make sure group exists
mode = "0440";
};
}
Check service user:
{
systemd.services.myservice = {
serviceConfig = {
User = "myuser"; # Must match secret owner
};
};
}
Ensure you have decryption access:
# Test with your SSH key
agenix -d secret.age -i ~/.ssh/id_ed25519
Check all secrets can be decrypted:
# Rekey with specific identity
agenix --rekey -i ~/.ssh/id_ed25519
Verify secrets.nix syntax:
nix-instantiate --eval secrets.nix
Check file path:
{
# Use correct relative path
age.secrets.api-key.file = ./secrets/api-key.age; # Relative to config file
# Or absolute path
age.secrets.api-key.file = /etc/nixos/secrets/api-key.age;
}
Verify file exists:
ls -l secrets/api-key.age
Convert to age-compatible format:
# ED25519 keys work directly
ssh-keygen -t ed25519
# RSA keys need conversion
ssh-keygen -t rsa -b 4096
Use ssh-to-age for conversion:
nix-shell -p ssh-to-age
ssh-keygen -y -f ~/.ssh/id_rsa | ssh-to-age
Check for accidental store inclusion:
# Search for secrets in store
nix-store -q --references /run/current-system | xargs -I {} nix-store -q --tree {} | grep -i secret
Fix: Use runtime reading:
# ❌ Wrong
config.password = builtins.readFile config.age.secrets.password.path;
# ✅ Correct
systemd.services.app.script = ''
export PASSWORD=$(cat ${config.age.secrets.password.path})
'';
Set identityPaths explicitly:
{
age.identityPaths = [ "~/.ssh/id_ed25519" ];
}
Check XDG_RUNTIME_DIR:
echo $XDG_RUNTIME_DIR
ls -l $XDG_RUNTIME_DIR/agenix/
Verify home-manager activation:
home-manager switch --show-trace
{
network = {
description = "Production deployment with secrets";
};
webserver = { config, pkgs, ... }:
{
deployment.targetHost = "web.example.com";
imports = [ inputs.agenix.nixosModules.default ];
age.secrets = {
ssl-cert.file = ./secrets/ssl-cert.pem.age;
ssl-key.file = ./secrets/ssl-key.pem.age;
};
services.nginx = {
enable = true;
sslCertificate = config.age.secrets.ssl-cert.path;
sslCertificateKey = config.age.secrets.ssl-key.path;
};
};
}
{
meta = {
nixpkgs = import <nixpkgs> {};
nodeNixpkgs = {
server = import <nixpkgs> {};
};
};
server = { config, pkgs, ... }:
{
deployment = {
targetHost = "server.example.com";
targetUser = "deploy";
};
imports = [ inputs.agenix.nixosModules.default ];
age.secrets.deploy-key = {
file = ./secrets/deploy-key.age;
mode = "600";
owner = "deploy";
};
};
}
{
deploy.nodes.server = {
hostname = "server.example.com";
profiles.system = {
path = deploy-rs.lib.x86_64-linux.activate.nixos self.nixosConfigurations.server;
};
};
nixosConfigurations.server = nixpkgs.lib.nixosSystem {
modules = [
agenix.nixosModules.default
{
age.secrets.deploy-key.file = ./secrets/deploy-key.age;
}
];
};
}
# .github/workflows/deploy.yml
name: Deploy with Secrets
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: cachix/install-nix-action@v18
- name: Setup SSH key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
- name: Rekey secrets if needed
run: |
nix run github:ryantm/agenix -- --rekey
- name: Deploy
run: |
nixos-rebuild switch --flake .#server --target-host root@server.example.com
{ config, pkgs, ... }:
{
age.secrets.app-env = {
file = ./secrets/app-env.age;
mode = "644";
};
virtualisation.oci-containers.containers.myapp = {
image = "myapp:latest";
volumes = [
"${config.age.secrets.app-env.path}:/app/.env:ro"
];
};
}
Before:
{
services.myapp.apiKey = "hardcoded-secret"; # Insecure!
}
After:
{
age.secrets.api-key.file = ./secrets/api-key.age;
systemd.services.myapp = {
script = ''
export API_KEY=$(cat ${config.age.secrets.api-key.path})
${pkgs.myapp}/bin/myapp
'';
};
}
sops -d secrets.yaml > secrets.txt
agenix -e secret.age < secrets.txt
# Before (sops)
{
sops.secrets.api-key = {};
}
# After (agenix)
{
age.secrets.api-key.file = ./secrets/api-key.age;
}
git-crypt unlock
cat secrets/api-key > /tmp/api-key
agenix -e api-key.age < /tmp/api-key
shred -u /tmp/api-key
.age files to Git0400 or 0440builtins.readFile on secret pathsdb-password.age not secret1.age.age files0777 or 0644 for secretsWhat agenix protects against:
What agenix does NOT protect against:
/run/agenix/)Age provides confidentiality but not authentication:
.age files can modify encrypted contentAs of 2024, age is not post-quantum safe:
{
inputs.agenix.url = "github:ryantm/agenix";
outputs = { nixpkgs, agenix, ... }:
{
nixosConfigurations.host = nixpkgs.lib.nixosSystem {
modules = [ agenix.nixosModules.default ];
};
};
}
# secrets.nix
{
"secret.age".publicKeys = [ user-key host-key ];
}
# configuration.nix
{
age.secrets.secret.file = ./secrets/secret.age;
}
Edit Secret
agenix -e secret.age # Create/edit secret
Rekey Secrets
agenix --rekey # Re-encrypt all secrets
Decrypt Secret
agenix -d secret.age # Decrypt to stdout
{
age.secrets.secret = {
file = ./secrets/secret.age; # Required
path = "/run/agenix/secret"; # Default
mode = "0400"; # Default
owner = "root"; # Default
group = "root"; # Default
};
}
Local user key
cat ~/.ssh/id_ed25519.pub
Remote host key
ssh-keyscan hostname
GitHub user keys
curl https://github.com/user.keys
This comprehensive skill covers everything you need to securely manage secrets in NixOS with agenix!