2026年8月26日 1 分钟阅读

MindooDB 实战:把 Local-first 协作数据库部署成“服务器只见密文”的自托管服务

tinyash 0 条评论

很多团队说“数据库已经加密”,实际指的是磁盘加密或数据库静态加密:应用服务器拿到查询请求后仍要解密,备份系统和拥有管理员权限的人也可能接触明文。对于离线应用、跨设备笔记和隐私敏感的协作数据,真正棘手的问题不是如何把数据库放进容器,而是能不能让同步服务器只负责保存和转发数据,却没有解密能力。

MindooDB 的切入点正是这里。它是一个 TypeScript 编写的端到端加密、Local-first 同步数据库:客户端先加密数据,再通过内容寻址存储交换缺失的加密条目。官方 README 还明确写着它处于 Beta 阶段,尚不建议用于生产环境。因此,下面把它当作一个值得验证的数据库架构和自托管实验,而不是已经完成安全认证的生产方案。

先理解它解决的边界

传统的“服务器端加密”通常保护的是存储介质:数据写入磁盘时是密文,但应用在查询、索引或备份时仍可能恢复明文。MindooDB 的设计则把密钥留在客户端,服务器接收的是密文条目。客户端可以在本地无网络创建或编辑文档,恢复连接后再同步。

这套模型由几个部分共同完成:

  • Local-first 副本:读写优先发生在设备本地,网络更像同步通道而不是每次操作的前置条件。
  • Automerge CRDT:并发修改按照 CRDT 的方式合并,减少“最后一次写入覆盖前一次写入”的粗暴冲突处理。
  • 内容寻址存储:客户端交换自己缺少的加密内容,服务端可以存储和转发,但不需要理解文档结构。
  • 签名变更与追加历史:README 将每次变更描述为数字签名,并提供可追溯的历史链路。
  • 细粒度密钥:不同文档可以使用命名加密密钥,按用户分享,而不是把整个租户交给同一把密钥。

这也意味着数据模型要更克制:不要把“需要服务端实时聚合的所有字段”都强行塞进客户端加密文档。可以把在线报表、全文索引和复杂筛选视为独立能力,明确哪些查询由客户端完成,哪些结果只返回经过最小化处理的派生数据。否则为了保留传统数据库体验,很容易重新引入一个拥有明文读取权限的旁路服务,最后只剩下存储层实现了端到端加密。

用 TypeScript 建一个最小数据库

官方 npm 元数据当前给出的版本是 0.0.54,要求 Node.js >=22.13.0。先创建一个实验项目:

mkdir mindoodb-demo
cd mindoodb-demo
pnpm init
pnpm add mindoodb

下面的示例取自项目 README 的 API 形态:创建租户、打开数据库、创建文档,再通过回调修改文档内容。

import {
  BaseMindooTenantFactory,
  InMemoryContentAddressedStoreFactory,
} from "mindoodb";

const storeFactory = new InMemoryContentAddressedStoreFactory();
const factory = new BaseMindooTenantFactory(storeFactory);

const { tenant } = await factory.createTenant({
  tenantId: "acme-corp",
  adminName: "cn=admin/o=acme",
  adminPassword: "admin-password",
  userName: "cn=alice/o=acme",
  userPassword: "user-password",
});

const db = await tenant.openDB("contacts");
const doc = await db.createDocument();

await db.changeDoc(doc, async (d) => {
  const data = d.getData();
  data.name = "John Doe";
  data.email = "john@example.com";
});

const ids = await db.getAllDocumentIds();
const loaded = await db.getDocument(ids[0]);
console.log(loaded.getData());

这段代码的重点不在联系人字段,而在生命周期:tenant 是权限与密钥上下文,openDB 打开逻辑数据库,createDocument 建立文档,changeDoc 让修改进入数据库的变更历史。示例使用内存内容寻址存储,适合单元测试和理解 API;它不会替代真正的持久化服务端。

自托管参考服务器

对于原型阶段,最实用的做法是把同步层和业务层分开验收:先确认一个文档能在两个客户端之间可靠复制,再逐步加入附件、权限和搜索。不要一开始就同时接入真实用户、外部身份系统和大文件上传,否则出现同步错误时很难判断是 CRDT 合并、授权配置还是传输层出了问题。

git clone https://github.com/klehmann/MindooDB.git
cd MindooDB
bash serversetup.sh
docker compose up -d
curl http://localhost:1661/health

初始化脚本会询问数据目录和绑定设置,生成服务器身份、密码文件以及 Docker Compose 覆盖配置。官方文档特别提醒:已有部署再次执行脚本时,应使用安全更新路径,而不是无条件重新初始化:

bash serversetup.sh --update
docker compose up -d --build

日常运维命令仍然是普通的 Compose 操作:

docker compose up -d
docker compose down
docker compose logs -f
docker compose up -d --build

参考服务器默认在容器内监听 1661。如果要由反向代理接入,初始化或更新时可以设置不同的 Host port,让外部端口映射到容器端口,而不是直接修改应用内部端口。健康检查只能证明服务能响应,不能证明客户端密钥、租户权限和恢复流程配置正确。上线前还应把备份分成两类:一类是服务器保存的密文数据,另一类是客户端和管理员持有的身份、密钥与授权材料。只备份前者,恢复后可能得到一堆无法解密的历史;只备份后者,又无法还原同步内容。把两者分别登记、加密保存并定期做恢复演练,才是这类系统真正的运维闭环。

运维时最容易忽略的文件

服务端部署完成后,数据目录中会出现服务器身份、配置、受信任服务器和系统管理员身份等文件。官方文档将 config.json 视为日常管理的关键文件,因为它承载 capabilities 配置:谁可以调用哪些系统端点。

身份工具也由仓库根目录的包装脚本提供:

./mindoodb-cli.sh identity:info server.identity.json
./mindoodb-cli.sh identity:change-password server.identity.json
./mindoodb-cli.sh identity:export-public \
  system-admin-cn-sysadmin-o-myorg.identity.json \
  --output ./system-admin.public-identity.json

这些命令揭示了一个重要运维事实:服务器身份和管理员身份不是普通环境变量。它们是需要备份、限制文件权限并纳入恢复演练的密钥材料。尤其不要把 ../mindoodb-data 目录直接打包到公开日志、CI artifact 或聊天记录中;“服务器没有文档解密钥匙”并不意味着服务器身份文件可以随意暴露。

它适合什么,不适合什么

MindooDB 适合用来验证以下场景:离线优先的团队知识库、需要跨设备同步的私有笔记、客户端持有密钥的内部工具,以及希望自托管同步层的原型项目。它的价值在于把“数据库服务”和“数据可读性”拆开:服务端负责可用性、存储和同步,客户端负责解密和本地体验。

它暂时不适合直接承担关键生产数据。第一,项目自己声明是 Beta,0.x API 可能变化;第二,加密数据库的搜索、索引、全文检索和 OCR 必然涉及功能与隐私之间的取舍,不能只看“服务器看不到明文”这一句宣传;第三,离线副本会扩大终端设备的安全责任,设备丢失、恶意扩展、调试日志和备份策略都要纳入威胁模型;第四,CRDT 能解决并发合并,却不能替团队决定谁有权修改数据。

实际落地时,建议先做一个小型验收矩阵:断网后创建文档,恢复连接后确认同步;用两个客户端制造并发修改,观察合并结果;检查服务端数据库和日志中是否出现业务明文;轮换管理员密码并演练身份文件恢复;最后测试撤销用户访问后,已有本地副本和后续同步分别会发生什么。还要记录测试所用的客户端版本、浏览器存储清理方式和网络恢复顺序,避免一次“看起来成功”的演示被误当成可重复的可靠性结论。只有这些问题有明确答案,才值得从实验项目走向更大的数据范围。

结语

Local-first 和端到端加密并不是给传统数据库加一个开关,而是重新安排客户端、同步服务器和密钥之间的职责。MindooDB 目前最值得关注的地方,是它把这套架构压缩成了可运行的 TypeScript 引擎、Docker 参考服务和明确的身份工具。与此同时,Beta 状态也要求我们保持克制:先验证数据流和恢复边界,再谈生产采用。

相关链接

发表评论

你的邮箱地址不会被公开,带 * 的为必填项。