news 2026/9/21 16:25:06

Nix SSH Substituter 指南:通过 SSH 远程 Nix Store 自动拉取二进制包

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Nix SSH Substituter 指南:通过 SSH 远程 Nix Store 自动拉取二进制包

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 会:

  1. 检查本地 store;
  2. 依次询问已配置的 substituter(如二进制缓存https://cache.nixos.org);
  3. 仍缺失的路径才会从源码构建。

SSH Substituter 把上述第 2 步替换为:通过 SSH 询问远程 Nix Store 中是否存在所需路径,若存在则直接拉取。其行为与 Nix 常用的二进制缓存 substituter 类似,只是传输层从 HTTP 换成了 SSH。若远程没有该路径,Nix 仍会回退到二进制缓存 substituter,最后才选择本地构建(此回退顺序在 ssh-substituter.md 中有明确说明)。

工作流程

  • 本地需要 store pathP时,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

这条命令会:

  1. 以用户alice的身份 SSH 连接到avalon
  2. 查询 Firefox 闭包中每个 store path 在远程是否可用;
  3. 拉取可用的路径并安装,缺失的路径回退到默认 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 = falsenix-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-programnix-store远程机器上nix-store可执行文件的路径,最终会追加--serve参数
max-connections1允许的最大并发 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 编码),用于主机密钥校验
compressfalse是否启用 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 noAllowTcpForwarding noPermitTunnel no:禁止 agent/端口/隧道转发;
  • X11Forwarding no:禁止 X11 转发;
  • Match All:结束匹配块,避免规则泄漏到其他用户。

nix-store --serve的源码视角

--servenix-store的一个操作(operation),注册于 nix-store.cc#L1198-L1201。服务端进程的工作方式(nix-store.cc#L881-L1091):

  1. 从标准输入/输出建立FdSource/FdSink,即把 SSH 通道当作双向字节流;
  2. 通过ServeProto(serve 协议)完成版本握手;
  3. 循环读取命令并分发处理,支持的命令包括:
    • QueryValidPaths:查询路径是否有效;仅当以--write启动时才允许加临时根(temp roots)或触发远程 substituter 补全(nix-store.cc#L954-L967);
    • QueryPathInfos:返回路径的元数据(NAR 大小、引用、签名等);
    • DumpStorePath:输出路径的 NAR 内容(拉取二进制数据的核心);
    • ImportPathsBuildPathsBuildDerivation:均要求--write权限,否则抛错拒绝(nix-store.cc#L988-L1040)。

默认情况下(不传--write),opServe只允许查询与导出路径,任何导入、构建操作都会被拒绝,这正是"只读访问、不多给权限"的实现基础(nix-store.cc#L883-L888)。

小提示:如果希望该受限用户也能向远程 store 写路径(例如做双向同步),需要额外加--writeForceCommand 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_configMatch 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.confsubstituters全局配置;
  • 服务端推荐用 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/21 16:13:57

1M token超长上下文不是噱头:Spark-X2.5-1.7B百万字文档理解实战指南

1M token超长上下文不是噱头&#xff1a;Spark-X2.5-1.7B百万字文档理解实战指南 【免费下载链接】Spark-X2.5-1.7B Spark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色&#xff0c;涵盖对话、写作、翻译、推理、编程、工具调用和…

作者头像 李华
网站建设 2026/9/21 16:12:49

Task 环境变量完全指南:使用 TASK_ 前缀配置 Taskfile 构建工具

Task 环境变量完全指南&#xff1a;使用 TASK_ 前缀配置 Taskfile 构建工具 【免费下载链接】task A fast, cross-platform build tool inspired by Make, designed for modern workflows. 项目地址: https://gitcode.com/gh_mirrors/ta/task 导读 Task 是一个跨平台的…

作者头像 李华