agenix-secrets

Create age-encrypted secrets and wire them into NixOS modules.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "agenix-secrets" with this command: npx skills add edmundmiller/dotfiles/edmundmiller-dotfiles-agenix-secrets

Agenix Secrets

Create age-encrypted secrets and wire them into NixOS modules.

Repo Secret Layout

hosts/<host>/secrets/ ├── secrets.nix # Public key → .age file mapping (NOT imported into NixOS) ├── my-secret.age # Encrypted secret file └── restic/ # Subdirectories supported └── repo.age hosts/shared/secrets/ ├── secrets.nix # Shared cross-host secrets └── host-keys.nix # Maps hostname → public key for filtering

Auto-Wiring

modules/agenix.nix auto-generates age.secrets from secrets.nix :

  • Each "name.age" entry becomes age.secrets.<name> (.age suffix stripped)

  • Default owner = config.user.name (via mkDefault — overridable)

  • Default file points to hosts/<host>/secrets/<name>.age

  • Decrypted to /run/agenix/<name> at activation time

You do NOT need to set age.secrets.<name>.file — only override owner /group /mode when the default user shouldn't own it.

Workflow: Add a New Secret

  1. Add entry to secrets.nix

hosts/<host>/secrets/secrets.nix

let edmundmiller = "ssh-ed25519 AAAAC3..."; nuc = "ssh-ed25519 AAAAC3..."; in { "my-secret.age".publicKeys = [ edmundmiller nuc ]; }

Both user and host keys are needed — user key to encrypt/edit, host key to decrypt on deploy.

  1. Create the encrypted file

cd hosts/<host>/secrets

Pipe content (non-interactive)

printf 'SECRET_VALUE' | age
-r "ssh-ed25519 AAAA...user"
-r "ssh-ed25519 AAAA...host"
-o my-secret.age

Verify decryption

age -d -i ~/.ssh/id_ed25519 my-secret.age

The agenix -e CLI requires an interactive editor. For agent workflows, use age directly with -r for each recipient public key from secrets.nix .

  1. Reference in NixOS module

Simple: file path reference (most common)

services.myapp.environmentFile = config.age.secrets.my-secret.path;

Override owner when service runs as different user

age.secrets.my-secret = { owner = "myapp"; group = "myapp"; };

  1. Deploy

Stage by directory — NOT by filename (staging a .age path directly is blocked by the sandbox)

git add hosts/<host>/secrets/ git commit -m "secrets: add my-secret" git push && hey nuc

Sandbox note: The pi sandbox blocks git add (and any bash command) that explicitly references a .age file path. Staging the parent directory avoids this — git add hosts/<host>/secrets/ stages all changes in the dir without naming .age files directly.

Pattern: HA secrets.yaml via !secret

Home Assistant's YAML supports !secret key references. The nixpkgs HA module post-processes generated YAML to unquote ! tags (sed converts '!secret foo' → !secret foo ).

In HA module config:

services.home-assistant.config.homeassistant = { latitude = "!secret latitude"; # Unquoted by nixpkgs sed post-processor longitude = "!secret longitude"; };

Decrypt with correct owner and symlink into HA config dir:

age.secrets.hass-secrets = { owner = "hass"; group = "hass"; }; systemd.tmpfiles.settings."10-hass-nix-yaml" = { "${config.services.home-assistant.configDir}/secrets.yaml" = { L.argument = config.age.secrets.hass-secrets.path; }; };

The .age file contains standard HA secrets.yaml format:

latitude: 33.083423 longitude: -96.820367

Pattern: Update Existing .age File

Decrypt → modify → re-encrypt. Common when adding vars to an existing env file.

Decrypt to temp

age -d -i ~/.ssh/id_ed25519 hosts/<host>/secrets/my-env.age > /tmp/my-env.txt

Modify

echo "NEW_VAR=value" >> /tmp/my-env.txt

Re-encrypt (overwrites existing .age)

age
-r "ssh-ed25519 AAAA...user"
-r "ssh-ed25519 AAAA...host"
-o hosts/<host>/secrets/my-env.age /tmp/my-env.txt

Clean up

rm /tmp/my-env.txt

Verify

age -d -i ~/.ssh/id_ed25519 hosts/<host>/secrets/my-env.age

Pattern: 1Password + Agenix (Login Credentials)

For services needing a username/password — store in both 1Password (human access) and agenix (machine access).

1. Generate creds and store in 1Password

PASSWORD=$(op item create
--category=login
--title="MyService"
--vault="Private"
--url="http://nuc:8080"
--generate-password="32,letters,digits"
username="emiller"
--format=json | jq -r '.fields[] | select(.id == "password") | .value')

2. Create agenix env file

printf 'MYSERVICE_USER=emiller\nMYSERVICE_PASSWORD=%s' "$PASSWORD" | age
-r "ssh-ed25519 AAAA...user"
-r "ssh-ed25519 AAAA...host"
-o hosts/<host>/secrets/myservice-env.age

3. Add to secrets.nix, wire environmentFile, set owner (see below)

Pattern: Service Environment File with Owner Override

Most NixOS services run as a dedicated user (not emiller ). Override the secret owner so the service can read it.

In host config (e.g., hosts/nuc/default.nix):

modules.services.myservice = { enable = true; environmentFile = config.age.secrets.myservice-env.path; };

Override default owner (emiller) → service user

age.secrets.myservice-env.owner = "myservice";

The service username typically matches the service name. Check with grep -r "DynamicUser|User=" /etc/systemd/system/<service>* on the target host if unsure.

Key public keys

Read from hosts/<host>/secrets/secrets.nix — don't hardcode. The file defines edmundmiller (user SSH key) and host-specific keys (e.g., nuc ).

Common Pitfalls

  • Never builtins.readFile a secret path — leaks plaintext to world-readable Nix store

  • agenix -e fails in non-interactive shells — use age -r directly instead

  • Forgot host key in publicKeys — secret won't decrypt on target machine

  • Wrong owner — service can't read /run/agenix/<name> (default owner is config.user.name )

  • Shared secrets need entry in hosts/shared/secrets/secrets.nix AND host key in host-keys.nix

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

tmux-status-debug

No summary provided by upstream source.

Repository SourceNeeds Review
General

jj-history-investigation

No summary provided by upstream source.

Repository SourceNeeds Review
General

using-jj-workspaces

No summary provided by upstream source.

Repository SourceNeeds Review
General

nix-rebuild

No summary provided by upstream source.

Repository SourceNeeds Review