打开 selfdb.exe.xyz,页面上有一个按钮。点一下,后台执行的其实是一条 SQL 的 INSERT 语句——写入的目标不是某个数据库服务,而是正在处理这次请求的那个可执行文件本身。程序、网页内容、访客日志、按钮计数,全部挤在同一个 SQLite 文件里,谁都不是谁的附属品。
这是工程师 Farid Zakaria 对自己此前提出的 SELF(Structured Executable & Linkable Format)格式给出的第一个完整样例,项目叫 self-httpd。乍看像博客作者自娱自乐的思想实验,但检索能确认它是一个有 GitHub 仓库、有 DESIGN.md 设计文档、有 elf2self 和 self-exec 标准构建工具链的真实工程,不是一张演示截图糊弄过去的草稿。
一个文件怎么同时是程序又是数据库
SELF 的机制依赖 Linux 的 binfmt_misc:内核认出这是一个 SQLite 文件后,不直接执行它,而是先启动一个解释器,读出文件里存的程序段(segments 表),再跳转到入口点运行。程序拿到的 argv[0] 正好是自己的文件路径,于是可以在运行时用 sqlite3_open(argv[0]) 打开自己,边跑边查、边跑边写。
self-httpd 用三张表撑起一个网站:routes 存页面内容,visits 记访客,presses 记按钮点击。构建时先编译成普通 ELF,再用 elf2self 转换、灌入网站数据,跑起来之后,每一次访问和点击都以事务方式写回同一个文件。想改内容不用重新部署,一条 UPDATE 语句就是一次热更新,还能用 sqldiff 比对两个版本之间到底改了什么。
这套思路直接受 Justine Tunney 的 redbean 启发。redbean 也是单文件 Web 服务器,靠 Actually Portable Executable 加自解压 ZIP 实现跨平台运行,用 Lua 钩子处理请求。两者的分歧点在于:redbean 追求“到处能跑”,需要额外的 ZIP 容器;SELF 让数据库本身就是容器,用一条 SQL 语句代替脚本逻辑,换来的是“可以直接 SELECT 查询”的能力。作者自己也承认,SELF 在很多地方“不如 redbean 精巧”,只是用更简单的工具达到了相近的效果。
省掉的是文件系统,焊死的是风险边界
传统 Web 服务通常把程序、静态资源、日志、数据库分放在不同路径,/var、/tmp、/home 各司其职,拆分的好处是哪一部分坏了不影响别的部分。SELF 把这条边界拆掉了,好处是部署变成 scp 一个文件那么简单,坏处也很直白:数据库文件一旦损坏,程序和全部历史状态一起消失,原文和公开资料里都没看到对应的备份或容灾方案。
- 风险.多个 worker 同时向同一个 SQLite 文件写访客日志和按钮计数,虽然启用了 WAL 模式,但高并发写入场景下的锁等待和性能表现,目前没有被验证过。
另一个现实限制是平台依赖。整套机制建立在 binfmt_misc 拦截可执行文件这一步上,这是 Linux 特有的能力,redbean 的 APE 方案却能跨平台运行。原文提到,Linux 内核 VFS 维护者最近才为 binfmt_misc 落地了让 /proc/self/exe 指回原始文件的支持——换句话说,这条路线目前能跑,靠的是一个刚刚才在内核层面站稳脚跟的特性,离“随处可用”还有距离。
部署简单到一个 scp,代价是程序和数据共用同一条命门
谁会真的用它
系统编程爱好者和喜欢单文件部署的独立开发者,是这类项目最自然的受众——尤其是那些厌倦了容器编排、怀念 scp 一个文件就能上线的年代的人。但生产环境的团队短期内不太可能把它当真正选项:并发写入安全性、故障恢复、跨平台移植,三道关都还没有答案,项目本身也还停留在概念验证加一次线上演示的阶段。
内核层面对 binfmt_misc 的完善正在推进,这或许是判断这条路线能不能走远的更实际信号,比博客里的演示截图更值得盯着看。
