博客上线评论系统:Waline 自托管全记录

博客终于能留言了。这篇讲清三件事:Waline 这套评论系统的架构与我的 1G 小内存部署方案;头像背后的 Libravatar 是怎么工作的、它和 Waline 是什么关系;以及最实用的——怎么换掉你的评论头像。

#Waline#评论系统#Libravatar#Astro#nginx#systemd

好消息:本博客现在每篇文章底部都有评论区了,匿名就能留言,Markdown、表情、回复通通支持。欢迎测试、欢迎拍砖。

坏消息:没有。硬要说的话——为了这个评论区,我这台一共 1G 内存、已经被占掉一半的小 VPS 又多养了一个常驻进程。这篇记录整个接入过程,顺便把评论头像背后那套很多人没听说过的 Libravatar 机制讲清楚。

一、Waline 是套什么系统

静态博客本身没有后端,评论这种「读写数据」的功能必须外挂。Waline 就是干这个的开源方案,拆开看是标准的三层:

  • 客户端@waline/client):一个挂在页面上的 JS 挂件,负责渲染评论列表和输入框,向服务端发 REST 请求。约 50KB gzip,对静态站来说是唯一需要加载的额外 JS。
  • 服务端@waline/vercel):一个 Node 服务(ThinkJS 框架),提供评论的增删改查 API、登录注册、反垃圾审核,还自带一个 /ui 管理后台——审核、删评、置顶都在里面点点点。
  • 数据库:可插拔,MySQL / PostgreSQL / MongoDB / SQLite 都行,还支持 LeanCloud 这类 BaaS。

它的默认玩法是「白嫖流」:服务端部署到 Vercel,数据放 LeanCloud,全程不花钱。但这条链路在国内访问不稳,而且数据攥在两个第三方手里。我有自己的服务器,所以走全自托管:数据库直接用 SQLite——个人博客的评论量,一个单文件数据库绰绰有余,备份就是拷走一个 .sqlite 文件。

二、我的部署:1G 内存怎么抠

服务端跑在 comment.detached.online 子域上,链路很短:

浏览器 → nginx(TLS 终结)→ 127.0.0.1:8360 Waline(裸 Node)→ SQLite 单文件

这台机器内存只有 1G,部署方式是抠着选的:

  • 不用 Dockerdockerd + containerd 两个守护进程常驻就要吃 100MB+,为一个评论服务不值。
  • 不用 pm2:pm2 的守护进程本身就是个 Node 进程,等于为了看管一个 Node 多养一个 Node。
  • 用 systemd 直接管:反正它一直在跑,日志、开机自启、崩溃拉起全都有,零额外开销。

服务单元里再上两道内存保险:

ini
ExecStart=/usr/bin/node --max-old-space-size=192 node_modules/@waline/vercel/vanilla.js
MemoryMax=300M

--max-old-space-size 压住 V8 的堆水位,MemoryMax 是 systemd 层的硬上限——真泄漏了就杀掉重启,不许拖死整机。实际跑起来稳定在 30MB 上下,比想象中乖多了。

前端这边,本博客信奉「默认零 JS」,评论区也不能坏了规矩:@waline/client 走动态 import,用 IntersectionObserver 盯着评论区,滚动到附近才开始加载——你不看评论,就不为评论付流量。

两个坑,帮你踩过了

  1. JWT_TOKEN 必须配。自托管文档里存在感很低的一个环境变量,漏了的症状极具迷惑性:注册请求正常写库,但签发登录令牌时 500(secretOrPrivateKey must have a value),前端只会笼统地报「账号密码错误」。随机生成一串长密钥填上即可。
  2. SECURE_DOMAINS 记得设。来源域名白名单,不设的话任何网站都能调你的评论 API。上线稳定后加上自己的域名(调试期可以把 localhost 一起放进去,本地开发才连得上)。

三、头像哪来的:Libravatar 工作原理

发第一条评论时我就纳闷:评论框只有昵称、邮箱、网址三个字段,没有传头像的地方,但评论发出去居然带了一个独一无二的头像。它是哪来的?

答案藏在 API 返回里,头像字段是这么一个 URL:

https://seccdn.libravatar.org/avatar/cef99007848b8e04732c44aa127deaf9

这就是 Libravatar——一个「头像即服务」的开源联邦项目。它解决的问题很古老:全网这么多论坛、博客、评论区,每个都单独传一次头像太蠢,能不能把头像绑在邮箱上,一处设置、处处生效

查询协议:邮箱的哈希就是你的门牌号

工作原理简单到优雅:

  1. 把邮箱地址转小写、去空格,算一个 MD5(或 SHA256)哈希;
  2. 把哈希拼进 URL:https://seccdn.libravatar.org/avatar/<哈希>
  3. 这个 URL 直接就是一张图——服务端查库:这个哈希对应的邮箱注册过头像吗?注册过就返回那张图;没注册就回退查一次 Gravatar(老牌的同类服务,所以已有 Gravatar 头像的邮箱在这里也能直接显示);都查不到,返回默认占位图。

还可以挂查询参数:?s=200 控制尺寸,?d=identicon 让查不到的邮箱返回一个由哈希生成的几何图案(还有 monsteridretro 等风格可选),而不是千人一面的占位图。

隐私上有个细节值得一提:页面源码里只出现哈希,不出现邮箱明文——Waline 特意把「邮箱 → 头像 URL」这步放在服务端做,爬虫扒不到你的邮箱地址。当然,MD5 挡不住针对性的字典碰撞,只能算「防君子」的遮掩。

它和 Gravatar 的本质区别:联邦

Gravatar 是 WordPress 母公司的中心化服务,全网头像都存在它一家手里。Libravatar 是它的自由软件替代品,最有意思的设计是联邦机制:如果你拥有自己的域名,可以发布两条 DNS SRV 记录:

_avatars._tcp.example.com     → 你自己的头像服务器(HTTP)
_avatars-sec._tcp.example.com → 你自己的头像服务器(HTTPS)

严格实现协议的客户端查 user@example.com 的头像时,会先查这两条 DNS 记录——存在的话,请求就发给你自己的服务器,libravatar.org 全程不参与。头像数据的主权可以完全收回自己手里,这是中心化的 Gravatar 做不到的。

(诚实地说:Waline 没实现 SRV 查询这步,它是直接拼 seccdn.libravatar.org 的 URL。联邦是协议的能力,不是每个客户端的现实。)

Waline 和 Libravatar 是什么关系

一句话:松耦合,Libravatar 只是 Waline 的默认头像图源。Waline 服务端拿到评论者邮箱后按上面的协议拼 URL,仅此而已。两处都可以覆盖:

  • 站长可以用 GRAVATAR_STR 环境变量把图源模板换掉,比如换成国内可直连的 Cravatar:https://cravatar.cn/avatar/{{email|md5}}
  • 注册用户可以在个人资料里设置自定义头像,存进 Waline 自己的数据库,优先级最高——设了它,邮箱图源直接被绕过。

所以头像的优先级链是:Waline 账号自定义头像 → 邮箱在 Libravatar/Gravatar 的注册头像 → 默认占位图

四、教程:怎么换掉你的评论头像

对号入座,两条路:

路线 A:匿名评论的访客 → 去 Libravatar 注册

  1. 打开 libravatar.org,注册一个账号;
  2. 添加你评论时用的那个邮箱,去收确认邮件、点验证链接;
  3. 上传一张图片,把它指派给这个邮箱;
  4. 等几分钟 CDN 缓存刷新(浏览器本地缓存也可能顽固,强刷一下)。

从此以后,你在所有用 Libravatar/Gravatar 的网站上留言,头像都会自动生效——这就是「头像绑邮箱」的全部意义。如果你已经有 Gravatar 账号,那什么都不用做,这里能直接显示。

路线 B:在本站注册过账号 → 后台直接设

登录评论区(输入框右下角有登录入口),进个人资料页,直接填一个头像图片的 URL 保存即可。这个头像存在本站数据库里,优先级最高,不依赖任何外部服务。

手头没有现成的图片 URL? 本站自带了一个头像专用图床:avatar.detached.online。流程:

  1. 打开图床,把图片拖进去上传(免注册,单张 ≤ 10MB);
  2. 上传完成后复制图片直链(以 .jpg / .png 等结尾的那条);
  3. 回到评论后台的「个人资料」粘贴、保存,头像立即生效。

两条规则:

  • 传完请在 30 分钟内完成第 3 步——这个图床只为评论头像服务,上传后 30 分钟内没有被设为头像的图片会被脚本自动删除;
  • 头像建议用正方形小图(几百 KB 足够),评论区里显示尺寸很小,传原图纯属浪费。

既然可以匿名评论,如何防止被伪装评论

头像跟着邮箱走,那别人填我的邮箱评论,伪装我的昵称,那岂不是能完美伪装成我?

对,任何人填你的邮箱,就能顶着你的头像和昵称发言,这是一切允许匿名评论的系统的固有性质。

但是真正伪造不了的信号只有一个:登录后发的评论会关联服务端的账号身份。也就是你只需要在评论里面登录,你就能获得这个邮箱的唯一身份,网站把这个身份渲染成头像右下角的认证角标,我实测的三种情况对比:

Waline 评论身份对比:匿名评论的头像没有任何角标;登录的普通用户头像右下角带绿色对勾;登录的管理员头像右下角带橙色齿轮标记

  • 匿名评论:头像干干净净,什么角标都没有——哪怕填的是站主的邮箱、顶着站主的头像,看起来很像但是这是伪装出来的;
  • 登录的普通用户:头像右下角有一枚绿色对勾
  • 登录的管理员:头像右下角是一枚橙色齿轮标记。

(截图里每条评论下面的「通过 / 待审核 / 垃圾」审核按钮是管理员视角才有的,普通访客看不到。)

所以在本站请认准:头像带橙色齿轮的才是我,只有昵称和头像的谁都可能是。我自己以后的评论都会登录后再发。

提示

若果大家比较在意自己被伪装的话,可以登录再发评论(注册只需要提供一个邮箱地址,不会真的验证是不是你,所以你不用担心你注册会暴露任何真实信息给我,可以大胆注册拥有一个身份!)


评论区已经开门营业,本文底下就有——来试试吧,留下你的第一条评论吧。

评论

评论加载中……