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.sessionVariablesExtra 或 programs.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 失败时,按以下顺序排查,不要瞎试:
回滚
home-manager generations查看历史 generations,用home-manager rollback恢复到上一个可用状态。这一步解决 80% 的激活后问题。检查 profile 目录是否在 PATH
如果装了包但命令找不到,执行:echo $PATH | grep .nix-profile如果没有,说明
hm-session-vars.sh没有被加载,按前面 section 2 的方法补上。激活机制确认
看你是用哪种方式激活的:- 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)也是排查点。- NixOS module:激活通过系统 service 完成,检查
查看具体日志
journalctl --user -u home-manager-... -n 50
结论
home-manager 值得引入,但建议在第一次配置时做好三件事:
- stateVersion 写死并注释,永远不要手痒去改。
- 想清楚包管理归属:NixOS module 场景下
useGlobalPkgs = true+useUserPackages = true,同时确保 shell 环境变量已正确加载。 - 旧 dotfiles 迁移要有预案:高频项正式迁移为声明式配置,低频项临时 source 兜底,但要有迁移完成的时间点。
做到这三点,home-manager 基本不会给你挖坑。剩下的坑,交给 home-manager generations 兜底。