谁应该为源代码的可用性买单?

Lobsters Hottest 新闻

摘要

一篇博客文章讨论了依赖 GitHub 等集中式托管平台的脆弱性,探讨了分叉(forking)和供应商化(vendoring)作为缓解策略,并质疑谁应该为可靠的源代码可用性买单。

<p><a href="https://lobste.rs/s/rsztog/who_should_pay_for_source_code">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/08/09 14:47

# 谁应为源代码的可用性买单? 来源:https://kristoff.it/blog/source-code-availability/ 这是我被 Radicle 化的故事。 我有一个项目([Zine](https://zine-ssg.io/),一个静态站点生成器),它有 11 个依赖项,托管在 GitHub、Codeberg 和自托管的 Forgejo 实例上。**每当 GitHub 或 Codeberg 宕机时,我项目的新构建就会失败。** 虽然自托管的 Forgejo 实例的正常运行时间明显优于 GitHub,但它们最终永久宕机并永久破坏我的项目的风险更大。 解决这个问题最直接、最实用的方法是全部 fork 或 vendor(随项目分发依赖代码)。 Fork 意味着将所有依赖项移动到 Zine 所在的同一主机上。如果你能克隆主项目,你也能克隆它的所有依赖项。 Vendoring 则更进一步:你*提交*所有依赖项的源代码到自己的仓库中,这样克隆项目这个动作就能让你拿到字面意义上的一切。 如果你非常看重实用性和最大韧性,那么你可能会更喜欢 vendoring 而不是 forking。 Forking 要求你 fork 整个依赖树(即也包括你的间接依赖),并且还要修改任何非叶子依赖,使它们指向你自己的 fork。而 vendoring 对于使用 [Zig](https://ziglang.org/) 工具链的用户来说则非常直接。 不了解的人可能不知道,Zig 0.17.0-dev 最近改变了依赖缓存的工作方式:全局缓存现在以压缩归档的形式存储包,每个项目都有一个本地`zig-pkg/`目录,其中包含该项目所使用的依赖的解压文件。如果你的目标就是 vendoring,那么这使得它变得轻而易举:只需将你的`zig-pkg/`目录提交到版本控制中。(否则,建议将其添加到`.gitignore`。) 如果你计划对依赖项进行修改,并且在意如何更轻松地把修改上游回馈,那么 forking 可能比 vendoring 更可取。即使你不打算发送 pull request,在 fork 中 cherry-pick 一个提交,也比在外部项目的 vendored 目录中做同样的事情容易得多。 Forking/vendoring 确实有效,这点毋庸置疑。**但这真的是我们能做的最好的吗?** 拥有集中式包索引的语言(如 [npmjs.com](https://npmjs.com/) 或 [crates.io](https://crates.io/))不必担心这些问题,因为它们基本上把 forking 策略作为内置功能(所有源代码都被“fork”到集中式包索引托管的副本中),但这种方法实在让我不满意,因为它不必要地把解决方案和包管理绑定在了一起。**我们的目标应该是让源代码始终可靠地可用,而不仅仅是从包管理器获取时才可用。** 由于最近 GitHub 的不稳定性,这些话题变得更加相关,但实际上问题不是“我们如何让源代码托管变得可靠?”。如果我们真的想要,我们确实知道如何让代码托管可靠。问题更像是:**它应该付出多大代价,又该由谁来支付?** 正如我们行业经常发生的情况一样,我们从来不想面对这个问题,并且一直乐于依附大科技公司,但一如既往,答案最终还是必须给出,而当那一刻到来时,我们又迅速指责公司,义愤填膺地高喊“平台腐化!”。 是的,GitHub 毫无疑问正在腐化,和 Discord 以及它们之前的许多平台一样;但我们还指望免费消耗资源多久?GitHub 的情况只是向我们展示了这个从一开始就一直是破碎的体系中的裂缝。 我不认为公司没有责任,我也确信,把成本方程弄得模糊不清,在许多未写成的剧本中都有专门一章来讲述;但真正应该更明白的不是公司,而是我们。**正是我们的故意无知制造了一种扭曲,公司们要么拥抱它,要么利用它来保持竞争力。** 那么,有没有一种方式可以让源代码可靠地可用,同时平衡成本方程呢? 我认为,自托管一个 forge(代码托管平台)*并不足以*平衡这个方程,因为它把让源代码可用的全部成本都压在了软件创建者身上;但我们已经确认,这类代码的消费者也对其可靠可用性有兴趣,这正是导致 forking 和 vendoring 的原因。 Forking 和 vendoring 在某种意义上是一种分担保持源代码可用成本的方式,但它们都效率低下,因为**fork/vendored 的代码不容易被发现,也不能自动作为镜像使用**。 如果有人在你项目之外的语境下对你的某个依赖感兴趣,他们的包管理器无法自动从你的 fork/vendored 副本中获取它。当然,你可能并不想承担这笔成本,但按照目前的情况,其实你也没有太多选择。 如果我们能做得更好,那就太酷了,因为这可能有助于我们降低为所有人保持源代码可靠可用的总体成本。 ## Radicle 网络 [Radicle](https://radicle.network/) 是一个点对点(p2p)网络,网络中的节点会 seed(播种/提供)它们感兴趣的仓库。 有些 seed 节点可能对增强网络韧性感兴趣,因此会想要重新 seed 所有(或接近所有)内容;而另一些节点可能只对确保特定项目集合保证可用感兴趣,从而将它们的 seeding 策略限制为只允许相关仓库。 **Radicle 支持 Issues 和 Patches**(也就是 GitHub 语境中的 Pull Requests),其方式非常有趣:没有集中式(或联邦式)的 web 服务器来托管它们,因为**它们就存储在 Git 仓库本身**中,作为([CRDT](https://crdt.tech/) 启用的)Git 对象,当你向网络发布你的更改时(具体地说,运行`git push`),它们可以被重新同步。 为了帮助你更好地理解这个功能,让我们用一个具体例子来聚焦 Issues。 假设我想把 Radicle 用于 Zine。一旦我把我的仓库发布到 Radicle 网络,我就会想设置自己的常驻节点,以保证 Zine 始终可用,并促进开发。其他一些节点也可能决定 seed Zine,但由于 Zine 仍然是个小项目,我暂时不指望这一点。 当 Alice 想要开一个 issue 时,她会在她自己 seed 的 Zine 副本中打开(简单起见,我们再次借用 GitHub 语境,称之为她的“fork”)。这时,她会把她的更改发布到 Radicle 网络,任何对这些更改感兴趣的节点都会捡到它们。 我的常驻节点配置为 seed 所有 Zine 的副本,所以它保证能捡到 Alice 的更改,即使 Alice 的个人节点离线了,这些更改也仍然可用。 这时,当我从网络拉取更改时,我会看到 Alice 的新 Issue,并且我可以用 Radicle 的 CLI 工具或[桌面应用](https://radicle.network/desktop)进行评论、打标签、指派或关闭它;同理也适用于 Bob 所创建的假想 Patch。 想跟进进展的旁观者可以通过 web 界面看到所有源代码、Patches 和 Issues,例如这个在`heartwood`仓库(https://radicle.network/nodes/seed.radicle.dev/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/issues/a6c8699effadc3c70f1d2dbfd84a7a5daacee5a2)上的例子。不过 web UI 是只读的,因为所有更改都必须通过 Radicle 网络提交,在 AI bot 和爬虫日益崛起的背景下,这是一个有趣的、方便的权衡。 要理解 Radicle 的实际运作方式,还有一些东西需要学习,所以我邀请你去查阅[官方文档](https://radicle.network/)。 ## 用 Radicle 实现可靠的源代码可用性 在 Radicle 上发布 Zine 时,我也会有意从我常驻节点 seed 我的依赖项,以保证 Zine 的用户也能可靠地获取构建 Zine 所需的依赖。 我们几乎又回到了 forking/vendoring 的境况,但有一个关键区别:**seed 我的依赖项能让它们在网络中保持可被发现!** 角色的对调也是如此:当有人依赖 Zine 时,他们会有兴趣 seed 它,从而让 Zine 的源代码更可靠地可用,这既有利于我也对他们有利。此外,根据他们如何微调自己的 seeding 策略,Zine 的用户可能选择不仅 seed 我的权威副本,还 seed(全部或部分)Zine 的“fork”。通过 seed Zine 的“fork”,他们也会 seed Issues 和 Patches,从而帮助 Zine 上的协作更具韧性。 我发现,这种模式有助于让成本方程保持平衡,并与每个人的利益紧密对齐,同时相比“自托管 forge + forking/vendoring”的设置(后者本质上浪费了大量冗余),这种方式也要经济高效得多。 随着我对 Radicle 了解得更多,就在这个节点上,我觉得如果 Zig 包管理器能直接支持 Radicle 就好了,但事实证明,这并不像我最初想象的那么直接……只是其中涉及的摩擦也惊人地与相关各方的利益一致! ## 依赖一个 Radicle 仓库 在 Radicle 中,每个节点都有一个加密身份([DID](https://en.wikipedia.org/wiki/Decentralized_identifier)),它既用于在网络中标识节点,也用于声明对仓库的所有权。 当你向 Radicle 发布一个仓库时,`rad`CLI 工具会向该仓库添加一个身份文档,将仓库与所有者的 DID 绑定在一起;这个文档的哈希成为仓库 ID(RID)。 网络还会用这些信息来区分仓库的权威版本和其他人的“fork”,这也是 Radicle 工具如何保证,当你从网络克隆一个仓库时,你得到的是一个[你可以信任](https://radicle.network/faq#what-does-radicle-solve-that-isnt-already-solved-by-git-commit-signatures)的副本。 当你想要从 Radicle 网络克隆一个仓库时,你会运行类似这样的命令: ``` $ rad clone rad:z3WukSjzicL8WaZHFALbBwb2r8W52 ``` 上面例子中传给`clone`的参数是一个`rad`URI,其中包含一个仓库 ID;它明确地不是一个 URL,也就是说它并不表达应该从哪里获取仓库——而这正是关键所在。Radicle CLI 工具会与 Radicle 网络交互,从任何恰好拥有数据的已连接对等节点那里获取这个仓库。 如果我们考虑一个`build.zig.zon`文件的两种假想变体: ``` // 真实语法 .dependencies = .{ .zine = .{ .url = "git+https://github.com/kristoff-it/zine", .hash = "...", }, }, ``` ``` // 对 Radicle 的假想支持 .dependencies = .{ .zine = .{ .rad = "z3WukSjzicL8WaZHFALbBwb2r8W52", .hash = "...", }, }, ``` 你可以看到,关键区别在于第一个指定了一个位置,而第二个没有。 在第一种情况下,依赖是脆弱的:每当 GitHub 宕机时,你就无法获取它;而在第二种情况下,只要至少有一个活跃的 Radicle 节点 seed 了 Zine 仓库,你就能获取它。 这会是一个巨大的升级,但有一个障碍:要与 Radicle 网络通信,你必须成为网络中的对等节点,这意味着如果你不是 Radicle 用户,你将无法成功运行`zig build`(具体来说,你需要有一个本地节点运行,Zig 才能与之通信)。 这真是个大问题! **Zig 应该是“零依赖”的软件**,我们希望尽可能多的 Zig 项目只需安装 Zig 并运行`zig build`就能构建。这并不总是成立的,因为有些项目会有系统依赖,但在其他情况下,它应该可以工作,而不会硬依赖任何非必要的东西。 这时我开始思考,有没有办法不必成为对等节点就能查询网络,直到我意识到,发现不是问题所在,而且我没有在思考成本方程! Radicle 网络中的一个节点会有兴趣承担作为网络“长期记忆”一部分的成本,这确实意味着与其他 Radicle 节点之间传输数据,**但那并不意味着该节点愿意承担未认证客户端以无限制的速率请求获取该节点 seed 的一个或多个仓库副本的成本**。 如上所述,Radicle 节点有身份,网络中的其他节点可以决定封禁行为不当的节点,并且通常也会持续关注它们的行为。另一方面,`zig build`客户端本质上来说是未认证的(`git`客户端其实也是)。在日常使用中,两者可能没有太大区别,但想想某个人配置不当的 CI,毫无理由地反复打击你的节点。 如果考虑我自己的用例,我会有意承担这些外部流量,以便让非 Radicle 用户也能访问 Zine;但我能理解另一个节点(可能也 seed 了 Zine)不愿意做同样的事。 幸运的是,这个概念在 Radicle 中也得到了很好的建模。 我之前在谈到 Issues 和 Patches 时说过,旁观者可以用 web UI 跟进开发,但那其实是个稍显简化的说法。Radicle 节点默认并不运行任何 HTTP 服务。这是一个完全可选的功能,你可以根据自己的兴趣决定是否启用。 如果你启用了 HTTP 服务,那么 HTTP 客户端就能访问你的节点,并使用 web UI 或通过 Git over HTTP 来克隆该节点所 seed 的仓库,无需 Radicle 身份。 一张 Radicle web UI 的截图,展示了你如何像在 GitHub 等平台上正常操作那样通过 HTTP 克隆。 就我而言,我想在我的常驻节点上启用 HTTP 访问,让未认证的 HTTP 客户端按需下载 Zine(及其依赖)。任何其他决定 seed Zine(同时启用 HTTP 接口)的节点,实际上也等同于提供相同的服务。 这意味着,虽然指向启用 HTTP 的 Radicle 节点的 URL 不像 RID 那样具有韧性,但它们仍然等同于甚至优于指向自托管 forge 的链接,尤其是因为这样的节点有很多! 例如,`heartwood`可以通过以下任何一个主机通过 HTTP 获取(在撰写本文时它有超过 250 个 seed 节点),而且可能还有更多: - https://radicle.network/nodes/seed.radicle.dev/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 - https://radicle.network/nodes/index.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 - https://app.radicle.at/nodes/seed.radicle.at/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 - https://radicle.defelo.de/nodes/radicle.defelo.de/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 - https://radicle.network/nodes/rad.hardenedbsd.org/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 - https://radicle.jarg.io/nodes/radicle.jarg.io/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 *请注意,web 界面有时是一样的,但每个链接引用的是不同的主机,该主机会被用在“从 git 克隆”按钮中。* 要让 Zig 包管理器能够利用 Radicle,我们实际上需要的只是实现 [#14291](https://github.com/ziglang/zig/issues/14291)(对镜像的支持)。 正如我已经暗示过的,我仍在学习 Radicle 如何运作的过程中,所以我不确定这是否已经在进行中、已计划,还是不在范围内;但根据我目前的理解,我认为如果`rad`

相似文章

GitHub之前

Armin Ronacher

一篇关于GitHub之前开源开发历史的反思文章,讨论了作者在自托管基础设施、SourceForge以及GitHub带来的文化转变方面的个人经历。

GitHub 正在沉沦

Hacker News Top

文章认为,自被微软收购以来,GitHub 的可靠性与文化已大幅衰退,以正常运行时间问题和内容泛滥("slop")为由,指出开发者正转向其他替代方案。

我们应得的代码锻造平台

Hacker News Top

本文讨论了对GitHub可靠性日益增长的不满,并提出基于AT协议的去中心化Git锻造平台Tangled,作为一个结合了中心化便利性与用户数据所有权的有前途的替代方案。