Nix SSH Substituter 指南:通过 SSH 远程 Nix Store 自动拉取二进制包
【免费下载链接】nixNix, the purely functional package manager项目地址: https://gitcode.com/gh_mirrors/ni/nix
本指南讲解 Nix 包管理器的 SSH Substituter 机制:如何把一台远程主机的 Nix Store 当作"替身源"(substituter),在本地安装软件时通过 SSH 自动获取闭包(closure)中已经存在的 store path,从而避免重复下载或本地构建。读完本文,你将掌握ssh://替代源的使用方法、它与二进制缓存(binary cache)的优先级关系、nix-store --serve只读服务端的搭建,以及在 NixOS 上通过nix.sshServe快速开放的完整流程。
一、什么是 SSH Substituter
在 Nix 中,安装一个包(例如 Firefox)时,Nix 需要其整个闭包(该 store path 引用的全部依赖路径)都可用。通常 Nix 会:
- 检查本地 store;
- 依次询问已配置的 substituter(如二进制缓存
https://cache.nixos.org); - 仍缺失的路径才会从源码构建。
SSH Substituter 把上述第 2 步替换为:通过 SSH 询问远程 Nix Store 中是否存在所需路径,若存在则直接拉取。其行为与 Nix 常用的二进制缓存 substituter 类似,只是传输层从 HTTP 换成了 SSH。若远程没有该路径,Nix 仍会回退到二进制缓存 substituter,最后才选择本地构建(此回退顺序在 ssh-substituter.md 中有明确说明)。
工作流程
- 本地需要 store path
P时,Nix 通过 SSH 在远程主机avalon的 store 中查询P是否有效(valid); - 有效则直接把
P的 NAR 内容流式拉取到本地 store; - 无效则回退到二进制缓存,再不行就本地构建。
二、快速上手:用--substituters临时指定 SSH 替代源
安装时通过--substituters指定即可,无需修改任何全局配置。例如从主机avalon上的 store 自动获取 Firefox 闭包中已有的路径:
$ nix-env --install --attr nixpkgs.firefox --substituters ssh://alice@avalon这条命令会:
- 以用户
alice的身份 SSH 连接到avalon; - 查询 Firefox 闭包中每个 store path 在远程是否可用;
- 拉取可用的路径并安装,缺失的路径回退到默认 substituter 或本地构建。
如果你不想把包装进 profile,只想"实现"(realise)某个已存在的 store path(即把它的闭包补全到本地 store),可以这样:
$ nix-store --realise /nix/store/m85bxg…-firefox-34.0.5 --substituters ssh://alice@avalon这与下面这条命令在效果上基本等价(--substituters方式会自动使用 SSH substituter 协议,而nix-copy-closure是专用的闭包复制工具):
$ nix-copy-closure --from alice@avalon /nix/store/m85bxg…-firefox-34.0.5注意:SSH 口令限制
SSH substituter 当前不支持交互式输入 SSH passphrase。因此请先使用
ssh-add把解密后的私钥加载进ssh-agent,避免连接时被要求输入口令。
关于nix-copy-closure的补充参数
从 nix-copy-closure.cc 的源码可以看出,nix-copy-closure支持的方向与增强选项包括:
| 参数 | 含义 | 源码位置 |
|---|---|---|
--from <host> | 从远程主机复制闭包到本地(toMode = false) | nix-copy-closure.cc#L30-L31 |
--to <host> | 把本地闭包推送到远程主机(默认模式) | nix-copy-closure.cc#L32-L33 |
--gzip | 传输时启用 gzip 压缩(--bzip2、--xz目前会回退到 gzip 并给出警告) | nix-copy-closure.cc#L26-L29 |
--include-outputs | 同时复制 derivation 的输出 | nix-copy-closure.cc#L34-L35 |
--dry-run | 只做预演,不实际复制 | nix-copy-closure.cc#L38-L39 |
--use-substitutes/-s | 远程缺失时允许使用 substituter 补全 | nix-copy-closure.cc#L40-L41 |
nix-copy-closure内部会把目标主机解析为LegacySSHStoreConfig(即 SSH store),并调用copyClosure完成复制,见 nix-copy-closure.cc#L52-L65。
三、SSH Store 的底层实现原理
3.1 两种 SSH Store
从源码结构看,本仓库实现了两种基于 SSH 的 store:
ssh://(LegacySSHStore,即文档所述的 SSH substituter):URI scheme 为ssh,远程执行nix-store --serve作为服务端进程。其配置类定义在 legacy-ssh-store.hh,默认远程程序为nix-store(legacy-ssh-store.hh#L35-L36),实际启动命令由 legacy-ssh-store.cc 拼装为nix-store --serve(legacy-ssh-store.cc#L111-L112)。ssh-ng://(SSHStore,实验性):URI scheme 为ssh-ng,远程执行nix-daemon,协议更接近本地守护进程,但需要启用实验特性;其配置见 ssh-store.hh。
本文聚焦于文档所述的ssh://(Legacy SSH Store)。
3.2 连接管理与会话复用
LegacySSHStoreConfig提供了以下与连接相关的设置(legacy-ssh-store.hh#L35-L48):
| 设置项 | 默认值 | 说明 |
|---|---|---|
remote-program | nix-store | 远程机器上nix-store可执行文件的路径,最终会追加--serve参数 |
max-connections | 1 | 允许的最大并发 SSH 连接数 |
log-fd | 无效描述符 | 用于把 SSH 的 stderr 接到远程构建日志(仅非 Windows 平台) |
LegacySSHStore内部维护了一个连接池Pool<Connection>与SSHMaster(复用单个 SSH master 连接),见 legacy-ssh-store.hh#L77-L81。queryValidPaths还支持在远程端原子地创建临时锁(temp roots),避免远程 GC 在拉取过程中回收已有路径,见 legacy-ssh-store.hh#L163-L171。
3.3 公共 SSH 参数
ssh://与ssh-ng://共用的配置项定义在 common-ssh-store-config.hh:
| 配置项 | 默认值 | 说明 |
|---|---|---|
ssh-key | 空 | 连接远程主机所用的 SSH 私钥路径 |
base64-ssh-public-host-key | 空 | 远程主机公钥(base64 编码),用于主机密钥校验 |
compress | false | 是否启用 SSH 压缩 |
remote-store | 空(即auto) | 远程机器上使用的 store URL,默认自动(走 Nix daemon 或直接/nix/store) |
这些配置可通过nix.conf中形如ssh-substituter.ssh-key = ...的键或 store URI 参数形式指定。
四、搭建受限的 SSH 只读服务端(nix-store --serve)
要让远程主机安全地提供 SSH substituter 服务,最稳妥的方式是借助 OpenSSH 的forced command(强制命令)特性:为特定用户创建一个受限账户,只允许通过nix-store --serve对本地 store 做只读访问,其余一切(TTY、端口转发等)全部禁止。
在服务端sshd_config中添加如下配置,以限制用户nix-ssh:
Match User nix-ssh AllowAgentForwarding no AllowTcpForwarding no PermitTTY no PermitTunnel no X11Forwarding no ForceCommand nix-store --serve Match All各指令的作用:
ForceCommand nix-store --serve:无论客户端提交什么命令,SSH 一律执行nix-store --serve,客户端无法执行任意 shell 命令;PermitTTY no:禁止分配 TTY,避免交互式 shell;AllowAgentForwarding no、AllowTcpForwarding no、PermitTunnel no:禁止 agent/端口/隧道转发;X11Forwarding no:禁止 X11 转发;Match All:结束匹配块,避免规则泄漏到其他用户。
nix-store --serve的源码视角
--serve是nix-store的一个操作(operation),注册于 nix-store.cc#L1198-L1201。服务端进程的工作方式(nix-store.cc#L881-L1091):
- 从标准输入/输出建立
FdSource/FdSink,即把 SSH 通道当作双向字节流; - 通过
ServeProto(serve 协议)完成版本握手; - 循环读取命令并分发处理,支持的命令包括:
QueryValidPaths:查询路径是否有效;仅当以--write启动时才允许加临时根(temp roots)或触发远程 substituter 补全(nix-store.cc#L954-L967);QueryPathInfos:返回路径的元数据(NAR 大小、引用、签名等);DumpStorePath:输出路径的 NAR 内容(拉取二进制数据的核心);ImportPaths、BuildPaths、BuildDerivation:均要求--write权限,否则抛错拒绝(nix-store.cc#L988-L1040)。
默认情况下(不传--write),opServe只允许查询与导出路径,任何导入、构建操作都会被拒绝,这正是"只读访问、不多给权限"的实现基础(nix-store.cc#L883-L888)。
小提示:如果希望该受限用户也能向远程 store 写路径(例如做双向同步),需要额外加
--write:ForceCommand nix-store --serve --write。出于安全考虑,面向 substituter 的场景通常不建议开启。
与其他 store 类型的对比
nix-store --serve走的是"legacy" serve 协议,代码位于 serve-protocol-connection.cc 与 serve-protocol.cc。相比ssh-ng://所采用的 nix-daemon 协议,legacy serve 协议不支持"可信用户"(trusted-user)判定——LegacySSHStore::isTrustedClient()明确返回未知,并注释建议需要该能力时改用ssh-ng://(legacy-ssh-store.hh#L186-L190)。
五、NixOS 上一键启用:nix.sshServe
如果你运行的是 NixOS,无需手写sshd_config,直接在configuration.nix中启用内置模块即可:
{ nix.sshServe.enable = true; nix.sshServe.keys = [ "ssh-dss AAAAB3NzaC1k... bob@example.org" ]; }nix.sshServe.enable = true:打开 SSH substituter 服务,NixOS 会自动创建受限用户、生成对应的ForceCommand nix-store --serve的 SSH 配置;nix.sshServe.keys:列出允许连接的公钥列表(上述示例中的ssh-dss AAAAB3NzaC1k... bob@example.org仅为示意),只有公钥匹配的用户才能通过 SSH 访问 store。
六、完整实战示例
假设:
- 服务端:NixOS 主机
avalon,通过nix.sshServe开放只读 store 访问; - 客户端:任意装有 Nix 的机器,使用
alice@avalon的密钥访问。
客户端一次性配置(写入~/.config/nix/nix.conf),让后续所有操作默认使用该替代源:
substituters = ssh://alice@avalon https://cache.nixos.org之后直接安装即可自动优先从avalon拉取:
$ nix-env --install --attr nixpkgs.firefox若只是临时使用,保持全局配置不动,仅在单条命令上追加--substituters即可(即本文第二节的用法)。
排查要点:
- 若遇到"请输入 passphrase"提示,说明私钥未加载,执行
ssh-add ~/.ssh/id_ed25519后重试(SSH substituter 不支持交互式口令); - 若连接被拒绝,检查服务端
sshd_config的Match User块与ForceCommand nix-store --serve是否正确、用户公钥是否已加入nix.sshServe.keys; - 若拉取慢,可在 store URI 或
nix.conf中开启compress(即 SSH 压缩)减少带宽消耗。
七、小结
ssh://substituter 让 Nix 通过 SSH 复用远程 store 中已有的 store path,工作方式与二进制缓存类似,优先级低于本地 store、高于本地构建;- 客户端可用
--substituters ssh://user@host临时指定,或用nix.conf的substituters全局配置; - 服务端推荐用 OpenSSH forced command 配合
nix-store --serve(只读)搭建,NixOS 用户直接用nix.sshServe模块; - 实现层面,legacy-ssh-store.cc + serve-protocol.cc 构成了这套机制的核心:前者负责 SSH 连接与协议分发,后者定义服务端命令语义;安全上默认禁止写操作,需要写权限必须显式
--write。
【免费下载链接】nixNix, the purely functional package manager项目地址: https://gitcode.com/gh_mirrors/ni/nix
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考