Hyperuplink:像在 1998 年那样畅聊
介绍 Hyperuplink,一款现代化的互联网公告板软件,基于 HTML5/CSS3、100% 无 JavaScript,以单个二进制文件的形式交付,适配主流平台,无需任何运行时依赖,并使用 PostgreSQL。

在 2400 波特调制解调器刺耳的握手声、处于 Turbo 模式的电脑持续嗡鸣声,以及凌晨三点 CRT 显示器苍白的闪烁之间,互联网曾拥有一种在过去二十年左右已被彻底丢失的东西:灵魂。由真实的人们组成的社区聚集在公告板周围,页面一次请求即可加载完毕,普通的硬件就能将整个世界呈现在你的屏幕上,而不必占用你一半的 CPU 仅仅为了显示一个话题列表。如果你恰好亲历过历史上那段短暂而奇特的时光,你大概至今仍对那些日子怀有眷恋,还记得你发现的那些奇奇怪怪的网络角落、沉浸其中度过的漫漫时光,以及或许在途中结识的朋友。
Hyperuplink 就是我试图将那些回忆及其附带的情感装瓶,并倾注到一个在 2026 年依然说得通的东西里的尝试。它是一个现代化的互联网公告板,无需 Telnet,直接在浏览器中运行;在服务端渲染标准、现代的 HTML5 和 CSS;100% 无 JavaScript;以单个静态链接的二进制文件交付,没有外部运行时、没有解释器、没有 FastCGI、没有 /var/www、没有 node_modules,也不会在你的磁盘上散落任何零碎文件。它可以与单个 PostgreSQL 服务器或整个集群通信,可使用任意兼容 Redis 的缓存,并自带一整套怀旧与现代兼具的主题。
更重要的是,Hyperuplink 很有趣,它不把自己看得太重,它是为那些已经受够了与 phpBB 的运行环境纠缠、或受够了 Discourse 糟糕的 JavaScript 界面的所有人准备的论坛软件。没错,Hyperuplink 也真的会狠狠地抽打美洲驼的屁股。
===========================================================================
NOW DIALING ... :: CARRIER DETECTED
===========================================================================
█ █ █ █ ████ █████ ████ █ █ ████ █ █████ █ █ █ █
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █
█████ ███ ████ ███ ████ █ █ ████ █ █ █ █ █ ███
█ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █
█ █ █ █ █████ █ █ ███ █ █████ █████ █ █ █ █
::: A SUPER HIGH SPEED INTERNET BULLETIN BOARD AS SINGLE BINARY :::
===========================================================================
但是……为什么?
长话短说,对于那些一直在社区频道里潜水、或已经在早前的状态更新中读过背景故事的人来说,当时我想要为使用我正在构建的各种工具、程序和服务的用户搭建一个社区讨论论坛,却找不到一款我真正愿意忍受的软件。
我想找一个既像当年那些 old school 的 BBS 系统,又能让人们在现代浏览器的舒适环境中使用的网络论坛。我还希望它能够……
- 能够直接使用现有的数据表来验证用户,和/或……
- 支持简单的注册,最好能用XMPP JID 代替电子邮件地址
- 支持通过电子邮件、最好也能通过 XMPP 发送通知和回复
- 轻量,不会拖带一大堆运行时依赖
- 不要求用户启用 JavaScript
- 不会用一堆我可能永远都不会碰的管理功能把我淹没
- 主题定制相对容易,更重要的是易于长期维护
phpBB 显然是首选,因为它已经存在了几十年,而且与 Discourse 和 Lemmy 不同,它不会强迫访客启用 JavaScript。但 phpBB 是个功能过剩的庞然大物,安装和配置都很耗时,一旦算上它的扩展和运行时依赖,就会要求定期进行维护仪式,而坦白说我根本没有时间。至于 Discourse 和 Lemmy,我压根就不会考虑,因为它们在禁用 JavaScript 的情况下根本无法工作。我看过的其他所有软件,要么缺少我需要的功能,要么会带来类似的运行时麻烦,要么就需要我去 fork 并永远维护那个分支,才能获得我需要的几个功能。于是我做了那个理智、心理健康的选择——在去年年底开始自己写一套公告板软件。
技术絮叨
让我们用 Go!
在写下第一行代码之前,我坐下来权衡了那些常见的选项:PHP 配 Laravel、Python 配 Django、Elixir 配 Phoenix、Go 配 Fiber,以及 Zig 配 Jetzig。我甚至一秒钟都没有考虑过服务端的 TypeScript,因为 Node.js 和 NPM 生态就是一个充斥着彻头彻尾恶意软件的垃圾场,即便是像 Hyperuplink 这样故意搞得有点荒诞的项目,我也拒绝选用。
脚本技术栈让 Web 开发变得愉快,它们把繁琐的 HTTP、会话和表单处理都抽象掉了,让你能专注于正在构建的东西,但每一个都会在身后拖着运行时和维护负担。我对 Hyperuplink 的唯一目标,就是让业余管理员无需照看整个技术栈就能运行一个论坛。从管理的角度来说,我希望能偶尔更新一两个二进制文件就万事大吉,而不必去订阅什么 PHP 安全公告、邮件列表、GitHub 上的安全建议,还有 NVD,只为确保自己没有错过那成千上万个依赖中的某个关键 CVE。
Go 正好处于甜蜜点:一边是像 C、C++ 和 Zig 这样的底层编译语言,它们以开发速度为代价换来性能;另一边是像 PHP 和 Python 这样的解释型语言,数据结构用起来很爽,但运行成本高。决定性因素在于 Go 能编译成单个静态链接的二进制文件,你只需把它复制到任意 VPS 上就能直接运行。唯一的缺点是 Go 并非完全意义上的“Web 原生”语言,也没有像 Django 或 Laravel 那样能加速枯燥部分的框架,所以我在 Fiber v3 框架之上自己搭建了一个小型的Web 应用框架,然后以此为基础继续开发。
幕后原理
Hyperuplink 是单个静态二进制文件,在禁用 CGO 的情况下编译,可交叉编译到 Linux、macOS、FreeBSD、NetBSD、OpenBSD 以及一长串架构,因此部署无非就是把这个二进制文件复制到位。它原生支持 PostgreSQL、友好支持集群,并使用物化视图来优化读取性能。数据库结构迁移被嵌入到程序中并在启动时自动运行,这意味着没有外部迁移文件,升级应该就像直接启动新版本一样简单。
此外,兼容 Redis 的服务被用于缓存、会话和异步任务队列。头像、附件和自定义资源既可以上传到本地磁盘,也可以上传到任意兼容 S3 的对象存储(如 MinIO),这在水平扩展服务时非常有用。
Hyperuplink 在客户端零 JavaScript 运行,这意味着每个页面都是在服务端渲染的 HTML5 和 CSS,没有任何东西会记录你的光标如何又飘回那个争论菠萝到底该不该放在披萨上的帖子,仅仅为了收集你账号的数据。
说到这个,账号可以通过本地密码注册/登录,并可选 TOTP 双因素认证,但 Hyperuplink 也支持通过 OAuth 提供商登录,方便你把其他平台上的朋友拉过来。而对于觉得电子邮件太 老派 的任何人,注册和通知同样可以通过 XMPP 完成。至于授权,账号可以被分配到不同的组,并按版块设置权限,这样好东西就只对好人开放。
Hyperuplink 提供了一系列预置主题,其中一些拥有漂亮的复古美学,这要归功于 classic-stylesheets 项目。也有一些稍显现代的外观可供选择,而且每个主题的配色方案都可以互换,所以一个带有 Gruvbox 色调的 macOS 9 风格论坛是完全可以实现的。
公告板在帖子中支持 Markdown,提供头像和附件上传,为管理员提供举报和审核功能,界面还支持 i18n。
Hyperuplink 还自带 REST API,我认为它比 Lemmy 或 Discourse 暴露的 API 更友好,甚至还有自己的 TUI 客户端,并已正式集成到 Neon Modem Overdrive 中。
快速上手
Hyperuplink 在 tty.fail 上开发,并镜像到 GitHub,镜像站点负责构建和托管预编译的二进制文件和容器镜像(感谢免费的 CPU 算力!)。无论你决定以何种方式运行公告板,都需要一个可访问的 PostgreSQL 和一个兼容 Redis 的服务器,另外,如果你更愿意将上传文件存到本地磁盘之外,还可选一个兼容 S3 的存储。
原生部署
官方仓库包含了让你尽快上手所需的所有文档和配置,但基本思路很简单:只需从发布页面获取适用于你平台的二进制文件,放到你喜欢的任意位置,然后运行:
$ ./hyperuplink -c "file:///etc/hyperuplink.toml"
Docker / Podman
如果你更想一次性拉起整个技术栈,这里提供了一套完整的 Docker 和 Podman(无需 root!)配置。仓库自带了包含 PostgreSQL 和 Valkey 的 docker-compose.yml/podman-compose.yml,以及一个可选的 MinIO 配置:
$ docker compose up -d
Quadlets
Podman 配置能做到 Docker 配置所做的一切,只是无需 root 权限,而且除了 podman-compose.yml 之外,如果你更喜欢使用 systemd,还提供了一套 Quadlet 单元。
K8s
Kubernetes 也支持,一个带有几个副本的最小化 Deployment,再通过 Secret 传入配置,基本就足够了。由于上传可以使用 S3,Pod 可以保持无状态。
Gentoo
仓库中提供了 Ebuild,因此你可以在自己的 Gentoo……服务器上自行编译……大概吧。
NixOS / nixpkg
我尝试加入了 Nix 所需的一切,但说实话,我自己并没有在任何地方实际使用它,所以请把它更多地看作一个概念验证,而非积极维护的功能。如果你有意愿积极维护 Nix 相关的部分,欢迎联系我。
FreeBSD / OpenBSD / OpenRC / …
仓库中还包含了适用于 FreeBSD、OpenBSD、OpenRC 甚至 systemd 的 Service 定义所需的初始化脚本。
从源码构建
如果你想自行构建 Hyperuplink(需要 Go),同样非常简单:
$ git clone https://tty.fail/mrus/hyperuplink.git
$ cd hyperuplink
$ make build
自包含的二进制文件会生成在 ./build/hyperuplink, ready to be moved wherever you please.
注意: 好吧好吧,我撒谎了,被你抓住了。无运行时依赖并不完全准确,你可能正盯着一个拒绝让用户上传头像的论坛。原因在于 Hyperuplink 有一个运行时依赖,那就是 ImageMagick 的 convert 命令。服务必须能在其 $PATH 中找到这个二进制文件,头像功能才能正常工作。
至于为什么,说来话长,但长话短说就是图像处理很难,包括我在内,没多少人愿意为比如 WebP 压缩算法去用纯 Go 重新造轮子。由于我明确不想使用 CGO 以保留 Go 轻松交叉编译的优势,我决定调用 convert 二进制文件是最合理的做法。毕竟,如果你曾在系统上托管过任何与 Web 相关的东西,很可能已经装有 ImageMagick。
反向代理
无论你最终以何种方式运行该进程,都请将其置于一个负责终止 TLS 的反向代理之后,因为在 Mode = "production" 模式下,会话 Cookie 仅限 HTTPS,你肯定不想在 development 模式下运行论坛。此外,如果你希望对服务进行监管,仓库中已备好了前述的适用于 systemd、Gentoo 和 Alpine 上的 OpenRC,以及 FreeBSD 和 OpenBSD 上 rc.d 的服务文件。
EOF
Hyperuplink 以 SEGV License 开源,代码托管于 tty.fail,镜像和预编译的二进制文件则在 GitHub 上,其他所有你想了解的内容,要么可以在 hyperup.link 上找到,要么可以在其内置手册中找到(位于 Help -> Manual)。
如果这些听起来正合你意,欢迎来聊天室打个招呼,搭建好你的论坛后别忘了来秀一下,如果你愿意在开发或测试上搭把手,也请联系我,因为最好的社区永远是由真实的人、付出真实努力所构建的。
随机一篇博客
评论
登录后参与讨论