Owncast 联邦(Federation)互操作指南:ActivityPub、WebFinger、NodeInfo 与 Featured Streams 协议全解析
【免费下载链接】owncastTake control over your live stream video by running it yourself. Streaming + chat out of the box.项目地址: https://gitcode.com/GitHub_Trending/ow/owncast
Owncast 是一款自托管的直播服务器,其联邦子系统让每个实例以单个 ActivityPub Actor 的身份接入 Fediverse:对外广播开播通知与图文帖,同时通过轻量级 Featured Streams(特色直播)目录协议相互罗列彼此的直播状态。本文基于仓库根目录的 FEDERATION.md 为主干,结合services/activitypub源码与持久化实现,系统讲解 Owncast 支持的标准协议、Actor 模型、活动类型矩阵、流状态信令(Offer/Leave)、引文帖(FEP-044f)、HTTP 签名与隐私模式,帮助 Fediverse 软件运营者和开发者理解并实现与 Owncast 的互操作。
Owncast 联邦的设计哲学
Owncast 的联邦遵循 FEP-67ff(FEDERATION.md 规范),其核心定位是:
- 每个实例以单个
Service类型 Actor(而非Person)代表自身及其直播; - 广播开播(go-live)公告和图文帖给粉丝(followers);
- 不呈现社交时间线,也不关注单个联邦账户;
- 服务器之间可以通过 Featured Streams 协议相互罗列彼此的直播状态。
这种设计使 Owncast 在 Fediverse 中更像一个"广播节点"而非"社交节点"。从代码结构看,联邦子系统在 services/activitypub/activitypub.go 中作为组合根(composition root)装配,串联了persistence、workerpool(投递队列)、outbox(出站)、inbox(入站)、jobs与controllers六个子服务,并向流/管理端暴露SendLive、SendPublicFederatedMessage、GetFollowerCount等小型 API。
支持的协议与 FEP 标准
Owncast 联邦层支持四类基础标准:
| 标准 | 用途 |
|---|---|
| ActivityPub(Server-to-Server) | 活动的发送与接收 |
| WebFinger | Actor 发现(acct:形式的资源标识) |
| HTTP Signatures | 出站请求签名与入站验签 |
| NodeInfo | 实例元信息描述 |
同时实现了三个 Fediverse Enhancement Proposals:
- FEP-67ff:本仓库根目录的 FEDERATION.md 本身即是该 FEP 描述的规范形态;
- FEP-f1d5:NodeInfo 在 Fediverse 软件中的应用;
- FEP-044f:尊重用户同意的引文帖(quote posts)。
Actor 模型:以 Service 类型暴露直播实例
每个 Owncast 实例对外呈现为一个ServiceActor。其 profile 构成(见 apmodels/actor.go 中MakeServiceForAccount的实现)包括:
icon:服务器 Logo / 头像;image:profile 横幅图(当前与icon为同一资源);summary:服务器描述,按配置原样输出(来自GetServerSummary());attachment:由PropertyValue对象组成的数组,承载社交链接与元数据(源码中通过addMetadataLinkToProfile追加Stream链接与各社交平台句柄,若社交链接恰好只有一条,还会额外补一条Owncast链接以规避单元素 attachment 序列化不成数组的问题);tag:描述服务器内容的标签(Hashtag,来自GetServerMetadataTags());manuallyApprovesFollowers:私密模式下为true(取自GetFederationIsPrivate());discoverable:恒为true。
Owncast不暴露following集合,该端点返回404;它不关注单个联邦账户,但会以目录方式"关注"其他服务器以构建 Featured Streams(详见下文)。
活动类型矩阵:Send / Receive 全景
FEDERATION.md 给出了完整的活动矩阵,逐项解读如下:
| Activity | Object | Send | Receive | 说明 |
|---|---|---|---|---|
Create | Note | 是 | 是 | 开播时与手动联邦发帖时发送;入站 Note 仅在有提及/回复时产生插件事件 |
Update | Service | 是 | 否 | 服务器 profile 变更时发送 |
Update | Person | 否 | 是 | 更新缓存的粉丝 profile 信息 |
Follow | - | 是 | 是 | 粉丝关注(私密模式排队待审);目录关注(Featured Streams)也走 Follow |
Accept | Follow | 是 | 是 | 接受入站关注;或收到对方接受我们的目录关注 |
Reject | Follow | 是 | 是 | 拒绝/移除粉丝;或收到对方拒绝目录关注 |
Undo | Follow | 是 | 是 | 入站取消关注;出站取消罗列某服务器 |
Like | Note | 否 | 是 | 可选展示在直播聊天中 |
Announce | Note | 否 | 是 | 可选展示在直播聊天中 |
Offer | - | 是 | 是 | Featured Streams 直播中信号 |
Leave | - | 是 | 是 | Featured Streams 离线信号 |
QuoteRequest | Note | 否 | 是 | 远程用户请求引用我们的帖子(FEP-044f) |
Accept | QuoteRequest | 是 | 否 | 帖子存在且允许引用时发送,result携带QuoteAuthorization印章 IRI |
Reject | QuoteRequest | 是 | 否 | 未知帖子、私密模式或引用被禁用时发送 |
关键限制:Owncast不会把入站Create(Note)作为社交时间线展示,也不会向外转发;符合条件的提及与回复只投递给服务器插件。入站活动的处理入口在 inbox 包内,每个活动类型对应一个 handler(create.go、follow.go、like.go、announce.go、quote.go、offer.go、leave.go、update.go、undo.go、accept.go、reject.go等),通过 inbox/service.go 分发给 worker。
Featured Streams:Owncast 直播状态目录协议
这是 Owncast 联邦中最具特色的部分,允许服务器以轻量目录方式罗列彼此的直播。协议基于标准 ActivityPub 活动,外加https://owncast.online/ns#命名空间下的一小组 JSON-LD 扩展属性。
关注(罗列)一台服务器
目录向目标服务器发送Follow,要求:
actor为目录自身;object为目标服务器的 Actor;- 携带扩展属性
https://owncast.online/ns#directory: true,用于区分"目录罗列"与"普通个人关注"。
目录关注始终需要运营者审批(即便在公开实例上)。目标服务器审批后回复Accept(Follow),拒绝则回复Reject(Follow);后续目标服务器可再发Reject(Follow)撤销罗列,目录方也可发送Undo(Follow)停止罗列。
从源码看,Owncast 作为目录方向外发出 Follow 时,通过 owncast_metadata.go 中的SetBasicOwncastMetadata写入ns#directory标记;而作为被罗列方收到携带该标记的 Follow 时,会在 apmodels/actor.go 的ActivityPubActor.IsDirectory字段中记录,并进入运营者审批队列。
直播中 / 离线信号
一旦关注被接受,Owncast 服务器只向其目录粉丝(而非普通个人粉丝)投递流状态活动:
Offer:直播开始时发送,之后由定时器周期性重发(StartStreamPingTicker/SendStreamPing,见 activitypub.go),保证目录状态新鲜;Leave:直播结束时发送(SendStreamGoingOffline),使目录立即将该服务器从"直播中"列表移除,无需等待过期清扫(staleness sweep)。
两者的object均使用发送服务器的基 URL(scheme://host),流元数据以 JSON-LD 扩展属性附加。这些扩展属性的键定义在 config/constants.go:
属性(https://owncast.online/ns#) | 含义 |
|---|---|
streamStatus | live或offline |
streamTitle | 当前直播标题 |
streamDescription | 直播或服务器描述 |
serverName | 服务器显示名 |
logoUrl | 服务器 Logo URL |
thumbnailUrl | 直播预览缩略图 URL(仅直播中存在) |
streamTags | 流标签数组 |
出站侧,SetOwncastMetadata在开播时自动附加thumbnailUrl({serverURL}/thumbnail.jpg)与streamStatus: live;入站侧,inbox/offer.go 的handleOfferInboxRequest会先校验该活动确为streamStatus == "live"的合法 Offer、再确认该服务器处于"正在被我们关注"的状态(未关注或pending/rejected的服务器直接忽略),随后调用UpdateServerStatus写入数据库。
元数据截断防护:远程元数据被视作不可信输入,存储前会做长度与数量钳制(见 inbox/offer.go):
- 流标题上限 300 字符;
- 流描述上限 2000 字符;
- 服务器名上限 200 字符;
- URL 上限 2048 字符;
- 标签最多 20 个、每个最长 100 字符,且按 rune(UTF-8 安全)截断。
对应测试见 metadata_clamp_test.go。持久化侧,联邦服务器记录按scheme://host基 URL 作为主键,存储于federated_servers表(仓库 persistence/federatedserversrepository 包,含UpdateServerStatus等操作),并记录"最后在线时间"与"最后状态更新时间"(可对照 server-federation-2.md 的设计要求)。
与其他软件的互操作
该协议是开放的,但两个方向目前能力不同:
- 入站(第三方作为目录罗列 Owncast):Owncast 接受任何携带
ns#directory标记的目录 Follow,并向其投递Offer/Leave,因此任何第三方 ActivityPub 实现都可以充当罗列 Owncast 直播的目录; - 出站(Owncast 作为目录罗列他人):目前仅限 Owncast 自身——在关注某台服务器以便罗列其直播前,Owncast 会先校验对方 NodeInfo 报告的
software.name == "owncast"且metadata.federation.featuredStreams >= 1(见 utils/nodeinfo.go 中的校验逻辑),因此不会罗列非 Owncast 服务器。
Note 对象结构与开播公告
Owncast 发出的帖子为Note对象,包含:
content:带链接标签的 HTML 格式文本;attachment:直播预览的Image对象(开播帖存在预览图时附带;源码在SendLive中优先选用preview.gif,其次thumbnail.jpg);tag:Hashtag与Mention对象的数组;sensitive:直播标记为 NSFW 时,开播帖为true(对应GetNSFW()配置);interactionPolicy:公开帖默认声明任何人都可引用(canQuote的automaticApproval指向公共集合),除非引用被禁用或服务器处于私密模式。
开播公告:直播开始时发送Create(Note),内容包含配置的开播消息(默认I've gone live!)、服务器配置的标签、观看链接以及可用的直播预览图。若运营者清空了开播消息,则不发送公告(SendLive中textContent == ""时直接返回,见 outbox.go)。标签使用Hashtag类型(Mastodon/toot词表),链接指向https://owncast.directory/tags/{tag}以便跨实例发现;此外代码还会自动补充#owncast标签,确保出现在 Owncast 搜索结果中。
引文帖(Quote Posts)与 FEP-044f
Owncast 实现了 FEP-044f 的被引用侧(quoted-server side),为远程用户引用 Owncast 帖子提供同意流程:
- 公开帖携带
interactionPolicy,声明自动批准任何人引用; - 收到针对本服务器所创作帖子的入站
QuoteRequest时,以Accept应答,其result为已存储的QuoteAuthorization印章的 IRI;该印章可在该 IRI 上被解引用,供第三方服务器验证引用已获批准(见 inbox/quote.go,印章通过apmodels.MakeQuoteAuthorization构建并存储,然后经requests.SendQuoteRequestAccept投递); - 对未知对象的
QuoteRequest、私密模式下或引用被禁用时的任何请求,均回复Reject; - 引用默认启用,运营者可关闭(配置项
GetFederationEnableQuotes()); - Owncast不主动创作引文帖,也不撤销已签发的印章。
寻址(Addressing)
公开帖的寻址结构为:
{ "to": ["https://www.w3.org/ns/activitystreams#Public"], "cc": ["{actor}/followers"] }Owncast 还可向特定 Actor 发送私信(用于回复互动),此时收件人放入to,并附带对应的Mention标签以兼容 Mastodon(实现见SendDirectMessageToAccount:先经 WebFinger 解析账户、解析 Actor,再将活动与 Note 改为直邮寻址)。
HTTP Signatures:出站签名与入站验签
- 出站:所有
POST请求使用RSA-SHA256签名,被签名头为(request-target)、host、date、digest;每次投递尝试都会生成全新签名。对应实现位于 crypto/sign.go:httpsig.RSA_SHA256算法、DigestSha256摘要,headersToSign按需追加digest。出站侧在 outbox.go 的sendToInboxes中为每个收件箱构建workerpool.Delivery,签名在每次持久化投递尝试前即时生成。 - 入站:活动以
HTTP 202 Accepted接收(见 controllers/inbox.go),随后异步验签。仅携带有效 HTTP 签名的活动会被处理;未签名或签名无效的活动被丢弃、不执行任何动作。 - 对 actor、outbox、followers 端点的GET 请求不需要签名。
私密模式(Private Mode)
私密模式下:
- Actor 的
manuallyApprovesFollowers为true; - 关注请求进入运营者审批队列(
GetPendingFollowRequests可查询待审列表); - 仅在人工批准后发送
Accept(RespondToFollow中原子地记录审批结果并将 Accept/Reject 入队投递,见 activitypub.go)。
屏蔽(Blocking)
Owncast 支持域名级与单个 Actor 级两种屏蔽。来自被屏蔽域名或被禁用 Actor 的入站活动会被直接丢弃而不处理;被拒绝或屏蔽的粉丝会从未来的出站投递中移除。管理端移除粉丝时(RemoveFollower),若该粉丝是目录,还会尽力投递一个Reject(Follow)通知其撤销罗列(见 activitypub.go)。
WebFinger:Actor 与直播流发现
Actor 发现通过/.well-known/webfinger?resource=acct:{username}@{domain}进行。处理器在 controllers/webfinger.go 中:联邦未启用时返回405,主机无法规范化返回404,resource参数畸形返回400,请求的主机与实例主机不一致返回501。
响应中的 links(构建逻辑见 apmodels/webfinger.go)包括:
| Rel | Type | 指向 |
|---|---|---|
self | application/activity+json | Actor 文档 |
http://webfinger.net/rel/profile-page | text/html | Web 资料页 |
http://webfinger.net/rel/avatar | 图片类型 | Profile 头像 |
alternate | application/x-mpegURL | HLS 流 URL |
其中alternate链接是 Owncast 的特色:客户端可直接从 WebFinger 发现直播流(HLS)地址,而不必先加载 Actor 文档。
NodeInfo:实例元信息
NodeInfo 发现入口为/.well-known/nodeinfo,NodeInfo 2.0 版本在/nodeinfo/2.0提供(见 controllers/nodeinfo.go:v2.Path = "nodeinfo/2.0",link rel 为http://nodeinfo.diaspora.software/ns/schema/2.0)。相关端点还有:
/.well-known/x-nodeinfo2:扩展格式;/api/v1/instance:Mastodon 兼容的实例信息;/.well-known/host-meta:WebFinger 发现。
在 Featured Streams 出站校验中,NodeInfo 是判断对方是否为 Owncast 实例(software.name)及其是否支持特色直播(metadata.federation.featuredStreams >= 1)的依据,同时metadata.federation.username还用于提取联邦用户名(utils/nodeinfo.go 的ExtractFederationUsername)。
互操作注意事项速查
- Owncast 使用
ServiceActor 类型,而非Person; - Owncast 不关注个人联邦账户,也不暴露
following集合,但服务器之间通过 Follow 构建 Featured Streams 目录; - 入站
Create(Note)不作为时间线帖子展示、也不向外转发,符合条件的提及/回复只投递给插件; Like、Announce互动可在启用后展示在直播聊天中(由 chat 的入站处理与聊天事件系统协作完成);sensitive标记表示整个流为 NSFW 内容。
相关文档与实现索引
- 协议总纲:FEDERATION.md(FEP-67ff 的实现描述)
- 联邦子系统组合根与公开 API:services/activitypub/activitypub.go
- 入站活动处理(Offer/Leave/Quote/Follow 等):services/activitypub/inbox
- 出站消息生产:services/activitypub/outbox/outbox.go
- 扩展元数据解析与写入:services/activitypub/apmodels/owncast_metadata.go
- 命名空间常量定义:config/constants.go
- 联邦服务器持久化:persistence/federatedserversrepository
- 设计历史与演进:docs/features/server-federation-2.md、docs/features/server-federation-4.md、docs/features/server-federation-security.md
- 入站活动持久化模型:models/federatedActivity.go、models/federatedserver.go
【免费下载链接】owncastTake control over your live stream video by running it yourself. Streaming + chat out of the box.项目地址: https://gitcode.com/GitHub_Trending/ow/owncast
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考