编程 Home Manager 踩坑记录:stateVersion、包管理归属、dotfiles 迁移与激活排查

2026-09-02 00:04:09

Home Manager 踩坑记录:stateVersion、包管理归属、dotfiles 迁移与激活排查

1. stateVersion:不是版本号,是“不兼容行为的开关”

现象

很多人在升级 NixOS 或 home-manager 时,为了“图个新版本号”把 stateVersion 顺手改大,结果某些变量、行为悄悄变化,且完全不报错。这类 bug 极难排查。

本质

stateVersion 并不代表当前系统状态或软件版本,它是一个内部兼容性开关。home-manager 会根据它决定是否启用某些新行为(比如默认 PATH 拼接方式、shell 初始化逻辑等)。改大它 = 一次性开启所有不兼容变更,但没有任何显式提示。

正确姿势

  • 只在首次创建配置时设置一次,之后永远保持该值不变。
  • 如果确实需要新行为,应该显式写相应配置项,而不是改 stateVersion
  • 写注释提醒自己:# 不要动,动了查三天
home.stateVersion = "23.11"; # 首次安装定死,不再修改

2. useUserPackages / useGlobalPkgs:用户包装到哪,谁说了算?

这两个选项决定用户级包(home.packages)的实际安装位置和管理者:

选项作用
useGlobalPkgs = true用户包装入系统 profile(/nix/var/nix/profiles/per-user/xxx),与系统包共用 NixOS 的 channel / flake 输入,避免重复编译
useUserPackages = true用户包由 home-manager 管理,生成独立 profile,并写入 ~/.nix-profile

建议组合

如果通过 NixOS module 方式使用 home-manager:

home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
  • useGlobalPkgs = true:复用系统已有依赖,避免同一份源码被不同 channel 重复编译,省时间省空间。
  • useUserPackages = true:即使系统包与用户包有冲突,也能在 profile 层面隔离,home-manager switch 回滚安全。

坑点

如果 useUserPackages = true,但 没有在相应 shell 配置中加载环境变量(比如你只是用 home-manager 装包,却没用它管理 .bashrc),那么 ~/.nix-profile/bin 不会自动进入 PATH,表现为包装上了但命令找不到。

解决:手动 source home-manager 生成的环境变量文件(位置因版本而异,常见于 ~/.nix-profile/etc/profile.d/hm-session-vars.sh):

# ~/.bashrc
. "$HOME/.nix-profile/etc/profile.d/hm-session-vars.sh"

更推荐直接用 home.sessionVariablesExtraprograms.bash.enable = true 让 home-manager 接管 shell。

3. dotfiles 迁移:老 .bashrc 被覆盖,如何体面过渡?

现象

第一次 home-manager switch 启用了 programs.bash.enable = true 后,home-manager 会生成者将原来的 ~/.bashrc 备份为 ~/.bashrc.before-nix,然后新 .bashrc 成为 home-manager 管理的符号链接。如果你原来的 .bashrc 里有大量自定义内容,会发现很多 alias、函数、环境变量统统失效。

推荐迁移路径

按优先级分三档处理:

第一档:正式迁移(高频项)

  • alias → programs.bash.shellAliases
  • 环境变量 → home.sessionVariables
  • 初始化命令 → programs.bash.initExtra
home.sessionVariables = {
  EDITOR = "vim";
  LANG = "zh_CN.UTF-8";
};

programs.bash = {
  enable = true;
  shellAliases = {
    ll = "ls -alF";
    g = "git";
  };
  initExtra = ''
    # custom function
    myip() { curl -s ifconfig.me; }
  '';
};

第二档:兜底 source(低频旧配置)
对于尚未迁移的旧文件,可以暂时强制加载,但要注意路径冲突:

programs.bash.initExtra = ''
  [ -f ~/.bashrc.before-nix ] && source ~/.bashrc.before-nix
'';

这只是过渡方案。高频项长期留在兜底文件里会失去声明式意义——多台机器无法同步、无法审计、无法回滚。建议尽快全量迁完。

判断标准

  • 一周内用过 2 次以上的命令 → 正式迁移
  • 一些一次性偏好或调试代码 → 可以放弃,不要带进新配置

4. 激活失败排查顺序

home-manager switch 失败时,按以下顺序排查,不要瞎试:

  1. 回滚
    home-manager generations 查看历史 generations,用 home-manager rollback 恢复到上一个可用状态。这一步解决 80% 的激活后问题。

  2. 检查 profile 目录是否在 PATH
    如果装了包但命令找不到,执行:

    echo $PATH | grep .nix-profile
    

    如果没有,说明 hm-session-vars.sh 没有被加载,按前面 section 2 的方法补上。

  3. 激活机制确认
    看你是用哪种方式激活的:

    • NixOS module:激活通过系统 service 完成,检查 systemctl --user status home-manager-xxx 或者 nixos-rebuild switch 日志。
    • standalone home-manager:激活走 home-manager switch 直接运行,可在命令前加 HOME_VERBOSE=1-v 看详细输出。

    如果配置中有 systemd.user 服务或 startAsUserService,则用户级服务是否被正确启动(systemctl --user list-units | grep home)也是排查点。

  4. 查看具体日志

    journalctl --user -u home-manager-... -n 50
    

结论

home-manager 值得引入,但建议在第一次配置时做好三件事:

  1. stateVersion 写死并注释,永远不要手痒去改。
  2. 想清楚包管理归属:NixOS module 场景下 useGlobalPkgs = true + useUserPackages = true,同时确保 shell 环境变量已正确加载。
  3. 旧 dotfiles 迁移要有预案:高频项正式迁移为声明式配置,低频项临时 source 兜底,但要有迁移完成的时间点。

做到这三点,home-manager 基本不会给你挖坑。剩下的坑,交给 home-manager generations 兜底。

复制全文 生成海报 home-manager Nix NixOS dotfiles

推荐文章

程序员茄子在线接单