2026-02-02 09:28:22

girl in red
📃
一项新研究发现,原本有过心脏病发作的人群中,开始服用维生素 D 补剂并维持在最佳剂量内的,心脏病发作率降低了 52%。维生素 D 其实和心血管疾病没有直接关联,本文解释了维生素 D 与心脏健康的关联。简而言之,维生素 D 被人提吸收后,会调节上百种作用效果不同的基因,能够促进钙的吸收、调节炎症反应、调节血压——可以这样想,维生素 D 减少了其他疾病突然引发心脏病的概率。
维生素 D 的主要获取途径有三种:
我大概从一两个月前就开始服用维生素 D3 补剂,因为我发现自己根本没有办法每天晒 30 分钟太阳,无法从阳光获得足够的维生素 D。维生素 D3 补剂其实比较便宜,几十块钱买一瓶能吃几个月。关于购买补剂,这里分享一些个人经验供参考。3
补剂不是智商税,不自己动脑子找靠谱的资料研究,并在选购时忽略宣传话术找到有用信息自己做判断,才是交智商税。
📻
学者与喜剧演员的相似之处在于,这两类人都不太可能是为了名利而走上这条职业道路的,并且他们都需要一定的天赋。不同之处在于,搞笑有着一个不可违抗的自然标准,那就是「使人发笑」——即便一个人性格卑劣,但他要是真的让你忍不住笑出了声,那他就是搞笑的。然而,搞学问却有不少人造标准,比如某某学者发了多少篇论文、书是哪个出版社出版的,如今的学者似乎已经不在乎学者自己的观点了,更多地关注「别人怎么评价他的观点」。
允许我忽略内容,谈谈这期播客的结构。树老师在一开始先建立了「共识」,也就是「学者和喜剧演员可以在高尚的尺度上进行比较」的前提。我认为这是在进行智识交流时必要的步骤。之前有人说我文章写得太长,我认为我是把大部分的字数都用在了交代背景和建立共识上。当然,读者可以不同意我建立的「共识」,这样就没必要继续读下去了。
这个网站收集了各种猫猫图片,用来表示 HTTP 状态码,可以很方便地使用 https://http.cat/[status_code] 来引用某个状态码对应的图片。没有太多实用价值,但是猫猫可爱。

访问: http.cat
一家小公司,是 Letterbird 背后的团队,有不少和 Letterbird 类似的小巧的互联网产品,可以访问他们的 Lab 页面。他们的理念和行事风格我都很喜欢。他们以前甚至有一个连接到互联网的打印机,用户可以直接用这个打印机给他们发送内容。真想在这样的团队里工作。
访问: Good Enough
新网站 Jar 在本周一上线了,名字和 Java 没有任何关系。法语里的花园是 Jardin,前三个字母连在一起就是英语里罐子(Jar)的意思。对我来说,比起不断生长的数字花园,什么东西都往里面塞的罐子,大概更符合我的需求。这里面会存放极客死亡计划的废稿,一些不知道写在什么其他地方的笔记,未来还可能存放计划、路线图之类的东西。
设计上,Jar 秉承
mother-fucking website
的理念,几乎没有编写太多的 CSS,并且也是静态网站,用的也是 Hugo。值得一提的是,Jar 的域名是 jar.geedea.pro,而没有被放在 eltr.ac 域下,你可以把它理解为极客死亡计划的「后花园」。
不建议订阅这个网站,但可以偶尔来看看,因为这里面什么都有。
详见《 极客死亡计划书 IV 》。从现在起,请使用联邦宇宙、Webmention 或者电子邮件与我交流。另外,博客的点赞功能也被我偷偷移除了。
2026-02-01 23:53:07
书接上回 ,先前的一期《极客死亡计划书》最后留了个有关评论系统的引子。如你所见,目前我已经完成了评论系统的变更:我把它删掉了。当然,我提供了其他交流的途径,你仍然可以与我和其他读者,像在正常的评论系统中一样讨论文章内容。今天的文章将会解释评论系统变更的原因、新的与读者互动的方式、我对独立博客的评论的想法,以及读者还可以了解到如何使用新的「交互」系统。
从 Typecho 换到静态博客之后,给博客添加评论系统就变得很棘手。本质上,评论是「用户生成内容」(User-generated content),涉及到用户向服务器发送 HTTP 请求,服务器响应请求后将评论数据写入数据库并返回状态信息,其他用户访问页面时,服务器会从数据库读取评论数据。显然,对于静态网站而言,服务器和数据库都是不存在的,所以就需要外接一个评论后端。
记得早年间我还使用过
Gitalk
,一个基于 GitHub Issues 的评论系统。后来 GitHub Discussion 推出,更符合「讨论」这个逻辑,于是
Giscus
也就诞生了,几天前,我还在使用 Giscus 作为博客的评论系统。这两个评论系统都依托于 GitHub API,需要 GitHub 账号才能使用。用户也不真正拥有自己的数据,所有的讨论实际上都发生在 GitHub 这一个平台上。上一期极客死亡计划书我也表达了对 Giscus 的批评——依赖于 <iframe>,使得数据交换、优化加载速度和自定义都变得很难。不过,毋庸置疑地,Giscus 非常易用,不需要自己维护后端,使用 GitHub 登录也避免了绝大多数的垃圾邮件。
在此之前,我也有使用过 Valine 和 Waline,它们都依托数据库,可以很轻易地在 LeanCloud 上部署(不过就在最近,LeanCloud 已经停止服务了),也可以自托管。我也有听闻,并且作为访客使用过其他人网站上的 Twikoo、Artalk 和 Remark42。这些评论系统的设计各有特色,但共同之处是你需要自己搭建并维护后端和数据库,这不难,但必然会增加网站架构的复杂性。
动态博客加上评论系统是顺带的事情,但对于静态博客来说,为静态网页加上动态的内容,还要单独维护一个后端,就显得有些不符合静态网站这种架构的哲学了——既然你都选择维护额外的后端和服务器了,那你为什么不直接用动态博客呢?反正都要有后端的,一个轻量的 CMS 并不会占用太多资源。
本来我是为了摆脱 Giscus,才开始思考要不要自托管评论系统这件事的,但我很快意识到我并不想增加网站的复杂性。如果使用传统的填写称呼、电子邮箱和网址直接评论的方式,就会有身份被冒充的风险(获得某人的邮箱地址简直是轻而易举),而且垃圾信息也很难避免,需要做额外的措施。如果使用必须登录才能评论的方式,的确能解决上述问题,但我希望自己的个人信息被上传到尽可能少的地方,而且如果要变更邮箱地址,四处更改账号信息也是吃力的活儿,我的上网原则之一是「如非必要,绝不注册」,我当然不希望我的读者必须额外注册才能评论,维护一个用户认证系统也会让维护后端变得更复杂。
显然,这已经告别了绝大部分的评论系统了。
为了认证用户且不必维护新的认证系统,那么最简单的方式当然是使用一个现有的认证系统。我指的并不是 Google 登录、Apple 登录和社交媒体账号登录这种方案,因为实际上我仍然需要将用户的登录信息储存在自己的数据库里。最好是使用去中心化的身份系统,比如 OpenPGP 密钥对。
诚然,OpenPGP 也受到批评,它有时会
失效
。不过,在本文中,我们假定它是有效的,因为我还没有开始了解和研究它为什么有缺陷,而且,OpenPGP 也有替代品,你可以把本文中提及的 OpenPGP 理解为「OpenPGP 和与之类似的加密标准」。
在上一期《极客死亡计划书》中,我提到,电子邮件的发信一般是可信的。如果你收到了来自 [email protected] 的邮件,那么发信人基本可以确定就是 Eltrac。尽管也有伪造发信地址的技术手段存在1,但 SPF、DKIM 和 DMARC 等反制手段也很成熟了。在发信协议之上,也可以使用 OpenPGP 加密或者签名邮件2,使用公钥验证再次验证发信人的身份。
所以,如果能开发一种基于电子邮件的评论系统就太好了,大多数用户都有电子邮箱账号,而且电子邮箱是去中心化的,大家可以使用自己熟悉的邮件服务进行评论。我的设想是,评论者向一个电子邮箱地址发送邮件,比如 [email protected],邮件主题是页面标识(标题、URL 或者别的什么 ID),而邮件正文是评论内容。邮件服务器在这个邮箱收到特定格式的邮件后,如果没有问题,就把它当作有效的评论处理,存入数据库或者通过 CI/CD 工作流直接写入博客源代码。如果用户有提供签名,验证身份之后还可以像 Git commit 那样,在前端显示 Verified 或者一把小锁,表示评论者的身份是可靠的。技术上来讲,这完全可行。用户交互层面也不会很麻烦,因为可以使用 mailto: 链接,帮用户自动填写邮箱地址和邮件主体,用户只需要在自己的邮件客户端里撰写评论正文。
不过,我仍然需要一个用来接收和处理邮件的后端,把邮件内容写入博客显然也会增加网站架构的复杂度。通过发送邮件在网站上发送评论,仔细想想,其实非常反直觉,用户需要在不同的界面之间跳转,而且邮件发送和服务器处理会造成时延,用户不能立刻得到反馈。
这个方案最终还是没有被我采用,尽管头脑风暴的过程很爽。兴许写下来之后,会有人觉得有用吧。
谈到去中心化,那就不得不谈联邦宇宙了。尽管我并不了解 ActivityPub 规范和设计思想,但我大概了解联邦宇宙的工作方式。简单来说,所有人都可以待在自己选择的服务器上,并且与其他服务器通信,构成「互联网」。联邦宇宙并没有提供密码学意义上的身份认证,但至少伪造身份并不容易,尤其是对于那些拥有自己的域名,并且使用这个域名自建联邦宇宙实例的人来说。
比起一对一的电子邮件,开放的联邦宇宙更适合「公开讨论」。将联邦宇宙用作评论系统可以一举两得地解决身份验证和评论数据管理的难题,毕竟这两部分的复杂度都已经被成熟的联邦宇宙软件解决了。其实这和 Giscus 的逻辑差不多,如果使用 Giscus,讨论实际上在 GitHub 上发生,用户也使用 GitHub 账号登录,Giscus 所做的只是把 GitHub 的讨论和万维网页面映射关联起来。区别在于,使用联邦宇宙作为评论系统的话,尽管讨论也在博客之外的地方发生,但联邦宇宙是开放的,不受 GitHub 这样的中心控制。
如果为了评论而专门开发一个实现了 ActivityPub 的软件,显然有些没必要。符合直觉的做法是,文章发布后,将页面 URL 发布在联邦宇宙账号上,关注者看到之后就能直接对帖子进行互动(点赞、表情回应和评论)。这个帖子(ActivityPub 对象)就作为网站页面在联邦宇宙上的「载体」,对这个帖子的回应就被视为对原文的回应。
听起来只需要让访客到联邦宇宙上讨论文章内容就好了,不过要怎么把联邦宇宙上的讨论内容展示在网站上呢?一是可以在前端通过 API 动态抓取,二是可以用 CI 工作流每隔一段时间抓取一次数据,如果有更新就提交到 Git 仓库,Hugo 直接渲染保存在源码里的数据。那一开始要如何把联邦宇宙帖子和网站页面关联起来呢?一是可以每隔一段时间扫描指定账号下的帖文,检查是否有指定网站的 URL,如果有,就把这个帖子和帖子引用的链接关联起来;二是可以在网站源代码(比如 Markdown 文件的 Frontmatter)中指定帖文链接,直接请求这个帖文的数据。
听起来还是要从头开始搓一个工作流呢,增加复杂性看来是不可避免了,除非有另一个现成的解决方案?
我在
第 60 期周刊
中提过,博客实现了
Webmention
的接收;又在
第 63 期周刊
中描述了通过自动化工作流从 webmention.io 拉取 Webmention 数据并保存在 Git 仓库,让 Hugo 直接渲染本地数据文件的做法。简而言之,博客现在已经能够很好地接收并展示 Webmention 了,并且不会牺牲 Hugo 作为静态博客的速度优势和简单性。
Brid.gy 很好地实现了前文提及的「定时扫描联邦宇宙账号和网站页面并建立映射」的做法,简单来说,只要用联邦宇宙账号登录 Bridgy,它就会抓取与你有关的互动,比如有谁点赞了你的某篇帖子、有谁回复了你的帖子等等。与这些互动直接关联的帖子,如果包含一个 URL,Bridgy 就会尝试向这个网址发送 Webmention。

由于博客已经能够接收 Webmention,我也就不需要再想办法把联邦宇宙上的互动信息显示在博客上了,只需要渲染 Webmention 就好。
这种方案有缺陷,比如 Webmention 的同步有延迟,并且只能展示与帖文直接关联的互动,所以只能展示一级评论(评论的评论,也就是子级评论,由于是对回复帖文的互动,并不是直接关联的,所以不会被抓取)。不过,我认为这很符合 Webmention 的逻辑,即「有人在万维网的另一个地方提及了这个页面」,也就是说,这并不是评论系统的替代,而是告知读者「在另一个地方有人讨论了这篇文章」,如果读者感兴趣,就可以通过链接前去查看。
如果你现在移动到页面最下方,就能看到这样一个条:一个写着「+」的按钮,右边是一些人的头像,最右边显示的是喜欢了这篇文章的人的数量。点击按钮,就能看到一个浮窗,里面有两个表单。

第一个是联邦宇宙表单,你可以在这里填入你的实例域名,比如 c7.io mastodon.social 或者你自己的域名,点击「打开帖子按钮」,浏览器就会打开 https://<实例域名>/authorize_interaction?uri=<这篇文章对应的帖子链接>,效果就是在你的实例上打开这篇文章对应的帖子。不过,这个功能我自己测试的时候有点问题,还请更了解 ActivityPub 的读者给我一些反馈。你也可以选择留空,这样就会直接打开源地址。
值得一提的是,很多页面上都不会显示联邦宇宙表单,原因是页面发布的时候我还没有开始使用联邦宇宙,所以没有关联的帖子(当然你仍然可以在联邦宇宙上 @ 我并附加文章链接,来与我交流),另一种可能是你来得太早,我还没有发布对应的帖子。
第二个是 Webmention 表单,如果你撰写了一篇文章,并且在文章中提及了某篇文章(指页面中包含指向这篇文章的链接),就可以在这篇文章下方的 Webmention 表单里输入你的文章的 URL,点击发送即可向我发送 Webmention。你会被跳转到 webmention.io,如果成功就只会显示一串 JSON 数据,我之后可能会优化一下交互反馈。如果你使用的是 WordPress 或者别的什么实现了 Webmention 发送的博客软件,这个过程就是自动的,你无需手动提交 Webmention,具体能否自动发送还请读者自行研究。
简单来说,如果你要点赞或讨论某篇文章,请点击「打开帖子」并在联邦宇宙上对帖子进行点赞或讨论;如果你在你的网站上提及了我的文章,就可以向我发送 Webmention。
联邦宇宙是 Mastodon、Pleroma、Misskey 等实现了 ActivityPub 协议的开源自由软件组成的社交网络,你可以理解为去中心化的 Twitter(也有实现了 ActivityPub 的 YouTube 和 Instagram 替代)。你可以在任何一个联邦宇宙服务器上注册账号,或者自己搭建服务器,在任意一个服务器上登录都可以与任何一个联邦宇宙中的账号互动3。
我自己的实例( Eucalyptus )是不开放注册的单人实例,以下是公共实例,你可以选择一个加入,然后就可以和包括我在内的整个联邦宇宙互动了。
你也可以在 FediDB 上查看较完整的服务器列表。如果你对联邦宇宙一无所知,可以访问 这个网站 ,这里有直观的介绍。
有了一个联邦宇宙账号,你就可以点赞和评论我的帖子了。按照上述方式在你的实例上打开我的帖子,就像 Twitter 一样操作吧!当然,有了联邦宇宙账号之后,你能做的事情非常多,你可以去关注更多的人,也很容易在上面认识新朋友。
Webmention 和 ActivityPub 一样,都是 W3C 标准。我相信你一定有在微信群聊和社交媒体等平台上 @ 某人的经历,我们把 @ 称作「提及」,@eltrac 就是「提及 Eltrac」。显然,「提及」是局限于平台的,你只能提及和你在同一个社交媒体平台上的人。Webmention 直译为「万维网提及」,也就是发生在网站之间的「提及」。假设 A 在自己的网站上发布了一篇文章,B 觉得这篇文章写得不错,也有感而发写了一篇文章,并且在这篇文章里「提及」了 A 的文章。正常来说,A 并不会像在微信群聊里被「提及」了一样收到通知,但 Webmention 补足了这一点——如果你在自己的网站上提及了某人,而对方开放了接收 Webmention 的端口,你就可以向他发送 Webmention 通知;对方不仅能收到通知,还能把提及的内容展示在自己的网站上。
如果你在联邦宇宙账号上和我互动,你的点赞和评论就会通过 Webmention 的形式同步到我的网站上。你也可以不在联邦宇宙上与我互动,而是通过撰写文章页面的方式提及我的某篇文章。如果你使用 WordPress 等博客程序,你的网站大概会自动发送 Webmention;如果你不能自动发送 Webmention,你就需要手动在页面下方的表单里提交你的回应文章。
或者,你也可以用上古神器 curl 发送,毕竟 Webmention 实际上就是一种特殊的 HTTP 请求。
curl -i
-d "source=<你的文章URL> &target=<目标URL>"
https://webmention.io/www.geedea.pro/webmention
如果你想要自己的博客能够接收 Webmention,你可以参考 Taxodium 的 这篇博客文章 。4
当然!非常欢迎!我很喜欢和人用邮件这种方式交流。
之前有一些读者询问能否添加我的微信好友,我毫不犹豫地拒绝了。第一是我讨厌微信,我只在不得不使用的场景下使用这个软件,比如和现实中的朋友交流、工作和付款等等;第二是我希望尽可能保持匿名,我的互联网生活和现实生活是分开的,我的微信好友位一般只会留给现实生活中的朋友。之前我还会使用 Telegram 和网友交流,现在也弃用了。5
如果你偏好一对一的沟通,我最推荐的方式是通过邮件联系。不必注重格式,只要写清楚主题,交代清楚来意就好,就算是随便打个招呼我也会觉得很开心。相反,如果是给我发微信,就算是给我发钱我也会觉得压力山大;如果是打电话,那我会在做好接电话的心理建设之前大声尖叫。
这是我的邮箱地址:[email protected]。你也可以在文末的互动浮窗里点击「📧 也欢迎邮件交流!」这行字,这是一个 mailto: 链接,点击后会打开你的默认邮件客户端,并且会自动帮你填好邮件主题(Re: <文章标题>)。
去年二月份我写过一篇《 论独立博客的评论 》。这篇文章下也有不少人分享了他们的看法。这些评论原本是保存在 GitHub Discussion 上的,你现在可以到页面底部点击最下方的一行字,展开旧的评论数据,我都有保留。翻到这篇文章的时候我也有思考,我这么放弃了传统的评论区,是不是也放弃了这样有趣的交流呢?
我想我必须试试才知道。那篇文章中有提到 Owen 把 Telegram 群组代替评论系统当作一场实验,那么我在今年年初开始用联邦宇宙和 Webmention 代替评论系统,也算是一场不错的实验。我也很期待,我在这篇文章关联的联邦宇宙帖子上,以及发送到我网站上的 Webmention 里,也能看到同样有趣的交流和回应。
最近有读到一些博主对评论的思考,尤其是早就禁用了评论区的博主,再加上我最近也收到了我不太喜欢的评论。我又开始思考,评论区真的是最适合我的交流形式吗?我回忆起最近让我感到最愉悦的交流,也包括那些让我受到启发的留言,几乎全都来自联邦宇宙和电子邮件(那些给我发邮件的读者,谢谢你们!)。我也有想过要不要写一篇文章,谈谈我希望收到什么样的评论,不希望收到什么样的评论6,但仔细想想,我讨厌的是不能完全尊重我和我的观点的评论,如果评论者都不尊重我的观点了,他会同意我有关「我希望收到什么样的评论」的观点吗?这显然不会起作用。
此外,我还经常发布大多数人并不在乎的内容,比如法国文学的书评、塔罗牌的牌意解析,未来大概还会有…… Lisp 的学习感受?而这样的内容往往只能收到很少的评论,但它们又是我非常感兴趣的话题。原来的评论区会让我感到压力,而现在,即使一篇文章没有收到太多的回应,我也不会那么在意了,因为评论区都被我砍掉了。联邦宇宙互动和 Webmention 较少,是可以理解的,毕竟使用这些技术的人本来就比较少。抱着这样的心态,我大概会更有勇气写自己感兴趣的文字,虽然我大概还是会阴暗地在 GoatCounter 仪表盘查看某篇文章的访问量。
至于我在写作之外,能和读者以及其他的创作者通过新的交流方式碰撞出什么火花,那就在 2026 年一起好好见证吧!
一般来说,不建议发送加密的邮件,因为不符合标准。有关为什么不应该加密邮件内容的讨论,可以参考 Migadu 的说法 。况且,有了 SSL/TLS 的保护,邮件在发送过程中往往是安全的。 ↩︎
除非有管理员封禁了某个实例的情况,但一般来说不会。 ↩︎
是的,我会给他发 Webmention 的。 ↩︎
如果你还是更喜欢即时通信工具,你可以在 Matrix 上找到我,这是我的账号:@eltrac:matrix.org ↩︎
比如,那些显然没有读完我的文章,也对我没有太多了解,就凭借零碎的信息开始想象我的人物形象,给我贴标签或者对我进行说教的。哪怕没有恶意,也有爹味溢出屏幕的。这就是我不希望收到的那一类。 ↩︎
2026-01-29 14:49:53
读完最大的感受是,现在的读者和观众看了《艾米丽在巴黎》这样一部剧之后都会觉得剧情太狗血,十多年前的《绝望的主妇》也有人做出相似的评价,动不动就要给编剧寄刀子,真该来读读这部小说。
先容我快速地叙述一遍故事主线和结局。按照惯例,我相信好的小说不会因为被透露剧情就影响阅读体验,所以我会毫无保留地写作。故事发生在意大利,拿破仑还在征战的时期,主角叫作法布里斯·台尔·唐戈,是一名贵族。小说主要讲述他与几名异性的情感纠葛,也涉及不少宫廷政治(这部分对我来说很无聊)。法布里斯刚长大,就极度崇拜拿破仑(《 红与黑 》里的于连也是),于是干了件蠢事,跑到法国去投靠拿破仑的军队,想要去打仗。这样的蠢事在之后的故事中还会出现很多次。
作为一位侯爵的儿子,法布里斯的行径让本不看好他的父亲和与他父亲是同类人的哥哥感到非常愤怒。法布里斯的姑姑,彼埃特拉内拉伯爵夫人,很喜欢法布里斯。法布里斯由于没有护照、身份可疑,又遇上了一些军队里的纷争,惨兮兮地在好心人的帮助下逃到巴黎,在那里收到了母亲和姑姑的信,得知他的哥哥向米兰的警察告发了自己(大概是…… 叛国罪?),他要是回家就会被抓起来。他姑姑伯爵夫人借助她和帕尔马警卫大臣莫斯卡伯爵的关系,将法布里斯送到了安全的国外,此时,狗血的剧情就开始了。
法布里斯是个很清秀又很有热情的年轻人,他姑姑(丈夫死了)感到自己对法布里斯有种禁忌的爱,而法布里斯走后,帮助法布里斯脱险的莫斯卡伯爵由于和她频繁交往,再加上她实在美丽,爱上了他。莫斯卡伯爵提出,彼埃特拉内拉伯爵夫人和他搬到帕尔马,与宫廷里的一位老公爵形婚,这位桑塞维利约公爵人很好,他们无须经常见面也不用做什么事情,她就能有一栋很好的宅邸和遗产,这位老公爵很快就死了。从现在开始,彼埃特拉内拉伯爵夫人成为了桑塞维利约公爵夫人,并且在此期间继续和莫斯卡伯爵交往,而伯爵让法布里斯去进修神学,三年后好为他谋得一个大主教的职位(他这样出生高贵的人,当上大主教是很容易的)。此时,复杂的宫廷政治也开始了。
时间直接跳跃到四年后,法布里斯被莫斯卡伯爵接回来,此时人们已经忘记了他之前的冲动,公爵夫人也对他的改变感到震惊(有种…… 看到年轻的情人长大成人的感觉,更爱了呢)。亲王不喜欢法布里斯,因为不喜欢聪明人的气质,所以写匿名信给莫斯卡伯爵提及公爵夫人对法布里斯的爱情,这里伯爵忌妒得疯狂的心理活动非常值得品味,我会在后文展开谈到。法布里斯暂时离开了一会儿帕尔马,回到他父亲的城堡,去找布拉奈斯神父(他的精神父亲,某种程度上也是他真正的父亲)。他受到了很好的招待,神父也通过占星术预言他将会遭受一段牢狱之灾。
法布里斯意识到了自己的姑姑公爵夫人对自己的感情,而他自己也有着相似的爱,但是他并不想提出来,毁了他与他生命中最重要的女人的关系,于是,他的解决方案是…… 去追求另一个女人。他追求的是一个戏班子的女朋友,玛丽埃塔,戏班子吉莱蒂也就是他的轻敌,与法布里斯在边境决斗,法布里斯杀死了吉莱蒂。贵族杀死平民本不是件大罪,但由于他和莫斯卡伯爵的关系,而宫廷里有不少小人都想要莫斯卡伯爵下台(伯爵是亲王的心腹),其中一人就是审判官拉西,所以这桩小小罪行就被闹大了,法布里斯不得不在国外避风头。帕尔马这边,公爵夫人和伯爵一直在帮法布里斯想办法,兰德里亚尼大主教(法布里斯是代理大主教)也站在他那边。不过,宫廷政治还是太复杂了。在国外,法布里斯又遇到了玛丽埃塔,意识对对她实际上没有真情实感的之后,法布里斯冒险回到了帕尔马,去追求一位女歌唱家,浮斯塔,又和情敌纠缠打斗。由于交际圈的重叠,他在某人的客厅里同时见到了浮斯塔和他姑姑公爵夫人,这使得公爵夫人很生气。此时,上卷完。
伯爵政治上的敌人拉维尔西夫人写假信诱骗法布里斯回国,法布里斯被捕,关进了帕尔马的一间要塞,还是在高塔上(上卷的预言在此应验)。法布里斯被关进要塞的第一天,见到了要塞司令的女儿克莱莉亚,他们之前也有过一面之缘,当时公爵夫人帮助过她。两人就这样在要塞门口看对眼了,法布里斯爱上了她,克莱莉亚也觉得法布里斯有颗高尚的心,关在塔顶的法布里斯想办法在窗户上钻了个洞,每天和克莱莉亚眉目传情,还做了一套字母交流。从现在开始,克莱莉亚的磨难开始了,她想要帮助法布里斯,却不想让父亲为难。之后,公爵夫人带人协助法布里斯越狱,为了把绳子带进要塞,他们迷晕了要塞司令。克莱莉亚见自己的父亲因为法布里斯差点被毒死,感到悔恨,发誓再也不见他。她决定遵从父亲的意愿,嫁给一位很有钱的侯爵。当然,这段时间他们也有过各种拉扯,总之克莱莉亚一直摇摆不定,为道德观念和情感所折磨,而且,她知道法布里斯是个多情的人,她不相信他们俩的感情会有任何不同。公爵夫人和莫斯卡伯爵这边,因为救不出法布里斯,让伯爵这个警卫大臣很为难,他俩闹矛盾分手了,好几章的政治把戏过后,伯爵终于有了进展,公爵夫人收到伯爵的信过后,感觉自己简直要爱上他,还说要嫁给他。
这里省去各种不必提及的人和细节,公爵夫人最后成功帮助法布里斯越狱,并且极力推动克莱莉亚和那个侯爵的婚姻,想着这样能让法布里斯死心。后来,帕尔马亲王死了,他的儿子上任。公爵夫人发觉新亲王对她有爱慕,便卖弄风骚、耍了些把戏靠近亲王,想要借此帮法布里斯脱罪。法布里斯这边,甚至想要劫婚,不过他做了更疯狂的事——当公爵夫人和伯爵正在大力推进重新审判他这件事时,他跑回了要塞,让要塞司令给他关起来,这样就能再次见到克莱莉亚了…… 然后他就又在那个窗洞上跟克莱莉亚打招呼,让克莱莉亚痛苦不已。这是蠢还是浪漫呢? 法布里斯上一次在要塞的时候,就有人想给他下毒,这次也是。克莱莉亚听到消息之后,害怕法布里斯被毒死,带着面包冲进了牢房。这时,法布里斯倒在地上,其实还没有吃饭,但他知道,要是克莱莉亚以为他快死了,她就会流露真情——然后他们就在牢房里做爱了。
由于担心法布里斯被毒死,公爵夫人也陷入了疯狂当中,去找年轻的亲王帮忙。谈判多次后不得结果,公爵夫人答应了亲王的请求:如果你救出法布里斯,我就做你的女人…… 然后法布里斯就被救出来了,伯爵政治上的敌人也下台了,法布里斯当上了副大主教,客厅里挤满了人。可是法布里斯开心不起来,因为克莱莉亚已经成了侯爵夫人。公爵夫人没有履行对亲王的诺言,并且因为亲王要求这样一件损害她名誉的事情,和莫斯卡伯爵一起离开了帕尔马,她恨透了这个监禁他侄子的国家。离开后,公爵夫人成了莫斯卡伯爵夫人。亲王为了报复她,又把伯爵的敌人雇了回来,结果只是把他的宫廷搞得一团乱。
现在只有法布里斯还留在帕尔马,意外地受到亲王的喜爱,作为副大主教的声誉甚至比大主教更好。由于陷在爱情的痛苦中,法布里斯足不出户,吃不下饭,竟被人认为是高尚的举动,更受人崇拜了。之后,法布里斯准备开始讲道,并且在靠近克莱莉亚的教堂里讲道,想引起她的注意。终于,克莱莉亚被法布里斯的才干折服,但是她不能违背她向圣母承诺的「永远不见他」的愿心——与是他们就在晚上不点灯的时候偷情(我真的没招了)。三年过去,法布里斯和克莱莉亚有了儿子,当然还是由侯爵抚养,偷情一直没有被发现。法布里斯没有莫斯卡伯爵和姑姑的陪伴,也不能和克莱莉亚在白天见面,孤身一人,所以向克莱莉亚要求他的儿子——然后他们让儿子装病,在伯爵出差的时候伪装了儿子的死,然后把儿子接了出去。接出来不久后,儿子就真的病死了,克莱莉亚以为这是对她的惩罚,不久后也随儿子去世了。法布里斯悲痛万分,辞去了主教职务,到帕尔马的一间修道院去生活,每天也去找姑姑社交。搬到修道院的一年后,法布里斯去世了,莫斯卡侯爵夫人也在心爱的侄子死后不久去世了。
故事就这样结束了。
桑塞维利约公爵夫人,也就是一开始的彼埃特拉内拉伯爵夫人,到最后的莫斯卡伯爵夫人,法布里斯的姑姑,是本作中塑造得最饱满的角色,比法布里斯的人物形象还要饱满。公爵夫人是很有魅力的女人,帕尔马亲王和后来继位的年轻亲王也为她倾心,甚至让莫斯卡伯爵费心帮助他的情敌脱困,只为了和她交往。公爵夫人其实是很讨人喜欢的角色,她对仆人很好,对真心帮助她的人都毫不吝啬地给予回报,所有跟随她的仆人都十分忠诚。由于法布里斯受到不公正的判决,她曾向帕尔马老亲王扬言要离开他的国家,她收拾行囊时,所有的仆人都忍不住落泪。公爵夫人也很会社交,尤其会举办宴会,给帕尔马宫廷增添了不少色彩。亲王不愿意她离开帕尔马,除了对她有爱慕,也因为她过人的社交能力。
公爵夫人思维缜密,尽管做了许多不道德的事情,是非观念也很明确。比如,她从来没有向法布里斯真正地表达过感情,到了故事的后期,她也的确对法布里斯没有爱情了,真心实意地爱上了伯爵,与他一同生活。在我看来,公爵夫人是会为自己相信的东西奋斗的人,为了捍卫自己的立场、尊严和爱人,她会笃定地做出任何决定。这类重情感的角色也有致命的缺点,那就是容易被情感裹挟和驱动。法布里斯被再次关进要塞,很有可能被毒死的时候,她发疯地跟亲王请求,还做出了她不能兑现的承诺。当然,歇斯底里也是情感真挚的体现。
如果要把公爵夫人比作塔罗牌里的一张牌,毫无疑问是三号牌「女皇」。女皇形象的阔绰、懂得社交礼仪和爱人的特质,都在她身上体现了出来;女皇在逆位的时候,也会展现出类似的歇斯底里。
莫斯卡伯爵对待公爵夫人非常专情,我倒是觉得他对法布里斯的态度更值得玩味。首先,就像所有年长者对年轻人的态度一样,他时常教育法布里斯,也能像朋友一样相处。不过,法布里斯很快发现伯爵没有很喜欢他,因为伯爵非常清楚公爵夫人对法布里斯的感情。
在第七章,也就是莫斯卡伯爵受到亲王伪造的信件之后,他一个人在房间里发了疯:
一个凶狠的念头就像痉挛似的支配住了伯爵:“当着她的面攮死他,然后自杀?”
然而,就算没有公爵夫人的直接要求,莫斯卡伯爵也一直在帮助法布里斯脱罪,尤其是在公爵夫人因为伯爵的无能和各种其他政治因素,决定与伯爵分手之后,伯爵也一直在想办法找突破点,当终于有了进展后还第一时间给公爵夫人写信。不过,总体而言,伯爵是一个谨慎且有智慧的人,懂得宫廷里的各种潜规则,也敢于追求自己的幸福。
克莱莉亚的人物形象很有意思,表面上是个有教养的女人,很老实,也有宗教信仰,但抑制不住内心强烈的情感。克莱莉亚不容易冲动,因为大部分时候她都在抑制自己的情感,想要做父亲的孝顺女儿,但这种情感太过强烈,她既不愿意见到法布里斯,又深知没有法布里斯她就没办法获得幸福。这种矛盾和纠结从她见到法布里斯的那一刻起就一直纠缠着她,使她的内心不得安宁。
克莱莉亚的内心纠葛其实很现代,她的痛苦源自「既要又要」,既要成全父亲,又要追随爱情。不过,这种情感的另一面是主体性的缺失,克莱莉亚听从父亲的意愿,也离不开另一个男人能给她带来的爱情体验,她也相信天主会给她救赎,但她从来不知道自己想要的是什么。这么看来。「既要又要」并不是一个人要得太多,而是这个人不清楚自己真正想要什么。
最后,克莱莉亚成了侯爵夫人后,用一个自欺欺人的说辞让自己的内心得到了片刻的安宁:虽然我发誓再也不见法布里斯,但只要我们在晚上不点灯见面,就不算违背诺言。这也很现代人。人们很多时候并不是在追求公平正义、道德或者名誉,而是在追求内心的自洽。 之所以说是「片刻的安宁」,是因为在法布里斯提出要让他们的私生子和他一起生活时,她最终还是没有拒绝,这害死了儿子,也让她自己痛苦地死去。
私以为,克莱莉亚是最能在当今的读者心里激起波浪的角色。
在这里,我想分析一下法布里斯和《红与黑》里的于连的异同。
于连是农民出身,但拉丁语背得流利,有学识,受到贵族青睐,维璃叶市长雇他来当家庭教师,他也借此爬上了贵族的交际圈。于连在被判决死刑时,在贵族面前发表了一长串演讲,表明他对这种阶级划分的鄙夷,并声称自己的灵魂是高尚的,是「天生的贵族」;而法布里斯,是世袭的贵族,除了神学书没读过别的书。他们两个都与一位年长的女性有过纠葛(维璃叶市长夫人和公爵夫人),在之后又与一位更年轻的女性陷入爱情(玛蒂尔德小姐和要塞司令的女儿克莱莉亚)。他们两个都有相似的「爱无能」。
不同的是,于连有很高的政治抱负。尽管他和法布里斯实际上都没有真正的信仰,但他们都走上了神学之路。于连进修神学是因为干出一番事业的热情,而他却没办法去投靠拿破仑参军(红与黑中的红代表军装上的红色,黑代表教士服装上的黑色),所以只能在私下保留他的崇拜,在神学道路上另谋出路。法布里斯进修神学,仅仅是因为…… 听话。这是莫斯卡伯爵的主意。可见,于连是一个更有野心的人,而这种野心也使得他失去了爱人的能力,他对地位比他高的女性的爱情,仅仅是他政治野心的投射。法布里斯这边也相似,他追求一位女性,仅仅是因为他觉得「应该这样做」「应该过这种生活」。法布里斯冒险回到帕尔马追求浮斯塔的时候,他的随从跟他说:
“老爷,咱们走吧,”路多维克一再对他说,“您根本没有爱情;我看得出,您冷静和清醒得不得了。”
在上卷的末尾,法布里斯做出了许多荒唐的举动,最后仅仅得出了这样一个结论:我没有一颗爱人的心。
不过,下卷就用了很多笔墨描写法布里斯对克莱莉亚的痴迷。这里要插一嘴,我认为法布里斯对他姑姑公爵夫人实际上是没有爱情的,他只是对这位对他非常重要的女性有着不同于常人的情感而已,由于他不懂得爱人,所以误读了。我想思考的问题是:法布里斯有在下卷学会爱人吗?
如随从路多维克所言,爱情是令人不理智的,而法布里斯的确在下卷变得非常疯狂。我想,他的心的确是终于体会到了爱情,可克莱莉亚作为他第一段真正的爱情体验,并没有让他真正学会爱人,毕竟对方也是一个纠结痛苦、自欺欺人的人物,两个人也没有获得什么好下场。果然,就是要令人掏心挠肺的感情才有看点啊。
如果要从塔罗牌里选一张牌,我觉得法布里斯应该是一号牌「魔术师」,因为他的个人魅力非常强大,总是能够受人喜爱。不只是他姑姑对他的特殊的爱,他被关进要塞的时候,还能受到监狱看守的喜欢,看门狗也会和他玩。最后一章,一个有钱人家的女儿爱上了他这个副大主教(虽然她在故事里作用仅仅是为了让克莱莉亚吃醋),因为他讲道十分有智慧。他当了副大主教之后,也有人翻出他年轻时投靠拿破仑的故事,不过被塑造成了「他曾是一位军官」。写到这里我也想提一嘴,我觉得塔罗牌里的「魔术师」和「教皇」是相似的,但前者更聪明、圆滑、会说话,后者则更像是深耕于神学领域的。
噢,说他像「魔术师」还有一个点,那就是有些不顾他人感受,做事几乎是随心而动的。
想吐槽一下,司汤达的数学好像不太好的样子。译者写的脚注里,有好多都是在纠正「这里实际上是过去了 9 年,而不是 7 年,司汤达算错了」这种问题。
这本书读了一个多月,终于在 2026 年的一月结束之前读完了。今年的读书目标是 40 本,没想到第一个月就只读完了一本。不过本身就是一部有些难啃而且有五百多页长的小说,也能理解。司汤达有在书中简短提及伏尔泰和卢梭,大概可以以此为线索,探索更久以前的法国文学作品。
回见。
2026-01-28 18:07:29
PaaS 软件用了太久,导致自己对部署网站这件事产生了很多不切实际的想象,总感觉不使用 Vercel、Netlify 和 Cloudflare Pages 就不够现代。实际上,静态网站就是一系列 HTML 文件和必要的静态资源而已,即便是用 Hugo 和 Hexo 等静态网站生成器,得到的也是相同的文件。这些文件如果被用户下载到本地,用浏览器直接打开,也能够正常浏览,毕竟不涉及任何服务端的计算,那为什么还要把事情弄得这么复杂呢?
PaaS 的意思是平台即服务(Platform as a Service),可以把平台理解为开发和运行应用的基础设施,PaaS 提供了这些设施,降低了开发者进行运维工作和开发过程中部分工作的复杂度。比如,你编写了一个 Web 应用,通常情况下你需要购置一台服务器,然后配置应用的运行环境、配置 Web 服务器和反向代理、申请 SSL 证书并管理续期、设置防火墙和其他必要的安全措施、维护数据库、维护依赖等等,当应用的代码更新之后,你还需要重新部署。
PaaS 能让开发者做到,直接提交代码库给平台,应用就能跑起来并且运转良好,并且往往具有持续集成和持续部署(CI/CD)的能力,使得新的代码在推送到仓库主分支过后就能自动构建并部署应用。
与之相似的架构是 Serverless,也就是无服务器架构。这两种架构其实都极大地弱化了后端的存在,不过 Serverless 更偏向通过事件触发的云函数,而非随时在线的常规 Web 应用。这里略过不谈。
使用 Vercel、Netlify 和 Cloudflare Workers/Pages 这样的服务构建和部署静态网站是可行的,并且非常简单,一般来说步骤是这样的:
简单来说,你只需要把代码准备好就行,几乎不需要做任何运维操作。
问题在于,部署一个静态网站并不复杂,运行脚本构建好网站之后,将构建产物上传到一个可以分发文件(静态资源)的服务器上就好了。这个服务器甚至不需要有计算能力,可以是对象储存桶。印象中 Vercel 会把应用部署到他们的边缘网络(edge network)上,所谓「边缘」,指的是靠近端系统(end system)也就是用户的网络,网络的边缘部分离用户最近,所以响应速度往往很快。对于静态网站而言,这跟 CDN(内容分发网络)差不多。
所以,如果不用 PaaS,能把这个过程自动化吗?
由于我只使用过 GitHub Actions 和 Forgejo Actions,所以这里只谈这两个。Forgejo 的 Actions 几乎是和 GitHub 完全兼容的,语法也相似,所以在下文统称 Actions。
Actions 是自动化的工作流,有多种触发条件,最常见的是 push,也就是在有代码推送到远程仓库后触发;也可以限定分支,比如只有代码被合并到主分支之后才触发工作流。工作流可以执行命令,自然也可以执行用于构建静态网站的脚本,比如 npm run build 和 hugo --minify 等等。
需要说明的是,Actions 往往跑在与其他运行环境隔离的容器里,不能直接在宿主服务器上运行代码,并且工作流被执行完毕之后,往往会恢复现场,运行的结果不会保留在容器里,所以除了执行构建步骤,还要执行部署操作。如果要部署到一个对象储存桶,可以使用 rclone;如果要同步到远程服务器上,可以使用 rsync;如果要部署到 Cloudflare Workers 上,可以使用官方提供的 Wrangler 工具(不过你为什么不直接用 Cloudflare Workers 构建呢?)。
可以把工作流理解为「剧本」,程序员是编剧,把剧本交给 Actions 之后,它就会在规定的场合下按照剧本表演,结束后,会有人收拾舞台,一切都会恢复成原来的样子。如果要把舞台上的表演保留下来,就需要在表演进行时拍照和录像。
同样地,不同的剧目所需要的道具不同,就像不同的软件和脚本运行所需要的依赖项不同,戏班子上场的时候要把道具带上,编剧也要把道具在剧本里写清楚。Actions 一般跑在一个最小的、容器化、只有一个 CPU 核心的 Linux 系统上,以下称作 Runner。Runner 比较轻量,可以在 Docker 里运行,甚至给我一种 WSL(Windows Sub-system Linux)的感觉,上面预装的软件包很少,所以工作流里还要写安装依赖的步骤。
比如,构建一个 Hugo 网站,部署远程服务器上,工作流可能需要这样写:
npm install1
npm run build(运行 Node.js 工具,比如构建 UnoCSS)hugo --minify(构建网站)rsync ./public [email protected]:/path/to/site(部署网站到服务器)\由于在远程服务器上写入文件往往需要认证,最安全的方法是生成一对 SSH 密钥,在 rsync 之前先配置 SSH 私钥,保证目标服务器上有储存对应的公钥,这样才能正常访问目标服务器。此外,还需要在目标服务器上配置目录权限,保证工作流有权限写入。
具体的 workflow.yml 可能是这样的:
name: Build and Deploy Hugo
on:
push:
branches:
- master
jobs:
deploy:
runs-on: docker
steps:
- uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 0
- name: setup Go
uses: actions/setup-go@v5
with:
go-version: "1.25.1"
- name: setup Hugo
uses: https://github.com/peaceiris/actions-hugo@v3
with:
hugo-version: "latest"
extended: false
- name: build site
run: hugo --minify
- name: Install rsync
run: |
apt-get update && apt-get install -y rsync
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H ${{ secrets.DEPLOY_HOST }} >> ~/.ssh/known_hosts
- name: Deploy via rsync
run: |
rsync -avz --delete public/ \
${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:/home/eltrac/homepage/
之后在服务器上配置好 Web 服务器,将通过自动流程部署的文件公开就好了。以下是一个 Caddyfile 示例:
eltr.ac {
root * /home/eltrac/homepage/
file_server
}
这个方案的缺点非常明显,我用这个方法搭了一个网站之后就再也不想搭第二个了。
既然如此,我能不能直接在已经安装好运行环境的宿主服务器上直接运行构建脚本呢?构建产物直接在宿主服务器上生成。这样就可以省去与服务器通过 SSH 密钥认证并建立连接的过程,也不需要把大量的时间浪费在准备运行环境上了。
构建和部署静态网站之所以使用自动化工作流,是想要实现持续集成和持续部署,也就是在确认代码更改后就立刻更新生产环境的代码。一般来说,将代码合并到主分支就代表代码可以运行在生产环境了,自动化工作流可以以此为触发点,在有代码推送到主分支后就执行构建和部署步骤。在服务器上直接构建代码而不是用 Git 仓库的自动化工作流部署,最大的不足在于无法通过 git push 自动触发构建,但这个问题并非没有解决方案。
我不清楚 GitHub 的情况,Forgejo 可以在仓库设置里配置 Webhook,即在某些事件发生时向指定的 Webhook 端口发送 HTTP 请求。只要在服务器上开放一个 Webhook 端口,在接收到 HTTP 请求时拉取新代码并重新构建网站,然后配置 Forgejo 仓库在 master 分支收到新的代码推送时向这个 Webhook 端口发送 HTTP 请求,就能够实现 git push 后自动更新网站。宿主服务器不是容器化的最小运行环境,只要配置好了依赖和必要的软件,就不需要像自动化工作流那样,每次构建都需要重新安装依赖,更新可以在几秒内完成,体验上不会比动态网站慢。
具体而言,要先在服务器上配置好必要的工具。我使用 Hugo,也就需要在服务器上安装好 Go、Git 以及 Hugo。我的 VPS 安装的是 Ubuntu 系统,所以这里用 Aptitude 包管理器安装。
sudo apt update
sudo apt install golang-go
sudo apt install git
使用 Aptitude 安装 Hugo 的时候,我发现版本不是最新的,因此遇到了一些问题。所以我打算直接从源代码编译,先克隆仓库,然后编译,将编译后得到的二进制文件移动到 bin,测试能否在终端运行。
git clone https://github.com/gohugoio/hugo.git
cd hugo
go build .
mv hugo /usr/local/bin/
hugo version
其实也可以直接用 Go 安装,但我忘记当时遇到了什么问题,可能是没有把 Go 的 bin 添加到 $PATH,总之没能成功安装。
go install github.com/gohugoio/hugo@latest
hugo version
接下来准备构建网站的脚本。先把网站的源代码克隆到本地,安装好依赖之后,创建一个 .sh 文件
# 这是托管在我自己的 Forgejo 实例上的网站
git clone https://src.eltr.ac/eltrac/jar.git
cd jar
pnpm install # 安装 Node.js 依赖
vim build.sh
这个文件里,按照实际情况编写构建网站的步骤。由于需要被执行,所以要在开头写上 #!/bin/sh
#!/bin/sh
git pull # 先拉取最新的更改
pnpm unocss "**/*.{html,md}" -o assets/uno.css
hugo --minify
运行这个 Shell 脚本,测试能够正常构建网站。如果输出正常,就可以继续配置 Webhook 了。这里使用 adnanh 的 webhook 实现 ,这是一个命令行工具,能够读取 JSON 或 YAML 配置。以下是示例配置文件,用 JSON 编写。
# 先安装 Webhook
sudo apt-get install webhook
// hooks.json
[
{
"id": "build-hugo",
"execute-command": "/home/eltrac/jar/build.sh",
"command-working-directory": "/home/eltrac/jar"
}
]
执行 webhook -hooks hooks.json,API 就会运行在 :9000 端口上,我们刚刚配置的 API 路径是 localhost:9000/hooks/build-hugo。要允许公网访问,可以用 Caddy 配置反向代理。这里建议用 systemctl 运行和管理 Webhook 服务,具体步骤此处略去。
hooks.example.com {
reverse_proxy localhost:9000
}
然后,在 Forgejo 仓库配置 Webhook,当有代码被推送到主分支上时,就向我们刚刚配置好的 API 端口发送 HTTP 请求。运行在服务器上的 Webhook 收到请求后,就会运行我们刚才编写好的 build.sh,网站就会被重新构建。

在 Forgejo 仓库设置找到 Webhook

添加 Webhook
完成后,向 Forgejo 远程仓库推送更新,测试构建脚本有没有被触发。不出问题的话,你就能获得一个几乎能在 git push 一秒后更新的静态网站。
对于缺少经验的人来说,使用 PaaS 是最简单的,几乎去掉了维护一个网站所需要的所有运维技能,只需要关注写代码。不过,似乎大部分 PaaS 都只支持从 GitHub 和 GitLab 和拉取代码,或者自己手动上传代码,不支持和自托管的 Gitea 或者 Forgejo 实例集成。个人感觉,使用 PaaS 就像是在和恶魔交易,出卖自己的灵魂以换取便捷,代价是会被科技公司锁死在他们的平台上。
GitHub Actions 类的自动化工作流,想必是有用武之地的,但我认为并不适合用来部署网站。如果只是运行自动化测试或拉取数据这种操作,把工作流脚本和代码放在一个仓库里大概是合适的。如果某些操作需要大量的依赖项才能运行,那么自动化工作流的效率就会因为需要安装大量依赖而被拉低。
目前实践下来,个人认为最优雅的方案就是在服务器上直接构建,通过 Webhook 触发,这样就省去了部署的步骤。不过,如果没有一台 Web 服务器,也就用不了这个方法了。
印象中还有人会在本地构建,将构建产物使用 rclone 同步到储存桶,或者使用 rsync 同步到服务器上,我没有实践过这种做法,就不评价了。
其实我更喜欢使用 pnpm,但如果要在自动化工作流里使用,还要额外安装,所以这里就直接用 npm 了。 ↩︎
2026-01-26 10:15:25

from My Little Pony
来自《我的小马驹:友谊是魔法》第二季的一首歌曲,其实从歌曲的风格、质量和剧情的设计来看,就不难理解这部动画为什么如此受成年人喜爱了。
📃
我日常使用的终端模拟器是 Ghostty,这个项目在前不久正式成为了非盈利软件并接受捐赠(参见 第 58 期周刊 ),这周他们团队发布了 AI 使用政策,以下是我最喜欢的一句话:
Bad AI drivers will be banned and ridiculed in public.
坏的 AI 使用者会被封禁并且被公开羞辱。
📜
作者观察到了一个有趣的现象,在 macOS 上,亮色模式正在变得越来越亮。在以前,系统 UI 没有亮色模式和暗色模式的选项,那个时候的 UI 除了个别是黑色背景,大部分都偏向中性的灰色。到后来,UI 界面变得越来越白,暗色模式也随之出现。作者表示,由于 macOS 的某次更新让 UI 变得太亮,他一直保持使用暗色模式,尽管他有些想念以前的灰色 UI。
令我震惊的是,iOS 26 竟然开始使用 HDR 技术让一些元素变得比 100% 的白色还要更亮,简直是邪恶至极,又多了一个讨厌 iOS 26 和 macOS Tahoe 的原因。
白色的确会让人感觉更干净,黑色越深也让人感觉越酷,这种审美倾向使得亮色模式变得越来越亮。说不上有什么社会危害,但对我的眼睛非常不友好。作者建议,如果要设计 UI 界面,可以大胆一些,选择 50% 的灰色。我们的眼睛都会谢谢你的。
📜
比起「现在有了 AI 人人都能做副业当程序员开发软件」的说辞,我更喜欢这篇文章作者的态度。他 Vide Code 了一些解决自己需求的小软件,用来代替订阅制软件,并且承认他清楚这些软件的质量无法承受更多的用户,他不会将它们商用甚至推荐任何人使用。他仅仅是用来解决自己的需求而已。
不过,LLM 编程工具并不是免费的,Vibe Coding 实际上很烧钱。从我有限的经验来看,开发规模稍大一些的软件时,使用自然语言编程所耗费的精力比自己亲手写要多得多,描述需求就是很累人的,因为需要完成想法到指令的转换——说真的,为什么有人比起自己敲代码,会更愿意去做对接沟通和审查工作的事情? 所以,长期而言,我很怀疑作者能不能用这种方式维持自己的数字生活。对于其他想要做此尝试的人:作者本身就是软件工程师,只是没有编写 macOS 应用的经验而已,而 LLM 编程工具只是帮他补足了这方面的空缺。
📜
前几天折腾 NeoVim 插件和配置,经常接触到 LSP(Language Server Protocol)和 Tree-sitter 这两个东西。我感觉它们提供了类似的能力,但似乎略有不同。这篇文章解释了两者在应用层面的区别,读完之后有豁然开朗的感觉。
简单来说,Tree-sitter 常被用来实现代码语法高亮,它看起来像是一个语法解析器,但实际上是个「解析器生成器」——给它一门编程语言的语法描述,它就可以生成这门语言的语法解析器程序。比起使用正则表达式匹配语法结构,Tree-sitter 能够容忍语法错误——这很重要,因为在编写代码的时候,代码在大部分时候都处于「有语法错误」的情况。如果使用正则表达式实现语法高亮,编写代码的时候,屏幕上代码的颜色就会一直变化。一些插件也可以调用 Tree-sitter 来查询代码中是否存在特定的语法对象。
而 LSP 是一个开放的协议,是编辑器和语言服务器(Language Server)通信的协议。语言服务器从名称上来看,似乎就会做更多的计算和分析。Rust 的官方语言服务器名为 rust-analyzer,就是「Rust 分析器」的意思。编辑器里显示的错误和警告,例如未被使用的符号定义、不够现代的 for 循环写法等等,都是语言服务器提供的。还有代码补全、通过函数引用寻找定义、显示某个标准库函数的文档,也是语言服务器的能力。所以我不理解那些非要用 IntelliJ IDEA 写 Java 代码的人,真要伺候这门语言也完全可以换个好点的编辑器,IDEA 有超过一半的功能都可以用 Java 的语言服务器代替,另外一部分功能学会命令行也能用,而且更快,不会在臃肿的 GUI 迷宫里迷路。
如果用人类语言类比,我感觉 Tree-sitter 像是趣广泛的语言学爱好者,知道 à、en 和 dans 是法语里的介词,但不知道是什么意思。给他一个法语句子他能划分出句子成分,但是自己不会写也读不懂。天哪,这简直就是我。 语言服务器除了能读懂语法,还能指出用词不当和句式杂糅等问题,对一门语言有最官方和全面的了解。
从功能上看,LSP 优于 Tree-sitter,但后者速度更快,代码高亮和基础的语法分析应该用它。
在 Fedi 上表达了对评论系统的纠结, @lok 给我推荐了这个网站。简单来说,这是一个罗列了几乎所有开源、自托管评论系统的数据库,可以按标签筛选、搜索和按条件排序。
我不知道什么时候产生了技术栈偏见,看到了后端用 Java 写的就避而远之,看到 Node.js 也总想换别的,看到 Go 就像看到家人了一样。嗯…… 大概也不算好事。 不过,如果我一定要选的话,我大概会更偏向于使用用 Go 写的 Remark42。
如果你的博客也是静态网站,有使用新评论系统的打算,可以参考这个网站。
上周更新了邮箱地址到 [email protected],突然想到自己的 GPG 密钥对也应该更新,毕竟密钥上还用的是旧邮箱信息。一开始我不知道能够直接用 gpg --edit-key 增加 UID,所以生成了一个新的密钥对。正好,我之前的密钥一直是储存在本地的,日常使用也是直接使用主密钥签名,这并不是最佳实践,甚至可以说十分危险,新建一个「干净」的密钥也是好事。
开始讨论之前要简单解释几个名词。PGP 是 Pretty Good Privacy 的缩写,是一个非自由软件,用于加密、认证和签名,为通信和数据交换提供隐私保障,其原理是非对称加密算法——用户生成一对公钥和私钥,公钥可以加密信息,但是只有私钥可以解密,这种非对称性也能用于验证互联网上某人的身份。OpenPGP 是一个开放的协议,而 GPG(GNU Privacy Guard)是实现了这个协议的自由软件。在应用层面,OpenPGP 可以给 Git commit 签名以验证代码提交者的身份,可以签名或者加密电子邮件保证隐私,可以直接加密本地存储的文件以避免明文储存敏感信息…… 关于 OpenPGP 还有很多值得探索的,这里点到为止。
一般来说,OpenPGP 的最佳实践是离线创建一个只有签发(Certificating)能力的主密钥,并且储存在永远离线的介质上;同样在离线情况下,使用主密钥签发具有其他能力的子钥,比如签名(Signing)、认证(Authenticating)和加密(Encrypting),并且子钥的有效时间应该较短,可以每年或者每半年更换一次。原则上,只有需要签发新的子钥的时候,才应该使用主密钥,日常都应该使用私钥,并且主密钥应该从联网的设备上删除。
不过就算是私钥,一旦暴露也会对自己的数据身份造成威胁,应该有更安全的、不可导出的、兼顾存储安全和使用便捷性的解决方案。事实上也确实有,那就是实体密钥。最常见的实体密钥是 Yubikey,支持 OpenPGP、Passkey 和多种协议,但是国内买一个 Yubikey 实在是太贵了,要六七百。联邦宇宙上 有人推荐 了国产且开源固件的 Canokey,价格不到两百,而且支持的协议也完全够我用,于是我几乎没有犹豫就下单了。

我把加密子钥和认证子钥导入了 Canokey,把签名子钥留在了 MacBook 上,因为我需要频繁给 Git commit 签名,一直插着 Canokey 大概也不太合适。目前我的 GPG 密钥对信息是这样的。1
➜ ~ gpg --list-secret-keys
[keyboxd]
---------
sec# ed25519 2026-01-22 [SC] [expires: 2029-01-21]
2DF03B76B4588E9DE7A80FEF8CF13C15FE112E87
uid [ultimate] Eltrac (Eltrac's Master Key) <[email protected]>
ssb ed25519 2026-01-22 [S] [expires: 2026-07-21]
ssb> rsa3072 2026-01-22 [E] [expires: 2026-07-21]
ssb> rsa3072 2026-01-25 [A] [expires: 2027-01-25]
主密钥我离线储存在加密的 U 盘和离线硬盘上,有两份,目前大概还做不到异地备份,但暂时也足够安全了。
关于 GPG,我还有很多要探索的,就放在之后再谈吧。
这周我购买了 Contabo 的一台 VPS,安装配置了 Akkoma(一个联邦宇宙软件,Pleroma 的分支),迁移了自建的 Linkding 和 Forgejo,安装了 Uptime Kuma。我还迁移了博客的对象存储到 Cloudflare R2,你大概会觉得图片加载慢了一些。
详细的相关内容,可以阅读《 极客死亡计划书 III 》。值得在这里一提的是,我的 Uptime Kuma 站点名字叫作「How’s Eltrac?」(Eltrac 还好吗?),域名是 hows.eltr.ac ——这下这个 $45 的域名也算是玩回本了(并不)。
我开始讨厌 Giscus,但是我四处翻找都没能发现令我满意的评论系统,这真是静态博客最不可忽视的缺陷之一了。
最近在 Hacker News 上看英文文章的时候,突然开始羡慕英文博主能够大大方方地不添加评论区,因为他们可以把链接提交到 Hacker News 上,大家在这个网站上讨论页面内容,甚至有不少博主会引导访客到 Hacker News 上讨论。于是我在 Fedi 上发了一条 帖子 :
希望中文独立博客圈有类似 Hacker News 的网站,这样我就不用纠结要使用什么评论系统了,大家都在一个网站上讨论文章内容就好,还能把写作和社交这件事情很好地分开。
中文博客圈的 RSS 聚合网站和导航网站已经够多了,如果有一个类似于 Hacker News,能让大家围绕一个 URL 展开讨论,并且足够开放和自由的平台就好了!V2EX 和 LinuxDo 这样的论坛似乎很多人喜欢,可我也很难融入那里的氛围,而且也不是为了「讨论其他网站上的内容」而生的。
已经在考虑自建一个支持 ActivityPub 的中文 Hacker News 替代了。
如果你不熟悉 GPG 的命令行界面,其中 sec 表示私钥(Secret Key),而 ssb 表示子私钥(Secret Subkey);# 表示没有在本地设备上找到这个私钥,> 表示这个私钥储存在卡片上。 ↩︎
2026-01-24 23:36:43
嗯?这个系列好像快一年没更新了?
没错,上一次发布《极客死亡计划书》,还是在 2025 年的三月底,现在 2026 年的一月份已经快要结束了。 上一篇计划书 写了自己对写博客这件事情的一些思考,以及公开写作和私有笔记的区别,还介绍了「卡片」分区,不过这个分区已经被弃用了。
这次的计划书会更偏技术,反映了我最近关于「技术自由」的思考。
Fediverse 是 Federation(联邦)和 Universe(宇宙)的混成词,可以理解为一个巨大的分布式社交网络。所有人都可以建立自己的实例服务器,或者加入一个现有的服务器,就能和整个联邦宇宙网络中的居民互动。这些服务器运行的通常是支持 ActivityPub 协议的自由软件,常见的是 Mastodon、Misskey 和 Pleroma,以及各种分支。联邦宇宙一般用来代替大科技公司提供的网络服务,比如 Twitter、Instagram 和 YouTube 等。
在我看来,联邦宇宙解决了因数据和社交网络无法迁移而造成的被大科技公司「锁住」的局面,譬如,现代中国人很难离开微信的原因并非是微信的软件质量极佳,而是因为工作需要、已有的聊天记录和联系人无法迁移到其他软件,以及「身边人都用微信」。联邦宇宙提供了这样一种解决方案:我们不需要在同一个平台上,只要我们使用的平台支持同一种通信协议,我们就能够发信和互动。这其实和电子邮件的逻辑如出一辙:A 使用的可能是 Outlook,B 使用的是 Migadu,C 使用的是自己搭建的邮件服务器,A、B、C 的邮件服务器由不同的商家提供,底层的软件实现也不一样,甚至不在同一个国家和地区,但只要他们知道了对方的邮箱地址,就能够相互通信,因为几乎所有邮件服务都使用 SMTP 协议。如果某天 Microsoft 干出了什么令 A 作呕的事情(这样的事情难道还不够多吗?),觉得继续用 Outlook 是十分令人反胃的事情,A 可以随时走人,只要告知其他人他的新邮箱地址就好。如果使用自己的域名,只需要修改 DNS 记录,不需要更换地址。
联邦宇宙也是如此,几乎所有联邦宇宙实例都运行在开源的自由软件上,也几乎都支持数据导出。如果你看不惯某个软件,你可以随时换到安装了另一个软件的服务器上;如果你不太喜欢某个服务器的风气或者管理者的行事风格,你也可以带着你的粉丝和关注者列表走人(粉丝甚至不需要做任何操作,只需要一些简单的账号迁移步骤,他们就会自动关注你的新账号)。由于软件是开源的,如果找不到自己喜欢的软件,也可以自己 Fork——我目前使用的 Akkoma 就是 Pleroma 的一个 Fork。这也是开源软件的好处之一,你永远不需要妥协,遇到不爽就大喊一声「Fork it!」1,把软件变成你认为它应该成为的样子。 广告?售卖个人数据?追踪用户?违禁词?算法?不好意思,没听说过。人们在大公司网络上呆久了,尽管也时常讽刺、嘲笑和表达忧虑,但往往忘记了自己还有选择。
等等,你不是要讲博客的计划吗?为什么开篇讲了这么多联邦宇宙相关的内容?
别急,我能圆回来的。 2024 年年底我写过一篇题为《
我与社交媒体
》的文章,当时的我认为我可以用博客来代替社交媒体,因为博客完全满足了「发表想法,然后收到回应」的需求,而社交媒体往往是更短的信息载体,更喜欢用长文本持续记录的我,当时并不看好这种方式。而且,在当时的我看来,社交媒体和主流互联网上更容易爆发争吵,而独立博客之间所组成的「网络」反而在大多数时候显得风平浪静。
使用了快一个月的联邦宇宙之后,我甚至搭建了自己的 Akkoma 实例(名为 Eucalyptus ,欢迎来访),我显然已经投入了不少金钱和精力在上面了。那么,我对社交媒体的想法有改变吗?
很遗憾,没有。我仍然认为博客等长文本媒介是更好的表达平台,不过,在联邦宇宙上和网友互动的过程中,我有想清楚一些事情。原本我在正式加入联邦宇宙,重回社交媒体时,以为联邦宇宙会比由大公司网络组成的主流互联网要更和谐,但实际上并非如此:我仍然能见到不少的争吵,读到一些偏激且令人难以理解的观点,我仍然会因为浏览社交媒体而感到焦虑(这和我在上文提到的那篇文章中所写的,社交媒体对心理健康的影响相关)。显然,社交媒体的问题并没有因为其使用的技术的倾向而被修正,它在形式上就是有缺陷的(或者,一个更偏激的观点:人类就是有缺陷的,一群完全不同的人聚在一起就是会吵架),这与是否去中心化、是否开源、是否自由无关。
不过,我之所以选择继续留在联邦宇宙上,是因为我逐渐开始区分「智识交流」和「社交交往」。
约莫半个月前,在联邦宇宙上看到一条帖子,我认为发帖人有些不公正且不准确地对我喜欢的一个播客主播表达了批评,我感到了些许气愤。我本来想反驳,但使用社交媒体的几年经验阻止了我。我开始反思:我为什么会生气?
我当然知道,我应该接受并倾听自己不赞同的观点,避免陷入到信息茧房里。然而,我并没有真的倾听到对方,我只是看到了一个单薄的、没有支撑和阐述的「结论」——是的,这就是问题!人们在社交媒体这样的短文本媒介上,总是更喜欢给结论,而不在乎其他能够支撑论点的必要内容。我相信,如果对方给出详细的批评理由,我能够更好地理解对方,即便不能完全赞同,我也能看到对方的出发点。我不赞同你的观点,但我理解你为什么会那样想。 我意识到自己的愤怒,很多时候源于不理解——怎么会有人能做出这种事情?我真的搞不懂你!? 如果不理解某人的行为逻辑,在其他人眼里看来,就容易被解读为「蠢」甚至「坏」。
显然,短文本并不是承载阐述、论述和论据的好媒介。阐述是指描述清楚观点本身,社交媒体上简单的一句「我觉得 Markdown 很蠢」背后表达的可能是「Markdown 的标准不统一,在声称支持 Markdown 的平台上使用这种语法不总是能得到相同的结果,并且不支持下划线……」,但过多的留白使得发言者很容易被想象为一个不懂得纯文本之美的麻瓜,收到「你听起来像是只会用 Word 文档写东西的人」这类冷嘲热讽。
可怕的是,这种倾向于不阐述、不论述、不提供论据的表达方式,已经从社交媒体蔓延到现实生活中了。我的一个朋友跟我聊起佛教与精神分析的共同之处,他只是抛出了一些支离破碎的结论,并没有给出阐述让我理解,也没有提供论述和论据,这让我在得到碎片化的信息之后产生了「这像是一群学者没事做了硬搞出来的课题」的印象。我提出我的想法之后,他也没有进一步解释自己的观点(或者说他读到的结论),而是有些冷嘲热讽地说「这两个东西就是有联系的呀,我觉得很有意义啊」。我感到不舒服,直接走开了,没有继续对话。显然,这是不愉快的——为什么有深度的知识和观点给人带来的是这种不悦呢?
听《独树不成林》的主播树老师时常批评,如今从事哲学的学者总是喜欢用一些令大众难以理解词汇,或许是为了提高哲学的门槛,又或许是为了将「高尚」的哲学和「低俗」的大众文化区分开来。这点在译者上尤为明显,他们会把原文本身就非常通俗的文本翻译得文绉绉的。我发现,许多人会把理解的门槛故意抬高,以此来凸显自己的孤傲。前文提到的那个朋友,曾经跟我说过,他觉得能理解的人能理解,不能理解的人就是不会理解,不需要费心解释。然而,他从一开始就没能把想法表达得足够清晰,他把表达的责任转化为了听众的理解责任,以使得自己不需要清晰地组织词句,只是用有些决定论的想法定义那些不能够理解的人。在《 阈限思维 》这本书里,作者提到了一个观点,很多想法、事实、观点在一群人眼里是「显而易见」的,在另一些人眼里则不是,这是由我们的经验塑造的。假设所有人都和自己拥有相似的经验,同样的思维方式,把这种差异解读为「理解的无能」,何尝不是一种愚昧?很可惜,这样的思想无处不在。
我无意改变任何人,我只是意识到,我不应该以「智识交流」的要求规范「社会交往」。如果有些人就是希望在人群中说一些大家都听不懂的话,或者给人一些神神叨叨的建议,以此来塑造具有神秘感的人设,那就随他去吧!反正我不会和这类人做朋友。在社交媒体上也是如此,直接抛出结论是高效的,也能迅速收获和自己处于同一个「显而易见」群体的其他人的支持,而那些与自己经验和认知差异巨大的人,可以通通归类为「蠢」和「坏」,以此保持自洽。虽然我讨厌这种下意识的反应,但人的确应该保留一定程度的信息茧房,因为和与自身经验和认知差异巨大的人相处是非常费心费力的,人不应该每时每刻都暴露在自己不认同的观点里。
总而言之,现在我把社交媒体看作「社会交往」的一个媒介,把博客看作「智识交流」的一个媒介。我享受社交媒体给我带来的社交满足感,并且包容不理性、缺乏逻辑连贯性的言论,而且,那些观点即便没有充分的阐述,也能给我带来一些思考。我仍然坚信,真正的思考应该发生在写作当中,博客等长文本媒介是不可或缺的。
我曾经非常在意网页的加载速度,所以在不久之前,博客的图片等静态资源都是储存在又拍云上的,由他们的 CDN 提供加速服务。这是个国内的云服务商,所以要求使用备案域名。为此,我把早年前注册的一个 .cn 域名(出于某些考量,我就不在这里给出完整的域名了)提交了备案,并一直用它作为图床的加速域名。我对又拍云的印象其实很好,如果你选择使用国内服务,我会推荐他们。不过,我在今年一月决定注销备案并且弃用 .cn 域名。
我对智识交流的一个要求是「自由表达」和「尽可能少的审查」(审查是不可能没有的,就算没有制度上的审查,也会有舆论审查和自我审查),所以我一直没有把博客使用的域名 geedea.pro 提交备案。最近我意识到,即便我使用一个单独的备案域名,这个域名也与我是强绑定的。再者,使用 .cn 域名没有办法隐去真实姓名和联系信息,无法保障隐私。表达了我的想法之后,联邦宇宙上也有人建议我停止使用 .cn 域名。权衡之下,我注册了 eltr.ac 这个域名,并用它代替旧的 .cn 域名。
截止目前,我已经注销了旧域名备案,并完全停用了旧域名。现在我只有两个域名:geedea.pro 和 eltr.ac,前者用于我的博客,后者作为我在互联网上的身份标识(我的联邦宇宙账号是 @[email protected],我的个人主页是
www.eltr.ac
),之所以做这样的区分,也是出于区分「智识交流」和「社会交往」的考量。我把极客死亡计划视作我的作品,而不是我自己在互联网上的化身。
我也有在避免选择美国的服务商。一方面,我在 Hacker News 和 New Yorker 上读到了不少新闻报道,美国似乎把大科技公司变成了某种政治武器2,我并不希望美国的政治局势对我产生任何影响,我也希望通过自己作为消费者的微薄之力,通过自己的选择改变市场需求;另一方面,我更看好欧盟地区对隐私和用户数据的保护(GDPR3),我希望尽可能把服务都迁往欧盟地区,或者对隐私保护更加强硬的瑞士。
我先是把自己的邮件服务迁往了 Migadu ,一家瑞士的邮件服务商。之后…… 事情就变得复杂起来了。我只能说,自由的代价并不小。
首先是最难割舍的 Cloudflare。人称互联网大善人的 Cloudflare 位于美国旧金山,他们提供了十分全面的服务(DNS 解析、DDoS 防护、CDN、Serverless、CI/CD……),而且大部分服务都有完全足够个人用户使用的免费额度。这也使得互联网变得越来越中心化,不久前 Cloudflare 乱用 unwrap() 使得服务故障,大半个互联网都瘫痪了好几个小时。即便现在我想要迁出 Cloudflare,我也很难找到一个替代品,我甚至把又拍云的储存桶迁移到了 Cloudflare R2,因为实在是太好用了……
果然,免费的才是最贵的。其中一个代价就是我变成了麻瓜,我把 Cloudflare 当成了「网络安全的保障」,而不去思考抵御网络层攻击的原理。DNS 解析很容易替代,但是网络层的清洗、缓存,以及一个免费好用的 S3 储存服务,是需要花很多时间和精力才能找到替代方案的。如果不想思考和折腾,包括我在内的大多数人可能都会选择使用免费又省心的方案。
不过,Cloudflare 的 R2 储存桶有提供将数据存放在欧盟地区的选项,所以…… 也算是能用吧。我打算在未来半年点一点网络安全的技能,逐渐脱离对 Cloudflare 的依赖。
注销了备案,自然也就弃用了国内服务器。我在选购 VPS 的时候,看过了 CloudCone、RackNerds、Vultr 等著名的 VPS 提供商,在最后看上了 Contabo,一家位于德国的云服务提供商,他们以非常低廉的价格出售位于欧盟地区的 VPS。4 核 8 GB 只要不到四美元一个月,确定不是疯了吗? 据说 Contabo 经常超售,但从 TrustPilot 的评价和我目前的体验来看,其实并不差。我主要用这台服务器来跑 Akkoma(基于 Pleroma 这个资源占用很低的联邦宇宙软件)、Forgejo(用 Go 编写的轻量 GitHub 替代)和一些自托管 Web 应用,除了偶尔需要编译软件,对 CPU 的需求其实并不高,所以也还好。
值得一谈的是 Forgejo,我还安装配置了 Forgejo Runner,可以理解为 GitHub Actions 的替代品。目前
www.eltr.ac
就是用 Forgejo Runner 构建,并使用 rsync 部署到 VPS 上,算是实现了自有的 CI/CD(持续集成/持续部署),和使用 Vercel 和 Cloudflare Pages 一样,只要推送更新到
远程仓库
,就会触发工作流,自动构建和部署网站。所以,Cloudflare Pages/Workers 我已经找到了替代品,未来也会把这个博客从 Cloudflare Workers 上迁移走。
目前我还没有找到很好的位于欧盟地区的 S3 兼容对象储存服务。Scaleway 似乎不错,但是不支持 PayPal 付款,而我现在找到的支持 PayPal 的服务都有些超预算。所以,我暂时还不知道如何替代 Cloudflare R2。
所以这就是自由的代价之一吧:一切都要自己学习,一切都要自己理解,一切都要自己负责。对我而言,这其实非常令人兴奋。
将服务逐渐迁移至欧盟地区,除了远离纷扰,还有一个重要的原因是数据安全。GDPR 法案提供了严格的用户数据保护策略。此外,我也逐渐意识到自己需要将数据掌握在自己手上,而不是任由他们留在某个平台。拥有数据的一个先决条件是:数据有副本在本地,最好是本地数据优先,并且数据应该以开放的格式储存,例如纯文本、JSON 和 XML 等。
同样地,这是对麻瓜非常不友好的要求,大多数人其实没有管理数据的能力,或者没有办法离开数据封闭的平台或软件。短期内这样的软件选择会带来便捷,但长期来看,会被平台困住,留在平台上并不是因为软件质量,而是因为数据无法迁移或者迁移太困难。我在审视我博客的架构时,也发现了不少数据隐患——我的 Webmention 后端由 webmention.io 提供,所有的 Webmention 都在他们的数据库里存放着;我的评论系统使用 Giscus,数据存放在 GitHub 上。如果哪天 webmention.io 这个免费服务倒闭,或者 GitHub 发病,我的数据不都没了?
Webmention 和 ActivityPub 一样,是一个 W3C 标准,用于在万维网上相互提及。理想情况下,在一篇文章里提及一篇位于其他网站上的文章链接,目标网站就会收到通知,可能还会把提及此文的其他万维网页面链接显示在页面中。不过实际上,这个过程涉及到发送 HTTP 请求,要实现自动发送 Webmention 的话,还是有点麻烦的,不过接收 Webmention 就很简单了,只要在 <head> 标签里声明接收 Webmention 的端点,指向可以接收 Webmention 的 POST 请求的服务器后端就好了。
<link rel="webmention" href="https://webmention.io/www.geedea.pro/webmention" />
由于数据存放在服务器上,对于没有服务器的静态网站而言,一般的做法是:每次用户访问一个页面,网页都会通过 JavaScript 从 webmention.io 抓取这个页面的 Webmention 数据,如果有,就显示在页面中(有多少人喜欢了这篇文章、回应了这篇文章的链接……)。动态地加载数据其实很不优雅:首先,我崇尚「客户端极简主义」,需要在用户的设备上运行的代码应该越少越好;其次,我尽管没有特别在乎加载速度,但我关注网页的渲染速度和性能,如果用户已经加载了我的网站内容,却还要等待来自另一个网域的内容加载,我觉得是不合理的;再者,我使用的是 Hugo,一个静态网站生成器,我应该尽可能发挥 SSG 的优势,让数据在构建过程中就被写入页面中。
我的解决方案是,用 CI/CD 每隔半个小时从 webmention.io 的 API 抓取一次全站的 Webmention 并储存为 JSON 文件,Hugo 直接使用这个 JSON 文件里存储的数据渲染 Webmention。如此一来,数据就以开放格式储存在了本地,准确来说是代码仓库中,还享受了 Git 提供的版本控制能力。
有了这个先例,我又继续开发了 Hugo 渲染 JSON 和 XML 格式数据的能力。现在你可以访问 林卷 页面,你能看到直接从我的 RSS 阅读器中导出的 OPML 订阅文件被渲染为列表,以代替手动维护的链接列表;你还能看到同样通过 CI/CD 抓取到本地的来自 Linkding 的书签列表。尽管这些并非重要数据,但有意识地存储、使用和开发自己的数据,就是一种态度。
不过,还是那句话,自由是有代价的…… 接下来我不得不面对一个非常难啃的瓜——评论系统。
除了数据隐患,使用 Giscus 的体验并不美妙,虽然很方便,但 Giscus 的表单和评论展示都是在一个 <iframe> 内的,这使得数据交换和自定义 CSS 变得很麻烦。比如,以下是我获取评论数的代码。
giscusIframe.addEventListener('load', () => {
// receive message from giscus iframe
window.addEventListener('message', function(event) {
if (event.origin !== 'https://giscus.app') return;
const giscusData = event.data;
if (giscusData.giscus?.discussion) {
const discussion = giscusData.giscus.discussion;
const totalComments = (discussion.totalCommentCount || 0) + (discussion.totalReplyCount || 0);
// set comment count
commentCountEl.textContent = totalComments.toString();
// if no discussion found, set to 0
} else commentCountEl.textContent = '0';
});
});
HTML 结构也完全不受我控制,这使得开发 Giscus 的 CSS 样式变得很麻烦。由于这个 <iframe> 在 giscus.app 上而非本地,再加上每次加载还需要用 GitHub API 获取 Discussion 数据,加载需要很长时间,我不得不在 JavaScript 层面做额外的处理,使 Giscus 延迟加载,来保证页面速度。
无论怎么说,Giscus 都很不好伺候,而且还要求访客有一个 GitHub 账号,这不太合理。
我在选择自托管评论系统的时候,看过了主流的 Waline、Twikoo 和 Artalk,总觉得没有中意的。一方面,我不想维护一个单独的用户认证系统,我讨厌不必要的注册和登录,我也不希望我的访客忍受这一点。可是,另一方面,如果没有用户认证,就很难避免垃圾信息,而且人人都可以伪装成另一个人进行评论,毕竟要获取到某个人的邮箱地址并不是什么难事。等等…… 邮箱地址?
发信是很难伪装的,如果有 OpenPGP 签名的加持,邮件发送者的身份就可以确认了。电子邮件是开放的协议,人人都可以使用,信息交换的格式也是开放的纯文本,非常容易保存、迁移和管理。所以,有没有办法开发一种基于电子邮件的评论系统呢?
我在这里先留一个引子,关于评论系统的变更,大概会留到《极客死亡计划书 IV》来写了。感谢你读到这里,希望你也不要放弃自由和真理。回见!
关联「Fuck it!」(去他妈的);Fork 本身在这里是「创建软件分支」的含义。 ↩︎
参见: Every data centre is a U.S. military base ,还有一些别的,我没有找到链接。 ↩︎
General Data Protection Regulation,通用数据保护条例 ↩︎