9.7 KiB
Toot Worker
一个运行在 Cloudflare Workers 上的单用户联邦宇宙发布软件,使用:
- Workers: HTTP API、ActivityPub 路由、Mastodon API 兼容层
- D1: 用户、OAuth 应用/Token、嘟文、媒体索引、关注、收藏、转发、通知、提及、话题标签、收藏夹、置顶、远端 actor 与远端嘟文缓存、出站联邦投递队列
- R2: 媒体文件
- KV: OAuth access token 会话(D1 同步保留,便于管理)
当前目标:让常见 Mastodon App 完成实例发现、创建 OAuth App、登录、上传媒体、发布嘟文、读取公开/家庭/话题时间线、收藏/转发/回复/收藏夹/置顶、查看通知、搜索、关注/取关远端账号,同时支持单用户实例与 Fediverse 双向联邦。
本地运行
npm install
npm run db:local
npm run dev
默认管理员用户名来自 wrangler.jsonc 的 ADMIN_USERNAME,当前示例值为 sun。密码必须通过 Secret 或本地开发变量提供:
生产部署:
wrangler secret put ADMIN_PASSWORD
本地开发可以创建 .dev.vars:
ADMIN_PASSWORD=change-me-before-deploy
PUBLIC_BASE_URL 必须在首次正式部署前改成你的稳定实例域名,例如:
"PUBLIC_BASE_URL": "https://social.example.com"
这个值会进入:
- Actor ID
- Object ID
- WebFinger 返回值
- 媒体和头像 URL
一旦开始对外联邦后,不要再改域名,否则远端会把你视为另一个实例身份。
媒体 CDN 加速(可选但推荐)
默认情况下,客户端拉取上传媒体走 ${PUBLIC_BASE_URL}/media/<key>,会经过 Worker 反代 R2,消耗 Worker 请求数和 CPU。生产建议:
- 在 Cloudflare R2 控制台给
toot-media绑定一个 custom domain(如media.social.example.com),开启公开访问 + CDN。 - 把该域名填进
wrangler.jsonc的MEDIA_BASE_URL,例如"MEDIA_BASE_URL": "https://media.social.example.com"。
设置后,Mastodon 客户端拿到的 media_attachments.url 会直接指向 CDN 域名,不再经过 Worker。Worker 自己的 /media/<key> 路径仍然保留,可以作为后备访问入口。
Cloudflare 资源
wrangler d1 create toot_db
wrangler r2 bucket create toot-media
wrangler kv namespace create KV
把返回的 ID 填回 wrangler.jsonc,然后应用迁移并部署:
npm run db:remote
npm run deploy
已实现接口
Mastodon API 兼容
实例 / 应用 / 鉴权:
GET /api/v1/instance、GET /api/v2/instancePOST /api/v1/apps、GET /api/v1/apps/verify_credentialsGET /oauth/authorize、POST /oauth/authorizePOST /oauth/token、POST /oauth/revoke
账号:
GET /api/v1/accounts/verify_credentialsPATCH /api/v1/accounts/update_credentials(自动联邦 Update Person)GET /api/v1/accounts/relationshipsGET /api/v1/accounts/:idGET /api/v1/accounts/:id/statusesPOST /api/v1/accounts/:id/follow、POST /api/v1/accounts/:id/unfollow(向远端发送 Follow / Undo)GET /api/v1/follow_requests、POST /api/v1/follow_requests/:id/authorize、/reject(stub,默认全自动接受)
嘟文:
POST /api/v1/statuses(支持media_ids、spoiler_text、sensitive、in_reply_to_id、visibility、language、poll[...]、scheduled_at,自动解析@user/@user@host提及和#hashtag,投递 Create 给 followers 与 mention)GET /api/v1/statuses/:id、DELETE /api/v1/statuses/:id(联邦 Delete 出站)GET /api/v1/statuses/:id/contextPOST /api/v1/statuses/:id/favourite、/unfavourite(联邦 Like / Undo Like)POST /api/v1/statuses/:id/reblog、/unreblog(联邦 Announce / Undo Announce)POST /api/v1/statuses/:id/bookmark、/unbookmark、/pin、/unpin(本地落库)GET /api/v1/polls/:id、POST /api/v1/polls/:id/votesGET /api/v1/scheduled_statuses、GET / PUT / DELETE /api/v1/scheduled_statuses/:idGET /api/v1/bookmarks、GET /api/v1/favourites(列出本地 bookmark / favourite)
时间线 / 通知 / 媒体 / 搜索 / 其它:
GET /api/v1/timelines/public(分页支持max_id/since_id/min_id,响应携带Link头)GET /api/v1/timelines/home(合并本地嘟文 + 关注的远端账号缓存嘟文,按时间排序)GET /api/v1/timelines/list/:idGET /api/v1/timelines/tag/:tag、GET /api/v1/tags/:name(话题时间线 + 话题元数据)GET / POST /api/v1/lists、GET / PUT / DELETE /api/v1/lists/:id、GET / POST / DELETE /api/v1/lists/:id/accountsGET /api/v1/notifications、POST /api/v1/notifications/clear、POST /api/v1/notifications/:id/dismissPOST /api/v1/media、POST /api/v2/media、PUT /api/v1/media/:idGET /api/v2/search、GET /api/v1/search(本地账号 / 嘟文 / 话题标签 + 跨站 WebFinger 解析acct:查询 + 粘贴远端嘟文 URL 按需抓取缓存)GET /api/v1/custom_emojis、GET /api/v1/filters、GET /api/v1/trends/tags、GET /api/v1/markers、POST /api/v1/markersGET / POST / PUT / DELETE /api/v1/push/subscription(存储 Web Push 订阅参数;实际推送投递仍需 VAPID/加密发送实现)
ActivityPub / 发现
GET /.well-known/webfinger?resource=acct:user@example.comGET /.well-known/nodeinfo、GET /.well-known/host-metaGET /nodeinfo/2.0GET /users/:username(含endpoints.sharedInbox、icon、image、publicKey)GET /users/:username/followers、/followingGET /users/:username/outbox(支持?page=true翻页)POST /users/:username/inbox、POST /inbox(共享 inbox)GET /objects/:id(嘟文已删除时返回Tombstone,HTTP 410)
入站 inbox 处理类型:Follow / Undo(Follow) / Accept(Follow) / Reject(Follow) / Like / Undo(Like) / Announce / Undo(Announce) / Delete(Note)(同时清远端嘟文缓存)/ Delete(Person)(同时清缓存与关注关系)/ Update(Person) / Update(Note) / Create(Note)(被关注的远端账号公开 / followers-only Note,或投递给本地用户的 Note 会写入 cached_statuses 给 home timeline 和通知使用,同时若 mention/回复本地用户会触发通知)。
出站 ActivityPub 投递会先写入 D1 outgoing_deliveries 队列,再由请求后的 waitUntil 和每分钟 Cron 触发器后台签名发送。失败投递会指数退避重试,最多 8 次后标记为失败并保留错误摘要。
安全
- 密码哈希使用 PBKDF2-SHA256 / 100000 iterations,带每用户随机 16 字节 salt,旧版
salt.hash哈希也能继续验证(便于无缝升级)。 - HTTP Signature(rsa-sha256)出站:签名
(request-target)、host、date、digest,自动计算 SHA-256 digest。 - HTTP Signature 入站验证:
- 强制要求
Date头,允许 ±12 小时时钟偏移 POST必须带Digest,且与 body 哈希一致- 通过签名头里的
keyId取得远端 actor 公钥(并在actor_cache表中缓存),拒绝body.actor与 actor cache 不一致的请求 - 验证
host头匹配请求 URL
- 强制要求
- 远端 actor 公钥与 inbox 写入
actor_cache,默认 24 小时 TTL,远端Delete(Person)会清理缓存与所有相关关注 / 收藏 / 转发 / 通知。 - 重放保护:已处理过的
activity.id会写入remote_activities,重复投递被静默丢弃(返回 202)。
数据库结构
迁移文件:
migrations/0001_initial.sql— 基础表(users / oauth_apps / oauth_codes / statuses / media / follows / remote_activities)migrations/0002_features.sql— 通知 / 收藏 / 转发 / 提及 / 话题标签 / actor 缓存 / 出站关注 / 删除墓碑 / 嘟文扩展字段(summary / sensitive / language)migrations/0003_bookmarks_cache.sql— 收藏夹(bookmarks)/ 置顶(pinned_statuses)/ 远端嘟文缓存(cached_statuses)/ OAuth Token 持久表migrations/0006_cached_status_metadata.sql— 远端缓存嘟文的可见性 / mentions / tags / 本地收件人元数据migrations/0007_polls_lists_push_scheduled.sql— poll / list / push subscription / scheduled statusesmigrations/0008_outgoing_deliveries.sql— 出站 ActivityPub 投递队列 / 重试状态migrations/0009_markers.sql— Mastodon 读位 markers(home / notifications)
重要限制
这是一个单用户可运行实现,不是完整 Mastodon 服务端:
- 只支持单管理员账号自动初始化,不开放注册
- 本地状态的可见性已做基础控制:
public/unlisted可公开读取; ActivityPub outbox 只暴露这两类状态private会按 followers-only 投递,本地读取限作者和本地关注者direct仍没有完整受众表,本地读取保守限制为作者可见,不应当作为完整私信系统使用
- 远端嘟文缓存从入站
Create(Note)、已缓存嘟文的Update(Note)和搜索远端嘟文 URL 时按需写入,不抓取历史 outbox - 远端缓存嘟文会保留正文、CW、语言、可见性、mentions、tags、本地收件人和附件; 互动计数、poll、card 等扩展信息不会完整恢复
- Web Push 目前实现订阅存储和 API 兼容,尚未实现 VAPID 加密投递通知
- Poll 当前只在本地 Mastodon API 中序列化和投票,不会联邦成 ActivityPub Question
- 媒体上传只支持
image/jpeg、image/png、image/gif、image/webp,单文件 10MB,单条状态的附件数量不做服务端限制; 头像和封面同样只按图片路径处理 - 没有实现接口级限流、反滥用或审核流;
follow_requests相关接口仍是 stub
参考
- Cloudflare Workers 绑定:https://developers.cloudflare.com/workers/runtime-apis/bindings/
- Cloudflare D1:https://developers.cloudflare.com/d1/
- Cloudflare R2:https://developers.cloudflare.com/r2/
- Cloudflare KV:https://developers.cloudflare.com/kv/
- Mastodon API:https://docs.joinmastodon.org/methods/
- ActivityPub:https://www.w3.org/TR/activitypub/
- HTTP Signatures:https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12