agenix-secrets について
age で暗号化したシークレットを作成し、NixOS モジュールに接続するワークフロースキルです。受信者キー、自動設定、サービスの所有者、一般的なデプロイ方法を扱います。
主な機能
- 01公開鍵と暗号化ファイルの対応付け
- 02age の受信者を指定してシークレットを作成
- 03NixOS へのシークレットの自動設定
- 04所有者とパスの設定
- 05サービスと Home Assistant の設定例
活用シーン
- 01ホスト用の暗号化シークレットを追加
- 02NixOS サービスに認証情報を提供
- 03Home 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 には対話型エディターが必要です。エージェントのワークフローでは、secrets.nix の各受信者の公開鍵に対して -r を指定し、age を直接使用してください。
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 コマンド)をブロックします。親ディレクトリをステージすると、.ageファイルを直接指定せずにディレクトリ内のすべての変更をステージできます(git add hosts/<host>/secrets/)。
パターン:!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 ファイルを更新する
復号 → 変更 → 再暗号化します。既存の env ファイルに変数を追加する場合によく使います。
# 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は失敗します — 代わりにage -rを直接使用してください - publicKeys にホストキーを忘れた場合 — 対象マシンでシークレットを復号できません
- 所有者が間違っている場合 — サービスが
/run/agenix/<name>を読み取れません(デフォルトの所有者はconfig.user.nameです) - 共有シークレットには
hosts/shared/secrets/secrets.nixのエントリと、host-keys.nixのホストキーが必要です