关于 agenix-secrets
用于创建 age 加密密钥并将其接入 NixOS 模块的工作流技能。内容涵盖接收者公钥、自动配置密钥、服务所有权和常见部署模式。
主要功能
- 01将公钥映射到加密文件
- 02使用 age 接收者创建密钥
- 03自动将密钥接入 NixOS
- 04配置密钥所有者和路径
- 05说明服务及 Home Assistant 模式
适用场景
- 01为主机添加加密密钥
- 02向 NixOS 服务提供凭据
- 03管理 Home Assistant 的 secrets.yaml 值
- 04更新现有加密环境文件
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ edmundmiller/ dotfiles --skill agenix-secrets安装后在 Claude Code 中输入/agenix-secrets确认已加载。
详细介绍
Agenix 密钥
创建由 age 加密的密钥,并将其接入 NixOS 模块。
仓库密钥布局
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
自动接入
modules/agenix.nix 会根据 secrets.nix 自动生成 age.secrets:
- 每个
"name.age"条目都会变成age.secrets.<name>(去掉.age后缀) - 默认
owner = config.user.name(通过mkDefault设置,可覆盖) - 默认
file指向hosts/<host>/secrets/<name>.age - 激活时解密到
/run/agenix/<name>
无需设置 age.secrets.<name>.file——仅当默认用户不应拥有该密钥时,才覆盖 owner/group/mode。
工作流:添加新密钥
1. 在 secrets.nix 中添加条目
# hosts/<host>/secrets/secrets.nix
let
edmundmiller = "ssh-ed25519 AAAAC3...";
nuc = "ssh-ed25519 AAAAC3...";
in {
"my-secret.age".publicKeys = [ edmundmiller nuc ];
}
用户密钥和主机密钥都必不可少——用户密钥用于加密和编辑,主机密钥用于部署时解密。
2. 创建加密文件
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
agenix -e CLI 需要交互式编辑器。对于 Agent 工作流,请直接使用 age,并通过 -r 指定 secrets.nix 中每个接收方的公钥。
3. 在 NixOS 模块中引用
# 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";
};
4. 部署
# 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
沙盒注意事项: pi 沙盒会阻止显式引用
.age文件路径的git add(以及任何 bash 命令)。暂存父目录即可绕过限制——git add hosts/<host>/secrets/会暂存目录中的所有更改,而不会直接指定.age文件。
模式:通过 !secret 使用 HA secrets.yaml
Home Assistant 的 YAML 支持 !secret key 引用。nixpkgs 的 HA 模块会对生成的 YAML 进行后处理,以移除 ! 标签两侧的引号(sed 会将 '!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;
};
};
.age 文件包含标准格式的 HA secrets.yaml:
latitude: 33.083423
longitude: -96.820367
模式:更新现有的 .age 文件
解密 → 修改 → 重新加密。常见于向现有环境文件添加变量时。
# 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
模式:1Password + Agenix(登录凭据)
对于需要用户名和密码的服务,请同时存入 1Password(供人访问)和 agenix(供机器访问)。
# 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)
模式:覆盖所有者的服务环境文件
大多数 NixOS 服务以专用用户身份运行(而非 emiller)。请覆盖密钥的所有者,以便服务能够读取它。
# 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";
服务用户名通常与服务名称一致。如不确定,可在目标主机上使用 grep -r "DynamicUser\|User=" /etc/systemd/system/<service>* 检查。
密钥公钥
从 hosts/<host>/secrets/secrets.nix 读取,不要硬编码。该文件定义了
edmundmiller(用户 SSH 密钥)和特定于主机的密钥(例如 nuc)。
常见问题
- 切勿对密钥路径使用
builtins.readFile——这会将明文泄露到所有用户均可读取的 Nix 存储中 agenix -e在非交互式 Shell 中会失败——请改为直接使用age -r- 忘记在 publicKeys 中添加主机密钥——密钥将无法在目标机器上解密
- 所有者错误——服务无法读取
/run/agenix/<name>(默认所有者为config.user.name) - 共享密钥需要在
hosts/shared/secrets/secrets.nix中添加条目,并在host-keys.nix中添加主机密钥