一直在用 Memos 记碎片想法,但官方 Server 要自建 VPS,维护成本不低。直到发现 FlareMo——一个把 Memos 完整跑在 Cloudflare Workers 上的开源项目,D1 存数据、R2 存附件、Vectorize 做语义搜索、Workers AI 做嵌入,全部免费额度内。部署地址 memo.uuunit.com,iOS 用 MoeMemos 客户端无缝接入。这趟从部署到客户端跑通踩了六个坑,值得记录。
坑一:孤立仓库不能 pull,只能 reset
仓库是从 GitHub 上拿到的快照,本地只有一个根提交,和上游没有共同祖先。这意味着 git pull / git merge 必然冲突,升级的正确姿势是:
git branch backup-v0.6-snapshot # 旧版快照分支
git fetch origin --tags
git reset --hard v0.14.0 # 目标 release tag
但 reset 会把 wrangler.jsonc 一并覆盖——本地真实 D1 ID(da6ec409-...)和 FLAREMO_PUBLIC_URL 全丢。所以升级前先备份本地敏感文件,reset 后重新应用数据库 ID 和公网地址,再 commit 一次防止下次更新再丢。
坑二:Vectorize 索引不会自动创建
新版引入了语义搜索,上游 wrangler.jsonc 里声明了 Vectorize binding,但不会自动建索引——不手动创建,部署直接失败:
unset CLOUDFLARE_API_TOKEN CF_API_TOKEN # 必须 unset,否则走 API token 模式不是 OAuth
npx wrangler vectorize create flaremo-memos --dimensions=1024 --metric=cosine
npx wrangler vectorize create flaremo-memories --dimensions=1024 --metric=cosine
维度必须和嵌入模型匹配(默认 qwen3-embedding-0.6b = 1024 维 cosine)。还有一个隐藏雷:Vectorize V2 的 returnMetadata 参数要求字符串枚举 "all" | "indexed" | "none",上游代码传的是 V1 的布尔 false,语义搜索直接 500 Internal server error,要手动改成 "none"。
另外存量 memo 不会自动进向量库,只有新写入才索引。要回填,得把存量 memo 全部插入 embedding_tasks(operation=‘index’, status=‘pending’),触发任意请求让 waitUntil 跑 dispatch,之后向量传播还要等几分钟——Vectorize V2 的传播延迟是正常现象。
坑三:pnpm 无 TTY 装依赖报错
在无交互环境跑 pnpm install 会报 ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY,加一个参数即可:
pnpm install --config.confirmModulesPurge=false
坑四:MoeMemos 连不上,三轮排查
部署成功后,iOS 的 MoeMemos 填上地址和 PAT 却报「连不上」。先以为网络问题,一轮轮查才发现是三个叠加坑:
第一层:版本门槛。 MoeMemos 只支持 V1 版本 0.27.0~0.30.0(源码 SupportedMemosVersion.swift),而 FlareMo 上报的是自身版本 0.14.0,直接被判定 .unsupported 拒绝登录。
第二层:REST 路径缺失。 MoeMemos 探测版本走的是 GET /api/v1/instance/profile(Memos 官方 REST 路径),而 FlareMo 原生只实现了 Connect 风格路径 /memos.api.v1.InstanceService/GetInstanceProfile——那个 200 客户端根本不调。只改版本号不补 REST 路径照样连不上。
第三层:Swift 解码崩溃。 HTTP 全 200 了,客户端还是报 The data couldn't be read because it isn't in the correct format。原因是 D1 存的 createTime 带毫秒(2026-08-12T13:46:55.421Z),而 Swift 的 ISO8601DateFormatter 默认不支持小数秒,整个响应反序列化失败。
修复:两个补丁
补丁 A:兼容版本上报。 加一个 FLAREMO_COMPAT_VERSION 配置,只在 GetInstanceProfile 时上报 0.27.0,应用自身版本不变:
// env.ts 增加可选字段
FLAREMO_COMPAT_VERSION?: string
// connect-api 上报兼容版本
c.env.FLAREMO_COMPAT_VERSION?.trim() || FLAREMO_API_VERSION
// wrangler.jsonc
"FLAREMO_COMPAT_VERSION": "0.27.0"
补丁 B:时间戳秒级化。 在 DTO 层统一处理,所有 createTime/updateTime/expireTime 输出截掉毫秒——只修 instance/profile 一个端点不够,memo 列表、附件、reaction 全带时间戳:
const memosTimestamp = (ts: string) => ts.replace(/\.\d{3}Z$/, "Z");
顺手补了三个 REST 端点(instance/profile、用户设置、getStats)。getStats 还有个 Hono 路由坑:/users/:user:getStats 里的 :getStats 会被当成参数名吞掉,得在 /users/:user 处理器里用 endsWith(":getStats") 分流。
收尾
验证全绿:instance/profile 200 + version 0.27.0 + 时间戳秒级,MoeMemos 正常登录发 memo。整个项目最大的感触是:第三方客户端兼容靠的不是猜,是去读客户端源码——探测链路、版本范围、REST 路径全在 MoeMemos 的 GitHub 仓库里写得明明白白。自部署的乐趣就在这:任何不顺眼的地方,都能自己动手修好。