ByteNoteByteNote
landlock-run 打包设计:npm 多平台包分发
字

字节笔记本

2026年10月6日 · 约 5 分钟读完

landlock-run 打包设计:npm 多平台包分发

API中转
¥120

在 Linux 上限制不可信子进程,Landlock 是目前最顺手的内核机制之一,但要让普通用户通过 npm 用上它,分发环节的工程量并不小。DeepSeek 开源的 landlock-run 给出了一个值得细看的样本:它把一个静态链接的 C 二进制,装进了与 esbuild 同款的包布局里,一个 JS 入口包加若干平台可选包。与常见 Node 原生插件不同,这里没有 ABI 或后端维度之分,每个平台包只携带自己的 prebuilds.json 所声明的那几个静态可执行文件。

landlock-run 运行时选择三级收敛

三个包组成的矩阵

正式发布的包一共三个:入口包 @deepseek-ai/node-addon-landlock-run,以及 linux-x64、linux-arm64 两个平台包。不支持的平台在 optionalDependencies 里干脆缺席,官方支持矩阵文档解释了这一取舍:没有包,本身就是一条明确的支持声明。

更值得注意的是,这套矩阵不藏在 CI 配置里,而是落在入库的元数据上:

  • 入口包的 package.json 把平台包列为 optionalDependencies;
  • 每个平台包用 os 与 cpu 字段声明适用平台,prebuilds.json 声明包内可能存在的二进制,包括 tool、kind、path 三项信息;
  • 平台包刻意不写 libc 字段,因为二进制静态链接 musl,glibc 与 musl 发行版都能直接运行,声明反而会把能跑的用户误挡在门外。

CI 与发布的矩阵由专门脚本从这些元数据派生,构建脚本只负责当前主机的目标,并不是矩阵生成器。要调整矩阵,需要在同一次变更里同步更新包元数据、prebuilds.json、锁文件以及支持与发布文档,四处缺一不可。

运行时选择:三级收敛

安装完成之后,定位二进制这件事被拆成三级:

  1. npm 的 os/cpu 字段让安装器只拉取匹配的平台包,不匹配的包根本不会落盘;
  2. 入口包的 launcherPath() 把包名解析成包内 bin/landlock-run 的绝对路径,解析不了的包会得到一条确定性的、永不存在的回退路径;
  3. probe() 是唯一的可用性信号,返回 full、partial 或 unusable,二进制缺失与内核不支持被刻意压成同一个 unusable,消费方因此只有一条失败闭合路径,不必自行分辨是包没装上,还是内核太老。

为什么砍掉安装期编译回退

入口包没有 install 脚本,绝不在消费端现场编译。理由很直接:编译回退要求每台消费机都备有 musl 工具链,而编译成败依赖环境,会把原本干净的失败闭合退化,变成环境相关的随机行为。这条禁令由 verify-packed-install.mjs 的打包检查强制执行,入口包只要带上任何 install 生命周期脚本就会直接失败,靠机制而非约定维持。

landlock-run 打包闸门流程

npm pack 与 pnpm pack 各管一半

平台 tarball 由 npm pack 产出,入口 tarball 由 pnpm pack 产出,两个包管理器各司其职。这个拆分来自实测:pnpm 11.7.0 的 pack 命令会规范化文件权限并剥掉可执行位,平台包若经它打包,发出去的启动器没有任何消费者能执行;而平台包没有依赖,用不上 pnpm 的 workspace 协议转换。入口包正好相反,需要 workspace 协议转换,内部又不携带可执行文件。发布脚本把这套拆分写死在代码里,杜绝手工用 pnpm 打平台包。

两条打包路径都要通过 prepack 闸门,保证打出来的字节就是发布的字节:

  • 平台包执行 verify-launcher-binary.mjs:每个声明的二进制都存在且可执行,ELF 头的 e_machine 与声明的 cpu 一致,bin/ 目录里没有未声明文件;
  • 入口包执行 verify-entry-lib.mjs:确认构建产物 lib/ 存在。

最后一道闸同样是 verify-packed-install.mjs,它从 tarball 出发,把消费端路径完整排练一遍:载荷检查、一次即弃安装、把装出来的二进制与工作区构建逐字节比对、验证已安装副本可以执行,最后通过已安装的启动器完成一次真实的隔离验证。二进制缺失或不可执行会在这里大声报错,而不是被上游误读成内核不支持隔离。

可以抄走的三件事

跳出这个项目,这套打包方案有三点普适做法。其一,把支持矩阵写进入库元数据,让包管理与 CI 从同一份事实派生,避免两边漂移。其二,可用性收敛到单一探测信号,宁可把两种失败压成一种,也不让消费方去猜。其三,发布闸门盯住最终字节,可执行位、ELF 架构、安装后的完整演练,全部在出包那一刻验证完毕。对任何需要分发原生二进制的 npm 包来说,这都是一份可以直接对照的工程样本。

相关文章

分享: