使用 home‑manager 在 NixOS 中管理 sops‑nix secrets 的 git‑safe 方式

发布: (2026年2月17日 GMT+8 16:57)
10 分钟阅读
原文: Dev.to

Source: Dev.to

当你对 NixOS 配置(或任何 dotfiles)进行版本控制时,整个系统就可以被复现。
然而,存放在这些文件中的 API 密钥或密码会以 明文 形式保存在仓库里。

  • 你不能仅仅用 .gitignore 忽略它们——NixOS 在构建/激活时需要这些值。
  • 你也不想把它们暴露为环境变量。

解决方案: 使用 sops‑nix——用 SOPS 加密秘密并提交加密后的文件。
在激活时,sops‑nix 会使用仅存在于你机器上的密钥进行解密(仓库中没有这些密钥)。
这样你的 dotfiles 仍然是完全可复现、可推送且不泄漏的。

概览

  • 第一个需要管理的密钥:EXA_API_KEY(用于 Exa.ai)。
  • 本指南展示了使用 age 密钥(从你的 SSH 密钥派生)进行 单机 设置(基于 Michael Stapelberg 的方法并进行了修复)。
  • 可选:将自己添加为受信任用户,以消除构建警告。
# configuration.nix
nix.settings.trusted-users = [ "@wheel" "noor" ];

重新构建一次:

sudo nixos-rebuild switch

1️⃣ 准备 Age 密钥存储

# Create the directory where sops will look for age keys
mkdir -p ~/.config/sops/age/

进入一个包含所需工具的临时 Nix shell:

nix shell nixpkgs#ssh-to-age nixpkgs#age

1.1 从你的 SSH 私钥创建 age 身份

# If your SSH key is passphrase‑protected, you’ll be prompted for it.
ssh-to-age -private-key -i ~/.ssh/id_ed25519 -o ~/.config/sops/age/keys.txt

锁定权限(强烈推荐):

chmod 600 ~/.config/sops/age/keys.txt

1.2 获取你的 个人 age 接收者

age-keygen -y ~/.config/sops/age/keys.txt
# → prints something like: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxx

复制打印出的值——稍后会用到。

1.3 确认系统 SSH 主机密钥已存在

ls /etc/ssh/ssh_host_ed25519_key   # should exist

如果 不存在,生成所有主机密钥:

sudo ssh-keygen -A

1.4 获取 系统 age 接收者(从主机密钥派生)

cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age
# → prints something like: age1yyyyyyyyyyyyyyyyyyyyyyyyyyyy

同样复制此值。

退出临时 shell:

exit

现在你拥有 两个接收者

接收者示例值
admin(个人)age1xxxxx…
system(主机)age1yyyyy…

2️⃣ 创建 .sops.yaml 配置

在你的 dotfiles 仓库根目录创建文件(例如 ~/nixos-dotfiles/.sops.yaml):

# .sops.yaml
keys:
  - &admin age1xxxxx...   # Your personal age recipient
  - &system age1yyyyy...  # Your system's SSH host key

creation_rules:
  - path_regex: secrets/[^/]+\.yaml$
    key_groups:
      - age:
          - *admin
          - *system

这段配置的作用

  • 个人密钥(&admin – 让你以普通用户身份解密/编辑机密(无需 sudo,也不需要重复输入 SSH 密码)。
  • 系统密钥(&system – 让系统在启动时自动解密机密,并将其暴露在 /run/secrets/ 下。

⚠️ 注意事项: 如果你更换/重新生成 SSH 主机密钥(/etc/ssh/ssh_host_ed25519_key),必须使用 新的 主机接收者重新加密所有机密,否则启动时的解密将会失败。

3️⃣ 创建并加密你的第一个密钥

cd ~/nixos-dotfiles
mkdir -p secrets

使用 SOPS 打开文件(nix run 包装器会拉取正确的版本):

nix run nixpkgs#sops -- secrets/secrets.yaml

当编辑器打开时,添加密钥:

EXA_API_KEY: "your_actual_api_key_here"

保存并退出。文件现在已加密。

验证加密:

cat secrets/secrets.yaml
# → you’ll see values starting with ENC[AES256_GCM,...]

只有你(使用 ~/.config/sops/age/keys.txt)可以解密它。

以后编辑/查看:

nix run nixpkgs#sops -- secrets/secrets.yaml

4️⃣ 将 sops‑nix 接入你的 NixOS 配置

4.1 将 sops‑nix 添加为 Flake 输入

# flake.nix
{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";

    home-manager = {
      url = "github:nix-community/home-manager";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    sops-nix = {
      url = "github:Mic92/sops-nix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { self, nixpkgs, home-manager, sops-nix }:
    let
      system = "x86_64-linux";
    in {
      nixosConfigurations.nixos = nixpkgs.lib.nixosSystem {
        inherit system;
        modules = [
          sops-nix.nixosModules.sops          # **Important:** `sops-nix.homeManagerModules.sops` 必须被导入,以便在 `home.nix` 中可见 `sops` 选项。
        ];
      };
    };
}

4.2 在 home.nix 中配置密钥

# home.nix
{ config, ... }:

{
  # 告诉 sops‑nix 你的 age 私钥所在位置
  sops.age.keyFile = "${config.home.homeDirectory}/.config/sops/age/keys.txt";

  # 为该用户设置默认的 SOPS 文件
  sops.defaultSopsFile = ./secrets/secrets.yaml;

  # 声明我们想要公开的密钥
  sops.secrets.EXA_API_KEY = { };

  # 导出一个指向解密后文件的会话变量
  home.sessionVariables = {
    EXA_API_KEY = "${config.sops.secrets.EXA_API_KEY.path}";
  };
}

你将得到的效果

  • 登录时,EXA_API_KEY 指向位于 /run/user/<uid>/secrets/EXA_API_KEY 的临时文件。
  • 该文件中包含 原始密钥值
  • 在 shell 中读取它:cat "$EXA_API_KEY"

注意: sops‑nix 将密钥存储为 文件,而不是原始的环境变量。能够从文件路径读取密钥的程序可以直接使用。

5️⃣ 使用密钥

# In a shell
echo "My key is: $(cat "$EXA_API_KEY")"

如果程序需要直接在环境变量中获取原始值,你可以这样包装:

export EXA_API_KEY=$(cat "$EXA_API_KEY")
my-program --api-key "$EXA_API_KEY"

回顾

步骤你做了什么
1️⃣生成了一个来自你的 SSH 密钥的 age 身份,并收集了个人和系统接收者。
2️⃣创建了 .sops.yaml,它告诉 SOPS 哪些接收者可以加密/解密 secrets/ 下的文件。
3️⃣EXA_API_KEY 添加到 secrets/secrets.yaml 并使用 SOPS 加密。
4️⃣sops‑nix 添加到你的 flake,启用了该模块用于 NixOS 和 Home Manager,并指向你的密钥文件和秘密文件。
5️⃣导出了指向已解密秘密文件的会话变量,准备使用。

现在你的 NixOS 配置(或任何 dotfiles 仓库)可以安全地推送到公共或共享的 Git 远程,永不泄露秘密。 🎉

Source:

使用 sops‑nix 添加 API Key

1. 为什么需要这一步?

API_KEY 不是 文件路径。要让期望读取文件的程序正常工作,你有两种选择:

  1. 配置程序 使其从文件读取(许多程序支持 *_FILE 环境变量)。
  2. 包装/启动程序,让它把文件内容读取到环境变量中。

重新构建后,关闭所有已有的终端窗口——已打开的 shell 不会看到新的变量。打开一个全新的终端即可获取更新后的环境。

2. 系统级 sops 配置

~/nixos-dotfiles/configuration.nix 中添加以下内容(注意 NixOS 模块需要外层 { … }: 包装器):

{ ... }:

{
  sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
}

这段配置的作用

  • 声明系统级 SSH 主机密钥,供 NixOS 的 sops 模块在解密时使用。
  • 必须放在 configuration.nix(NixOS 层),不能放在 home.nix
  • Home‑Manager 的 sops 模块会使用它自己的 sops.age.keyFile(在第 4 步中设置)。
  • 使得在启动时无需用户交互即可解密,并为以后系统级的 secret 做准备。

3. 重建并应用

sudo nixos-rebuild switch --flake ~/nixos-dotfiles#nixos

重建后的选项

选项命令备注
A – 重启(最简单)sudo reboot确保所有服务都能看到新的环境变量。
B – 重启服务bash\nsystemctl --user daemon-reload\nsystemctl --user restart sops-nix.service\n如果服务之前未激活(home‑manager 问题),可能无效。

4. 验证 secret

打开一个 终端并运行:

echo $EXA_API_KEY
# → 应该会打印类似 /run/user/1000/secrets/EXA_API_KEY 的路径

cat $EXA_API_KEY
# → 打印实际的 API‑key 值

5. 编辑加密的 secrets 文件

cd ~/nixos-dotfiles
nix run nixpkgs#sops -- secrets/secrets.yaml

完成修改后,保存、提交并重建

git add flake.nix configuration.nix home.nix .sops.yaml secrets/secrets.yaml
git commit -m "Add EXA_API_KEY secret with sops-nix"
git push

重要提示:

  • 你的 age 私钥(~/.config/sops/age/keys.txt永远不要提交——它应当保留在仓库之外。
  • 加密文件(secrets/secrets.yaml)可以安全提交,因为没有私钥它是无用的。

6. 我们做了什么(总结)

  • 生成了 age 密钥,使用现有的 SSH 主机密钥(ssh-to-age),并将其存放在 ~/.config/sops/age/keys.txt(未跟踪)。
  • 配置 .sops.yaml,包含两个接收者:个人 age 密钥(用于编辑)和系统 SSH 主机密钥(用于启动时解密)。
  • 创建 加密的 secrets 文件(secrets/secrets.yaml),使用 sops 完成。
  • sops‑nix 接入 flake:在 configuration.nix 中加入 NixOS 模块,在 home.nix 中加入 Home‑Manager 模块。
  • 暴露 secret 为环境变量 $EXA_API_KEY,该变量指向解密后位于 /run/user/1000/secrets/EXA_API_KEY 的文件。

加密文件可以安全推送;私钥永远留在本机。(在后续文章中,我会展示如何在 Kubernetes 集群中通过 YAML 文件使用 sops。)

0 浏览
Back to Blog

相关文章

阅读更多 »

AI 编码工具:为什么开发者意见不合

AI‑Coding“辩论”并非真正的辩论 你会听到两个截然不同的故事: 朋友的创业公司创始人——“我们的团队现在使用 AI,功能发布速度提升了一倍。我是 e...”

谁在招聘 — 2026年2月

在以开发者为先的公司开放职位:产品工程师、Developer advocates 或 Community builders?以全新的 dev tools 机会开启新的一年。