缘起#
前面几篇介绍了通过environment.systemPackages安装软件的方法。在NixOS中,除了这种方法,还可以使用Home Manager、nix shell、nix run和nix develop等工具来安装或使用软件。它们都能提供软件环境,但适用的场景有所不同。
之前在Python环境管理方式总结中,我介绍过不同环境管理工具的使用方法。NixOS的软件管理也有类似的问题:如果没有统一的使用方式,系统、用户和项目中的软件环境就容易混在一起。
因此,这里总结一下NixOS中几种常用的软件安装与环境管理方式,介绍它们的用途和使用方法,最后给出一些选择建议。让AI帮忙安装软件时,也可以根据用途说明应该修改系统配置、用户配置,还是项目的开发环境。
前置条件#
- 已有可以正常构建的NixOS flake配置;
- 了解NixOS模块的基本用法,可以参考第六篇;
- 使用Home Manager的部分需要已经完成相应集成。
下面的配置都是简化示例,不包含私人配置。项目开发环境部分会使用一个独立的示例目录。
软件安装与环境管理方式分类#
根据软件的使用范围,可以分为以下几类:
- 系统软件包:通过NixOS配置管理,供这台电脑上的用户使用。
- 用户软件包:通过Home Manager管理,适合个人工具和程序设置。
- 临时软件环境:通过
nix shell或nix run使用,适合临时运行某个工具。 - 项目开发环境:通过项目的
devShell管理,适合解释器、编译器和项目依赖。
这些方式可以配合使用。例如,系统中保留常用工具,同时为不同项目提供各自的开发环境。
系统软件包管理#
安装系统软件包#
如果希望电脑上的用户都能使用某些常用命令,可以放进NixOS模块:
| |
这只是模块中的相关部分,原有配置不用删掉。ripgrep提供的命令叫rg,包名和命令名不一定相同。应用配置之后,系统环境里就能找到这些工具。
公共文件放哪些包,还是要看是否每台电脑都需要。几台电脑都用的诊断工具可以放在公共模块,只有一台电脑需要的软件就放在对应主机配置里,具体拆分见第三篇。
建议这里只保留需要长期使用的工具。对于偶尔使用的软件,可以先通过临时环境运行,避免系统软件包列表过于庞杂。
启用系统服务#
例如想启用SSH服务,需要的是:
| |
对应模块会准备软件和服务配置。注意,只把pkgs.openssh加入软件包列表,并不会启用SSH服务。如果需要让其他电脑连接本机,还需要启用服务,并检查认证和访问设置。
有些桌面程序也有programs.*模块,除了装包还会处理系统集成。需要这类功能时,先查相应模块,再决定是否直接加包。NixOS手册的软件包管理章节介绍了相关方法。
使用Home Manager管理用户软件#
Home Manager可以管理用户的软件和设置。例如在已有的Home Manager模块里写:
| |
home.packages把包加入这个用户的环境。programs.fzf.enable则使用Home Manager的fzf模块,除了软件本身,还可以配合已启用的Shell模块提供集成。这里的选项属于Home Manager,要放进用户模块,不能直接抄到普通NixOS模块里。
这里的jq和前面的系统软件包例子重复,是为了展示两种安装方式,实际使用时可以选择其中一种。对于与个人Shell、编辑器设置相关的工具,使用Home Manager可以把软件和配置一起管理。
Home Manager作为NixOS模块集成时,通常随nixos-rebuild应用;独立使用时,则有自己的home-manager switch。所以“放在用户配置里”不等于“这次一定不用重建系统”,还要看采用了哪种集成方式。安装和使用方式可以参看Home Manager手册。
Home Manager还可以管理配置文件。首次使用时如何处理已有配置文件,以及怎样组织用户配置,将在下一篇中介绍。
临时软件环境#
使用nix shell#
假设只想用jq检查一个JSON文件,可以先进入一个有这些工具的Shell:
| |
前一条命令准备软件,并在新的Shell中把它们加入PATH。exit退出后,回到之前的Shell环境,不需要为了试用一个工具而修改配置、重建系统。
如果只执行一条命令,也可以不用交互式Shell:
| |
这里的nixpkgs#jq里,nixpkgs通过flake registry解析到一个输入,#jq选择软件包。它不一定和系统flake.lock里的Nixpkgs是同一个版本。临时试用这样写很方便,想长期复用同一套环境时,就应该把输入锁在项目里。
“临时”指的是这次Shell的环境。下载的软件仍然在Nix store里,之后是否清理要看垃圾回收引用;程序写到家目录或者项目里的文件,也不会因为退出Shell而消失。
使用nix run#
如果只是想启动一个程序,还可以用:
| |
这里的hello是一个演示程序。第一条命令直接运行它;第二条命令中,--之后的参数会传给hello程序,用于指定输出的文字。
对于软件包,nix run会根据包的元数据和名称选择主要可执行文件,不是随便运行包里任何一个命令。一个包带了好几个程序,或者没有合适的默认入口时,用nix shell ... --command ...明确指定命令反而更直接。详细规则见nix run文档。
注意,nix shell和nix-shell是不同命令。旧教程中常见的nix-shell -p jq和项目中的shell.nix属于另一种用法。这里继续使用本系列的flake方式。
使用devShell管理项目开发环境#
在开发项目时,往往需要固定的解释器、库和命令行工具。如果每次都手动创建临时环境,就需要重复指定这些依赖,换电脑时也容易遗漏。
这时可以在项目自己的flake里定义开发环境,叫作devShell。它跟系统配置可以使用不同的输入,项目更新依赖也不必顺便更新整台电脑。
创建项目与配置文件#
这里用一个Python加NumPy的环境,同时提供jq。在一个新的示例目录中开始:
| |
新建flake.nix:
| |
这是完整的演示flake。如果已有项目已经有flake.nix,就把需要的输出合进去,不要直接覆盖原文件。
这里的systems列出了两种Linux架构,genAttrs为每一种架构生成对应属性。legacyPackages.${system}选择那种架构的软件包集合;名字虽然带legacy,但在flake中这样取包很常见,不代表我们退回了旧式配置。
devShells.<system>.default是默认开发环境,pkgs.mkShell用来准备它。packages列出环境里的工具;python3.withPackages则提供一个已经带NumPy的Python,所以进入环境之后就能直接导入它。这个例子使用的架构和包也要适用于自己的电脑。mkShell的参数可以参看Nixpkgs文档。
创建和使用开发环境#
保存配置文件后,运行以下命令:
| |
新文件先加入Git索引,Nix才能在这个本地Git flake中读到它。nix flake lock把分支当前对应的输入锁定下来;以后分享项目时,flake.nix和flake.lock要一起提交。分支会继续更新,锁文件则让这个项目继续使用已锁定的版本。
进入开发环境之后试一下:
| |
这里不写死版本号,输出取决于锁定的输入。想只运行一次检查,也可以在项目目录执行:
| |
nix develop准备的是开发/构建环境,不只是把几个程序放到PATH里。对于这里的mkShell,我们主要用它提供工具;遇到编译项目,还可以加入相应库和构建依赖。命令的更多用法见nix develop文档。
它也不是容器。进入环境后仍然能访问自己的文件、网络和原有环境,运行的程序也会真的修改文件。锁住工具版本很有用,但不等于项目的源码、外部数据和所有运行结果都自动固定了。
Python依赖管理#
上面的withPackages适合依赖已经在Nixpkgs里的小例子。不需要再对这个store中的Python环境执行sudo pip install;store里的文件由Nix管理,也不适合手动往里面装东西。
如果项目原本就用venv、uv或其他工具管理Python依赖,也可以继续保留它们,让Nix提供解释器、编译工具和系统库。不过这些工具另外解析的依赖,仍然需要自己的锁定和安装过程,不能认为有了flake.lock就全部管住了。涉及预编译Python扩展时,还可能需要处理动态库兼容问题。
因此,可以根据项目选择由Nix管理Python包,或者由Nix提供基础环境,再通过Python工具管理依赖。两种方式的锁定范围不同,使用时需要分别记录。
问题解决#
软件版本与PATH#
同一个命令可能同时来自系统环境、用户环境和当前开发环境。出问题时,先看看Shell实际上找到了哪个:
| |
type -a可以看到多个候选,也能帮忙发现别名或函数;command -v显示当前选中的入口。readlink这条适用于它返回可执行文件路径的情况,可以继续看到对应的store路径。
如果实际版本和预期不同,需要检查PATH和Shell初始化文件。Shell配置可能再次修改PATH,激活Python虚拟环境也可能改变使用的解释器。调整配置之后,可以重新打开终端再检查。
同一个store路径被多处引用,并不会因此把包内容复制几份;不同输入或构建产生的版本,则可能同时存在。这种共存是Nix的优点,但选择了哪个入口还是要查清楚。
推荐的使用方法#
根据上述几种方式的用途,我推荐按以下方法选择:
| 用途 | 放在哪里 |
|---|---|
| 电脑上各个用户都需要的常用工具 | environment.systemPackages |
| 系统服务或需要系统集成的功能 | 对应的NixOS模块 |
| 个人常用工具和程序设置 | Home Manager |
| 偶尔用一下的命令 | nix shell或nix run |
| 项目的解释器、编译器和依赖 | 项目自己的devShell |
还有nix profile install这样的安装方法,它会修改用户profile,软件可以跨会话保留,也可以移除。但它不会自动成为配置仓库中的声明。如果已经决定用Home Manager记录个人环境,就没必要再把同一批长期软件分散到另一张安装清单里。
遇到Nixpkgs里没有的软件、需要修改构建参数的包,或者必须按原来的Linux目录布局运行的程序,还可能要写包、使用override/overrideAttrs,或者考虑容器。这类软件需要另外处理构建或运行环境,仍然可以根据用途放入相应的系统、用户或项目配置。上一篇的mkForce处理的是模块选项的优先级,和修改软件包构建不是一回事。
使用AI协助安装时,建议同时说明软件的用途和配置位置。例如,可以要求“给这个项目添加NumPy,并写入项目开发环境”。修改完成后,再进入相应环境,检查软件版本并运行一个简单的测试,确认配置能够正常使用。
下一篇将介绍Home Manager的配置组织方式,以及管理已有dotfiles时的注意事项。
