小胡日记HMILYLD

Pineboard:把论坛塞进 Cloudflare 的免费额度里

2020 年我把论坛换成了 Flarum,Docker 里跑 nginx + PHP + MySQL,配 SSL、配备份、配邮件,折腾了小半天(当时还写了篇笔记)。后来 VPS 到期续费,论坛也就慢慢荒了。

这次想给自己和几个朋友留个说话的地方,需求很简单:能发帖、能回复、能搜到以前的东西,最好别再让我养一台服务器。

于是就写了 Pineboard。

它是什么

一个通用的小论坛,不限定行业或主题。前台有版块、主题、楼中楼回复、标签、中文全文搜索、通知和邮件摘要;后台有站点设置、内容维护、用户与举报管理、审计日志和集成配置。

前后端同源跑在一个 Cloudflare Worker 上,没有独立的数据库服务器,没有单独的对象存储网关,也没有另起一个发信服务。

Pineboard 首页
论坛首页:版块、主题列表和置顶。

账单:0 元

这是我最想记下来的部分。整套东西用到的服务,全部落在免费额度里:

服务 免费额度 用在哪儿
Cloudflare Workers 10 万请求/天、10ms CPU/次 所有 API 与页面路由
Cloudflare D1 500 万行读/天、10 万行写/天、5GB 存储 帖子、用户、设置、FTS5 索引
Cloudflare R2 10GB 存储、100 万次 A 类、1000 万次 B 类,出站免费 头像、图片、附件
Cloudflare Turnstile 免费,验证次数不限 注册、发帖、改帖的人机校验
Resend 3000 封/月、100 封/天 验证邮件、密码重置、回复摘要
GitHub Actions 公开仓库免费 CI 与 tag 部署

唯一不免费的是域名。不过 hmilyld.com 早就买了,forum. 这个子域不额外花钱,所以边际成本还是 0。

另外 Workers 的静态资源请求不算 Worker 额度,前端那些 JS/CSS/图片走 CDN,基本不消耗每天那 10 万次。

免费额度反过来塑造了实现

写着写着发现,免费额度不只是“省钱”,它会实打实地约束架构。这部分比额度表本身有意思。

10ms CPU 跑不动 scrypt。 免费版每次请求只有 10 毫秒 CPU 时间,服务端做 scrypt/argon2 这种密码哈希根本来不及。最后的方案是把派生放到浏览器:前端 PBKDF2-SHA256 迭代 31 万次,把结果当“密码”提交;服务端只做一次 HMAC(密钥是 PASSWORD_PEPPER),再常量时间比较。CPU 开销压到几毫秒,弱密码也还是先在浏览器里被拉伸过一遍。

敏感词匹配不能用 Aho-Corasick。 那个算法建自动机要 200ms 左右,直接超预算;换成一个小 trie,几百个词构建不到 1ms。

D1 按行计费,所以写入要合并。 发一个帖子不只是插一行:主题计数、最后回复时间、FTS 索引、@ 提及、通知、审计日志,全都要动。这些塞进一个 batch 一次写完,读的时候也尽量只取必要字段。

Resend 一天 100 封,所以邮件得攒。 有人回复你的帖子不会立刻发信——每 5 分钟一次 cron,把待发通知按人合并成一封(每人最多 10 条),全局日预算默认 80 封,留 20 封给验证码、密码重置这类必须马上发的。退信和投诉走 webhook,命中就停发。

R2 出站免费,所以图片直接放 R2。 上传走预签名直传,浏览器把文件 PUT 到 R2,Worker 只签个名、不碰字节流;读的时候走 R2 的自定义域名。真出问题还有 Worker 直传兜底。

能缓存的都缓存。 站点配置在 isolate 内存里缓存 45 秒,主题页的 meta 走 caches.default 300 秒,sitemap 1 小时。省下来的都是 CPU 和 D1 读取。

部署

日常 git push 只跑 CI,真正上线要打 tag:

git tag v0.2.4
git push origin v0.2.4

GitHub Actions 依次做:生成 wrangler.site.jsonc → 跑 D1 migration → 校验三个必需的 Secret → typecheck + test → build → wrangler deploy → 请求 /api/health 冒烟。任何一步失败都不会上线。

备份也是免费的:一个定时 workflow 把表逐张导出成 SQL 压缩包,丢进 R2 的另一个桶。

链接

最后

免费额度当然不是无限的。真有一天 10 万请求不够用了,Workers 付费版 5 美元起,D1 和 R2 按量也就几美元,对一个论坛来说仍然便宜得可以忽略。

但至少现在,它每个月花 0 元,而且我不用半夜起来看服务器有没有挂。对我来说,这就够了。