诸位老友,晚上好。这里是在吹干头发的 Chlorine。
这件事发生在小氯休假期间,也是它让小氯的休假净减少了一天上下。于是某元素娘决定把它写出来,大家可以就着茶点看一看,治疗一下自己的低血压。
密码学的壁障
首先,一个可能令人惊讶的事实:虽然小氯用了许久的服务器,但对于它们,小氯一直是毫无备份的习惯的。
这并不是小氯认定备份对自己无用,某元素娘只是不正常,不是否定一切常识,甚至我不懂计算机的娘亲也会叮嘱我「在做什么大事之前,记得备份」。至于原因,单纯是……小氯太懒了,一直把这件事当成 DDL 在宇宙热寂的任务去思索。另外,我们后面大概会说到,小氯不知道把它备份到哪里。
另外,小氯给服务器配置了 LUKS 加密。这自然是安全了一些,但这就造成服务器重启时,由于硬盘处于加密状态,SSH 也无法正常启用。于是小氯学着网上的方法,用了一个 initrd,它会在启动时拉起一个临时 SSH,等着我登录上去输入密码。当时也是遇到了不少问题,大概可以看这里。
于是直到现在。虽然说每一次小氯都会忘记如何解密而不得不求助语言模型,但每次都正常启动了。加之服务器重启的次数本来就不多,小氯也就没多在上面花心思。
事情发生在前几天,小氯刚刚开始休假时。当时是和一位熟识许久的老友一起出门看看,刚刚走下地铁、在下榻处放下箱包时。Linux 内核这几天更新了 7.0 版本,作为喜欢新东西的元素娘,小氯自然是要迫不及待地给自己的新服务器装上了。步骤和以往一样,nix flake update,然后 deploy 就可以了。
不过这次事情没那么顺利,NixOS 并没有让我成功更新,而是要求我先构建完成再手动重启以应用。这倒也合理,毕竟 dbus 相关单元都变了,在飞驰的汽车上换底盘,总归不是什么明智的举动。
简单查阅 deploy-rs 相关文档,只需要将 deploy-rs.lib.activate.nixos 改成 custom,然后后面的命令改成相应的 boot 就行。NixOS 也并未有何虚与委蛇,很快完成了重启工作,并拉起了我们的 initrd SSH。
登录上去,试图解锁硬盘……找不到此命令?
奇怪哦,之前都是有的。小氯也没多想,求助 Claude。后者建议我手动挂载硬盘并解锁。小氯之前输错了挂载的名字,导致失败了好几次。好在最后是解锁成功了。
事情解决,SSH 上去看看成果……指纹不匹配?
当时小氯就感觉不对了。按理来说,initrd SSH 启动完毕后,系统切换到正常的 SSH 服务,而 SSH host key 是固化的。现在出现 mismatch,要么是有 AitM,要么就是……密钥被覆盖了。
不管怎么样,先登录上去看看再说。用小氯的跳板机强行登录到服务器,看一眼密钥,果然被覆盖了。
这不是什么对劲的事情,不过小氯也没多想,先用正确的密钥覆盖上去好了,不然 sops-nix 都没办法解密了。然后继续回去刷 Fediverse……等等。什么叫「user not found」?
回到终端前,切到 postgres 用户看看 mitra 表……为什么是空的?
当时小氯整个元素就已经开始不好了。赶紧把服务停掉,再去看看 Forgejo,也显示用户未找到,连小氯自定义的图标都没了。停掉几个服务,直接 dust 整个 /var/lib……
哦,我的上帝啊。这空间大小所昭示的目录的情景,干净得如同新的别针一般。
这个时候小氯才意识到一个可怕的可能:在这次更新的过程中——极大可能就是在我操作加密硬盘的过程中,小氯的数据丢失了——从大小看,可能是全部丢失了。
- 朋友:氯酱你还好吧?
- 小氯:我遇到了一些……需要严肃处理的事情。
- 朋友:那很严重了。
洪水过后
很遗憾,小氯的一番垂死挣扎没有奇迹。由于小氯没有备份的糟糕习惯,这些数据已经无处可寻。唯一能让小氯有一点安慰的是,Vaultwarden 的数据没有丢——准确来说,是我在意识到不对时第一时间从我的浏览器扩展中导出了一份数据。不然的话,这次事件可能就要从灾难性事件变成不会被发布的事件了。
当时小氯刚刚吃过一点朋友带回来的东西,之所以只是一点,是因为实在吃不下。很难形容我那时的心情,我想当时我不慎把小钠的头发染成了蓝色时的心情大致也不过如此吧。
明天还要出门,因此小氯并不想通宵修这些恼人的问题——咱也没有通宵的习惯。所以前者决定先关机一会儿。悟已往之不谏,来者咱也不追。数据丢了就丢了,就当是祭祀给欧姆弥赛亚的牺牲了。
但没办法,小氯失眠了。你看那洁白的天花板像不像我什么都没剩下的服务器?
于是小氯倒了杯热水,开了一点点的窗户,裹着衣服坐在桌子前开始尝试着做一些事情。
风与牧歌之城
首先,既然服务器已经烂成这个样子,小氯也不想在这个伤心地多留。用 nixos-anywhere 给它个体面的结局好了。不过在安装时,似乎出了些神秘的问题——没办法,这里的网络就是这样——这次可不是像最初安装 NixOS 那样,关掉代理就能解决的了。既然如此,小氯也没必要和它多做纠缠。直接在 Hetzner 的后台把它删除掉。在同一个 IP 下创建一个新的实例就好了。
这大概就是「云服务器」相比普通 VPS 的好处之一吧。
然后,虽然不知道这次悲剧究竟是否和 LUKS 有关,但小氯可是再也不敢开它了。修改也不难,直接在 disko.nix 把 crypted 的外层去掉,直接暴露内部的 Btrfs 即可。而且现在想想,小氯也有些怀疑在虚拟服务器场景下开启 LUKS 的必要性。
LUKS 是一种「静态加密」:如果你的服务器处于关机状态,别人窃走你的硬盘,他们只能得到无法解密的块,但在运行时并非如此——这也好理解,不然服务器如何正常运行呢,又不是同态加密。我们设想某神秘情报机构想对你做一些不那么有趣的事情,他们并不需要下令服务器自刎归天然后把硬盘拿走,只需要对宿主服务器的内存做一次 dump,就可以得到它一切想要的东西。LUKS 在这种情况下,作用大概是让墓碑规格高一些。
如果没有这样的 large evil agency 呢?那小氯想不到有什么情况需要担心的。快照的话,小氯不会用服务商自带的快照;至于云服务商的硬盘销毁过程出现 bug,在 APT(Advanced Persistent Threat)以下的威胁模型担心这个问题,还不如担心小氯做的点心不好吃来得合理些。
所以 LUKS 先辈,就请您先休息一下。小氯在配置自己的便携式计算机时,大概还会仰仗您的威仪,但虚拟服务器还是竹杖芒鞋轻胜马为好啦。
说起来,在 MacBook 上安装 Asahi NixOS 的时候,如果要启用 LUKS,还需要在第一步手动划分硬盘。真是繁琐又危险的事情,明年做的时候大概需要先焚香沐浴,诚心祭祀一下三位老祖吧。
皎月云间之梦
成功启动后的第一样东西应该是什么呢?
小氯觉得是反向代理和证书服务,就像一个新的政府成立后会亟亟地派出外交使节寻求他国的承认一样。得益于 Nix 的声明式配置,具体的步骤大体也没什么好说的,直接启用这两个 service 就可以了。
后面的第一个服务小氯选中了 Vaultwarden,毕竟 password manager 里面装着小氯大半的家当。然后将自己导出的 JSON 导入新的密码库就好了。看到归位的众多密码项,小氯心里还是有点奇怪的宽慰的。
说起来,小氯也在考虑换成 KeePassXC。同为各自方向的 SOTA,相比 Vaultwarden 的服务器—客户端架构,KeePassXC 的离线优先、单文件自包含似乎更合小氯的胃口。可惜,后者的同步体验似乎不如前者,大概是 P2P 的痼疾了。
寂远无妄之国
在丢失的数据中,自然也有山羊会计的账本。
自从小氯差不多一年前将它部署到园子,GoatCounter 就一直漫不经心地记录着园子的风声和脚步,将其化作安静而冷淡的数字。它的工作无可指摘。而在发布 Gradient 04 前后,小氯也终于是用一种相当……拧巴的方式公开了它的一部分结果,例如阅读量最多的文章之类。
但近几个月,小氯发现,它似乎不再有作用了。
并不是说它坏掉了,而是小氯几乎不会打开它的面板了,因为我想不到什么理由去打开。从 GoatCounter fetch 数据的过程大概只会拖慢构建,至于阅读量最高的几篇文章,大抵就是那几篇,几乎不会有什么变化。
而且你们这群讨厌鬼为什么一直在看小氯是怎么把 Vaultwarden 主密码搞丢的啊!(气哭)
小氯当时部署 GoatCounter 的时候,似乎是在不停地乱改园子的基础设施的阶段。当时想的除了看一看来自哪个国家(的 IP)、哪个浏览器的老友最多之类的数据感觉有意思之外,也有防止园子漂流到无人问津的远海而小氯却浑然不知的考量。但现在园子的技术栈和部署环境已经相对稳定了——已经用上自托管的服务器和通用的 web server 了,小氯想不到还有什么更进阶的方案,总不能用 IPFS 吧——这个需求似乎也变得没有多大必要。毕竟,如果园子真的出了什么问题,Indicator(小氯的公开 uptime 面板)和小氯的老友们大概都会先于统计服务告知我的。
所以,既然这将近一年的数据已经找不回来了,那似乎也没有维持它的必要了。除去又少了一个微不足道的 JS 文件,这也顺便让园子达成了两个成就:其一,它是完全自包含的,只要 clone 园子的仓库,就可以在任何环境下直接构建出园子的静态工件;其二,它不依赖任何后端服务。
智妙明论之林
掩冉众叶之居所
在恢复了几个无关痛痒的小服务之后,小氯就要对 Forgejo 做重建工作了。
Forgejo 的重建本身不费劲,只是需要重新注册账户,然后将本地的所有仓库都重新 git push 一遍而已。另外,Codeberg 的镜像仓库也需要重新配置一下 SSH 公钥(因为小氯用了 SSH 推送 Likelihood 的仓库到 Codeberg)。
大概是到这个时候吧,小氯已经困得受不了了。再清醒过来就是第二天早上八点左右了。
- 朋友:是我抱你到床上的哦。
- 小氯:嗯……嗯?(脸红)我没印象啊。我记得是我自己……
- 朋友:真的,你当时睡得可沉了。而且我怎么感觉你轻了这么多啊,我抱着都感觉硌手……
- 小氯:正常,我每学期都会掉一些体重……等等,你说你感觉我轻了?你对比的基线是什么?
- 朋友:啊,这个,不重要……
(一番拷问后她承认只是想开个玩笑。)
趁此契机,小氯也想解决一个 Likelihood 长久以来的难题,就是 CI/CD。
奥秘栖息之林泽
很久很久以前,小氯有说过一些自己对 CI/CD 的看法。当时小氯的观点大概是——现在也是:不需要让工具本身的复杂性超出要解决的问题。对于小氯这个无聊的园子,CI/CD 并不是什么必要的举动,小氯在本地构建好之后 rsync 上去就好了。
不过这也并不意味着小氯不需要 CI/CD,毕竟某元素娘偶尔还是会写一点小项目的。于是在建设新服务器的时候,小氯也配置了 Woodpecker CI。至于为什么不是 Forgejo Actions……因为小氯不是抖 M,没有用 YAML 模拟控制逻辑的兴趣。虽然说写 pipeline 文件的时候大部分还是会求助语言模型,所以 schema 的逆天程度对决策的影响有所削弱,但谁会不喜欢像自然语言一样简洁流畅的语法呢。
不过我们有个问题:性能。
小氯的 CAX11 确实有一半多空余的内存,两颗 Ampere Altra 的 vCPU 的性能也胜过市面上大多数 VPS。但在面对 Rust 项目的编译时——即使只是中小型项目,也会力有不逮。我们举个例子:为了用小氯自己给 Mitra 做的 fork,我们的 NixOS 需要从头编译这个包,每次编译的时间大概十分钟——不算特别长,但足够把 CPU 全部跑满了。至于更大的项目,小氯难以想象。惟一可以确定的是:小氯这台服务器难以胜任。
那其他服务器呢?CAX 在小氯的 VPS 里面已经是性能最佳;小氯的旧电脑虽然处理器小有实力,但至今没弄明白如何在不妨碍自己未来在万恶的计组课上用 Vivado 的前提下把它变成服务器。
那看来只能想办法搞一下 CI Agent as a Service 了(CIAaaS)。天底下最大的公共 Woodpecker CI 提供者自然是 Codeberg 的 CI 系统,正巧小氯也有将项目镜像到 Codeberg 的习惯。简单调查可知,可以用 evaluate: 'CI_REPO_URL contains "codeberg.org"' 保证 CI 只在 Codeberg 上触发。而 wp-gitea-release 插件完全可以将 release 发到其他的 Forgejo 实例,比如 Likelihood。
看起来一切完美,但小氯卡在了动手前的一步。原因也非常小氯:道德问题。
Codeberg 的 CI/CD 资源是有限的。虽然服务条款没有规定不允许让 Codeberg 的 CI 对接其他平台,小氯的软件也属于自由软件,但这看起来还是在 freeride 人家的 CI/CD 资源。饭桶集线器家大业大,总不至于因为小氯多用了几分钟的 GitHub Actions 就反复下线 uptime 连 90% 都保不住了(一定不是因为 GitHub Copilot 吧,一定不是吧);但 Codeberg 的资源就那么些,全靠几个志愿者用爱发电撑着,对同志还是 nice 些好。双标这一块。
我们岔开一下话题。在这次灾难之前,小氯做了些小小的迁移工作,将 CircleAt 和自己 fork 的 Mitra 的 main repo 都迁移到了 Likelihood 上。这样,Likelihood 就成为小氯绝大部分工作的 SSoT,而 Codeberg 作为被推送的镜像存在,同时承担社区入口的功能。
这个「社区入口」有些难懂,我们简单解释下。虽然小氯目前的项目还是 0 人在意,但准备一个可用的问题反馈和协作机制并不是坏事。考虑到 Likelihood 是不开放注册的,除了等待 Forgejo 的 ActivityPub 实现完善(可能要等很久了)和王政复古地使用 Git patch 外,也只有借助 Codeberg 这一逐渐壮大的社区了。例如,如果有人愿意贡献,可以在 Codeberg 上提一个 PR。小氯审核完成之后,并不在网页端点击合并,而是把 PR 拉到本地 merge,然后推回去。小氯不清楚这样能否让 Codeberg 自动显示 merged,但应该是可以的。
看上去分工明确:Likelihood 作为 SSoT,而 Codeberg 作为社区协作的入口和 CI/CD 平台。但这套方案的佶屈聱牙的程度让小氯自己看了都皱眉。如果社区协作和构建都发生在 Codeberg,Likelihood 这个「SSoT」未免太名不副实了。
最简单的解决方案自然是把 SSoT 直接迁移到 Codeberg,一次性解决所有问题。但这会让刚刚迁移到自己的服务器的小氯显得像个笨蛋。再者,在任何平台上,小氯都不大习惯当用户。这种把自己一部分的生杀大权交到别人手中——即使是善良正直、值得信任的同志手中,即使此地美妙得如同神所赐下的另一片迦南,也总让小氯感到莫名不安。
然而,只要我们坚持守着自己的 Forgejo 实例,就迟早会踏上如何进行社区协作的分叉小径。自己掌控所有的协作过程听上去美妙,当然这需要我们把自己的 Forgejo 开放注册——不少的大型项目,比如 Akkoma 和 Iceshrimp.NET 就是这样。但这在小氯眼里难以接受:其一是对服务器本身的安全性和资源占用的担忧;其二……账户,尤其是这种参与社区活动的账户在小氯眼里是非常严肃的事物。它是你的一个子身份,是你的根身份生长出来的一根健壮的枝杈。这种来一个平台就注册一个账户的行为不仅会导致碎片化和管理的不便,而且(在小氯眼里)是本体论的威胁。至于古典的 patch 和 maillist 工作流,雅则雅矣——(理论上)完完全全的分布式、消除了账户体系的残留,但这种晦涩的仪典对绝大多数的开发者都陌生得如同十五从军征——包括小氯,我现在也没有用 patch 做过任何一次协作。
那在 Forgejo 的 ActivityPub 支持或者 Tangled 的 ATProto 支持成熟前,唯一的方法也只有把社区协作外包了——这也就是小氯现在的架构。再者,就算前两者成熟了,也和我们最初讨论的问题毫无关联——你依然无法证明,镜像到 Codeberg 然后使用它们的 CI/CD 资源有足够的正当性。
于小氯而言,这个问题无解。没有人能给我颁发一份「道德无可指摘证书」,目前的环境条件和技术实现也支撑不起一份完满的实践。理想情况的最优解自然是搭建一份私有的 CI 配合上身份协议的实现。但没办法,后者还在襁褓之中。至于前者……如果你拿不出一整台强劲的服务器的月费,就只能在精神上补缴一些内耗了。惟死亡与税收不可避免。
不过,小氯用的 CI 资源也不多,往多了说可能每月就几十分钟的时间;而代码和社区(如果有)也确实在 Codeberg 上,甚至 release 也有一份。考虑到 Codeberg 的本心就是「建立一个自由的代码创作、归档与保存平台并记录其开发历史」,好像……也不是那么不能容忍……
正在自我催眠的小氯.avif
好啦。既然某元素娘暂时强词夺理地说服了自己,那我们就开始吧。
Codeberg 只提供 x86_64-linux 的 agent,所以为了保证小氯不会吃不到自己做的点心,我们需要用交叉编译。万幸,Rust 的交叉编译支持不错,Woodpecker CI 也有一个用 Zig 作为链接器(哇贞德食泥鸭)的插件,也就是这样:
steps:
- name: 'build'
image: 'ghcr.io/rust-cross/cargo-zigbuild:latest'
commands:
- 'cargo zigbuild --release --target x86_64-unknown-linux-musl'
- 'cargo zigbuild --release --target aarch64-unknown-linux-musl'
用 musl 是为了保证文件是静态链接的,虽然 NixOS 也会帮忙把动态部分削掉就是了。
然后我们需要配置一个凭据,让 pipeline 能够自动发 release。具体来说,在 Forgejo 的设置—应用—API tokens 里面创建一个 token,权限给 issue(Forgejo 的 release 似乎是 issue 系统,但小氯也忘记是从哪里听来的了,可能需要再求证一下)和 repo 的读写就可以。然后在 Woodpecker CI 仓库的后台界面增加一个机密,把它填进去,勾选你在 when 中选择的触发事件即可。
pipeline 文件的相关配置大概就是:
- name: 'release-codeberg'
image: 'quay.io/thegeeklab/wp-gitea-release'
settings:
api_key:
from_secret: 'CODEBERG_RELEASE_TOKEN'
base_url: 'https://codeberg.org'
files:
- 'target/mitra-x86_64-linux'
- 'target/mitra-aarch64-linux'
checksum:
- 'sha256'
然后就可以愉快地 CI/CD 啦。
死生流转真如
此外,小氯顺便给 Likelihood 换了个 icon。
Likelihood 之前的 icon 是一个涂鸦风格的蓝色 L,自然是随着这次灾难而宾天了。新图标的话小氯没什么创意,既然叫「似然」,那就用似然的符号好了,\mathcal{L}。用在线 LaTeX 编辑器导出一个矢量图,然后把颜色调成小氯头发的颜色就可以。
由于只是换色板这种没太多创新性的工作,小氯也没多思考,把作为格式参考的 theme-forgejo-auto.css 和官方的色板文件丢给语言模型就是。另外,Everforest 的作者 Sainn He 也给了一份 Gitea 版本的 Everforest 主题,可以作为参考。
那下面就是安装的工作了。按说这种琐碎的配置小氯一般把它当作数据处理,直接丢到 custom path 下就好。但奈何那一天的元素少女似乎中了什么来自北境的魔咒,坚定地拒绝了自己的好心提醒,希望寻找一种声明式的管理方法。
说起来,「配置」和「数据」(以及诸多的区分也是经典的自行车棚刷漆难题了。小氯区分的方法则颇有佳发蛋糕案之风:
impl File {
// ...
fn file_type(&self) -> FileType {
let flag_edit: bool = self.will_chlorine_edit_it_manually();
let flag_inde: bool = self.will_chlorine_backup_it() || self.if_its_lost_is_it_catastrophic_or_impossible_to_recover();
let flag_rote: bool = self.if_its_lost_can_it_be_rotated_agily();
let flag_base: bool = self.can_it_make_my_program_running_faster();
match (flag_edit, flag_inde, flag_rote, flag_base) {
(true, _, _, _) => FileType::Config,
(false, true, _, _) => FileType::Data,
(false, false, true, _) => FileType::State,
(false, false, false, true) => FileType::Cache,
_ => FileType::LeaveMyMachineImmediatelyYouMicrosoftWindowsSpy,
}
}
}
按照这个标准,自定义 CSS 显然不适合作为数据处理。不过说它是配置似乎也不对劲,它更像是配置的依赖项——或者说,它是程序本身的一部分。那我们的方案就很明显了:把它当作一个包或者输入。
那我们至少先把它建成一个仓库吧。正好小氯很早就想学习 Codeberg 官方,释出自己的实例定制用的相关 assets 了。这种小片段小氯一般不会用 Copyleft,那首选就是宽松许可阵营的 MIT 了。只不过可能是前两天小氯刚刚重新看了《互联网之子》,因此不太想碰和 Massachusetts Institute of Technology 相关的东西。
于是小氯开始在 OSI 的网站上闲逛,然后遇到了这个东西:Blue Oak Model License 1.0.0。
小氯本来是打算从 MIT 许可的 flaws 讲起的,但奈何某元素娘突然不想写了,所以我们便长话短说吧。Blue Oak Model License 是由一群被称为 Blue Oak Council 的律师设计的新许可,它和 MIT、Apache 2.0 和 BSD 许可家族类似,都属于宽松型许可,但语言简短而易懂,同时在专利条款方面弥补了 MIT 的缺陷——MIT 并没有明确授予软件的使用者使用软件中包含的专利的许可。此外,它还有个小巧思:容错性。简单说,如果你不小心违约了,只要在 30 天内改正就没事。
清晰,严谨,自然且有人情味。对于这样的选择,小氯一般将其简称为「cute」。当然,作为年轻的软件许可,它的知名度和认可程度都还不高——OSI 认可它为「开源许可」,但 FSF 还没有授予它「自由软件许可」的认可。然而小氯只是在写一个微不足道的小项目,因此以上缺点可以忽略。
继续。我们如果要发布,还是提前把 CSS 压缩一下的好。简单搜索得到一个名为 Lightning CSS 的命令行工具,是 Rust 写的,善。按理来说是可以写一个 CI/CD 步骤,但某元素娘说过,不要让工具的复杂性超过要解决的问题,所以我们还是把构建放在本地吧。
小氯有自己的微型 Nixpkgs,不过对于这种微型片段,小氯更倾向于用 npins。所以我们的版本号也尽量顺着 npins 来。考虑到这种东西实在没有什么语义化版本可做,于是小氯直接让最后一位永远自增,随机气倒一位追求严谨的开发者。
提交,自动发布 release……很好,然后用 npins 拉取一下。一切如计划般进行。
然后 switch 之后小氯就看到了明晃晃的 broken link。
嗯?
ls 一下相应的 Nix store 目录,惊奇地发现里面有 LICENSE 文件——所以说 npins 没有固定 release,而是固定了 tag 相关的提交?
简单查阅官方文档,发现的确如此。这……倒也符合 Nix 的特性,以及这个社区的调性。
- Track git branches
- Track git release tags
- Tags must roughly follow SemVer
- GitHub/GitLab releases are intentionally ignored
这就不太舒适了。考虑到小氯绝对不希望搞一个 dist 分支,我们还是把它包到 pkgs 里面吧。
那下面回到我们最初的目的——自定义图标。Forgejo 的图标自定义比较奇异,官方的指南是 clone 整个仓库然后执行一条 make 命令。但简单观察源码即可发现,这其实只是一个调用了 sharp 和 svgo 库的简单 JavaScript 脚本,技术门槛接近零。
问题不在脚本本身的使用和实现,而在于小氯希望把它放到自己的仓库中管理。既然 Forgejo 项目是以 GPL 授权的,这个脚本自然也是如此。虽然小氯没有将脚本打包分发,应该不至于被传染成 GPL,但一个 Blue Oak 的仓库里面只有一个脚本是 GPL 授权的总觉得有些奇怪……
那我们净室(也可能不那么净)重写一下好了。之前曾经听过一些事情,某些公司和个人用语言模型大规模重写 GPL 授权的自由软件项目并以宽松许可重新分发,藉此逃避 Copyleft 的传染。对这种行为小氯持保留态度。至于小氯自己的重写……考虑到这个脚本的思路实在是太简单、太直接了,恐怕每一个要完成这个任务的开发者都会采取类似的思路吧,就像你写一个 hello world 然后把它以 GPL 分发并无意义一样。
总而言之,小氯用 TypeScript 随手搓了个功能类似的脚本。然后在 pub 步骤中加入运行这个脚本就行了。
白露澈明之泉
好的,下面就是最大的难题了——Fediverse 和 Matrix Homeserver 的恢复。
众所周知,Fediverse 目前是不能在同一域名下切换实现的,但在同一个域名下恢复同一个实现是可以的。参考了Kora 的相关经验得知,签名一般是账户级的,实例级的可以自然恢复。所以最简单的方法就是换个 handle 了。但小氯显然是舍不得 @chlorine 的,所以我们不妨做个实验。简单发了条帖子,确实被关注我的老友们看到了,Eltrac 还反馈说 Akkoma 还保留着以前的帖子——倒也合理。
万物优雅,缤纷如瓷。看来没什么需要我担心的了。
然后几个小时以后,小氯看着收不到关注请求的友邦开始头痛了。
算了,明知山有虎,还是暂避锋芒好些。小氯的名字还有不少 variation 呢,没必要和现代密码学死磕。于是小氯顺理成章地回退到了 @chlo,直接用域名前缀似乎也是不错的选择。
同理,Matrix Homeserver 的话小氯也不想和它死磕,一起调成了 @chlo。对称是美的。
好吧,虽然其实中间出了许多问题,但好在最后是解决了。小氯还借助上面提到的 Codeberg CI 用上了预编译版本的 Mitra,这样就不用每次都跑满 CPU 了。
只不过,这几天发的帖子似乎都没有回应。老友们都这么现充嘛。
直到我发现我在 Eltrac 的新文章下面的评论没有任何回应,但另外一条内容相同但发布更晚的评论却被回应了,小氯才感觉哪里不对。
上去看看日志……等等。什么叫 private mode?我记得我把联邦打开了啊?
打印当前使用的配置文件,没有问题;把 Mitra 拍扁成 string 搜索,也没发现端倪。当时有些疲惫的小氯选择了求助语言模型。至于结果嘛,事实证明,定理机器是靠不住的。在得到了一众无用的回复后,小氯感觉自己的绿色头发又变灰了一点。
不过 Claude 说的一句话倒是稍微提醒了小氯一下。在源码中翻找半天之后,找到了这样一段:
let mut federation_config = config.federation.clone();
if matches!(config.environment, Environment::Development) {
// Private instance doesn't send activities and sign requests
maybe_user_agent = None;
federation_config.enabled = false;
};
查询 Mitra 的官方文档,得知默认编译时是 Development;看看自己的 CI,没有显式注明 production 标志。
我无话可说(扶额苦笑)。
顺带说一句,有时候和语言模型对话时,小氯会加这样一句:
不要假设我会犯任何低级错误。
事实上,某元素娘还是对自己的杂鱼程度估计过低了。
Frontdown
好吧,重建可以到此为止了。
接下来的任务自然是解决一切罪恶的根源:备份问题。由于小氯用的是 Btrfs,最方便的方案自然就是 Btrbk,打一个快照也并不占地方。至于用什么来备份真正的数据,这事情也好想,当然是 Restic。
问题在于备份到哪里。
十殿阎罗
Backblaze B2
B2 也是一个(兼容 S3 的)对象存储服务,提供 10 GB 的免费额度。缺点嘛,美国公司。虽然 Restic 有客户端加密,理论上你把备份放在大洋国都没问题,但还是值得提一下。
实话说,B2 和 Cloudflare R2 差不多,甚至后者更合算些。只不过小氯已经不用 Cloudflare 了。
Bunny Storage
Bunny 是一家斯洛文尼亚的云服务商,声誉还不错。不过,Bunny Storage 初衷是配合 Bunny CDN 用的,直接操纵存储本身似乎并不合理。而且,它现在还没有提供 S3 兼容的 API,操作起来不大方便。
Hetzner Storage Box
大家或许不知道,HZ 家的 Storage Box 也是一绝,4 USD/(TB*mo) 的价格堪称慈善(这个型号叫 BX11)。但第一,小氯用不上 1 TB 这么大的空间;第二,HZ 服务器的备份放在 HZ 上……众所周知,没有异地备份等于不备份,要是哪天 HZ 爆炸了或者是发疯把小氯的账号封了,这多少有种沦落的美。
VPS
当然了,最 hardcore 的方案显然是再维护一台大盘鸡(硬盘较大的 VPS)。
在这一行里最著名的是 BuyVM。你大概可以把它理解为 VPS 界的 MXroute(事实上这两家公司的老板关系的确不错——至少以前如此)。卢森堡的机房——GDPR,抗版权投诉、无限流量、高质量的 CPU 或者是极大的硬盘,价格却依然控制在每月几美元的舒适区内。
当然,咱们想要,道友们同样如此,BuyVM 不管是哪个机房的哪款配置,都常年处于断货状态。而且,这是一家加拿大的公司。虽然说至高无上的人类神皇唐纳德·J·特朗普一世陛下一时半会儿还不会把加拿大变成至高无上的人类神皇唐纳德·J·特朗普一世陛下的永远伟大的神圣美利坚帝国的第五十一个州,但这是个值得提醒的事实。
此外值得考虑的是 AlphaVPS,一家保加利亚的老牌主机商——小氯还算蛮熟悉保加利亚的(笑)。保加利亚也是欧盟成员,但位置比较偏远,这可能也是件好事。
rsync.net
这个网站听着像 rsync 的官网,但正如 mail.com 也不是电子邮件的官网一样,它其实是一个提供存储服务的商家,也并不只支持 rsync。
We give you an empty UNIX filesystem to access with any SSH tool
Built on ZFS for data security and fault tolerance
You can back up any other cloud with your rsync.net account.
rsync / sftp / scp / borg / rclone / restic / git-annex
那么代价是什么呢?
首先,它贵——我是说,真的很贵,一般账户的价格折算是 12 USD/(TB*mo),还得是 800 GB 起订,即使是专家账户也是 1.6 USD/(200*GB*mo);其次,它是家美国公司——虽然它有苏黎世的机房。
BorgBase
听着和备份工具界的老兵 BorgBackup 息息相关,但它倒也支持 Restic。公司总部在马耳他(这里怎么也有医院骑士团的事情),有 10 GB 的免费额度,最小套餐大概是 2 USD/mo,看起来一切都很完美。
那么,这么优质的服务,一定用了很出色的基础设施吧?
是的,它的 EU 服务基于……Hetzner。
(深呼吸)(试图冷静)(报错)(抄起机械键盘)(发现自己没有机械键盘)(抄起 MacBook)(准备时空折跃到马耳他)(被小钠按住)
So What?
最后小氯的选择大概有点出人意料:是 B2。无它,只是因为小氯刚好有一个 Backblaze 账户而已。要说小氯对它多满意吗?不见得,但它能用,这就可以了。毕竟,某元素娘已经不想让自己的头发再变灰了。
另外,小氯也注册了一个 BorgBase 账户,计划无耻地消耗对方的善意借助免费额度做一次多点备份。虽然如果 Hetzner 被陨石砸了,这个备份的作用就相当于塔克拉玛干渔业管理局,但在一般情况下还是有些益处的。
妄想录
这一节的题目毫无含义,只是我偶然看到了我歌单里面有一首歌的名字翻译过来是这个意思,于是就用了。
简单来说,在组织好这个备份系统后,小氯就愉快地继续休假了。直到一个星期后,小氯突发奇想地登录服务器想看看自己的备份情况如何,然后就发现了点奇怪的事情:
怎么所有备份的大小都一样啊?
要说小氯在这一个星期没有产生一字节的数据显然是不可能的,所以大概是服务的确出了问题。简单打印日志后发现,服务一直在备份最早的那个快照。
好吧。虽然不明白原理,换个方法就是了。
SNAP=$(ls -1d /snapshot/@.* 2>/dev/null | sort -r | head -n1)
然后手动运行一次……报错。错误信息是……找不到目录?
这就很奇怪了。加调试日志看一看,明明能看到目录啊。那估计是服务的权限问题。搜索引擎大海捞针,求助语言模型半天,无果。
由于当时小氯急着去和朋友贴贴,因此情绪极不稳定之下决定遁入魔道:
ProtectSystem = lib.mkForce false;
ProtectHome = lib.mkForce false;
PrivateMounts = lib.mkForce false;
PrivateTmp = lib.mkForce false;
RestrictNamespaces = lib.mkForce false;
ProtectControlGroups = lib.mkForce false;
问题解决。
后记
嗯,没了。小氯也不知道记一下自己的杂鱼操作为什么也能写这么多。
最近小氯在赶论文,加上在高强度钻牛角尖,精力消耗得差不多了。接下来小氯可能会休息一段时间,至于什么时候复活,可能要取决于那天第三杯果茶的温度如何了。
就这样好了。祝老友们万事胜意。
B-Side
Genshin Impact OST.









