Files
lxc-streamutils/README.md
T
2026-08-19 12:30:30 -07:00

248 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 多用户直播组件服务
这是一个多用户 Rust/Axum 直播组件后端。目前内建 `danmaku_overlay` 弹幕姬、`song_request` 点歌姬、
`gift_effect` 全屏礼物特效、`guard_effect` 大航海特效与 `gift_menu`
礼物菜单,后端已经按“直播源 → 强类型事件 → 组件实例”的方式拆分。镜像构建阶段会编译 React 前端,运行时由 Rust 同域托管控制台和 OBS 页面。
直播连接使用相邻目录中的 [`libilibili`](https://github.com/feliscafra/libilibili)
crate。它负责 Cookie/WBI
API、直播 WebSocket 认证、心跳以及 JSON、zlib、brotli 包解析;本项目的 provider
adapter 只负责把强类型 Bilibili 命令转换成稳定的领域事件。crate 尚未建模的少量兼容命令只在 provider 边界解析,原始包不会进入组件协议。
控制台和 OBS 展示的所有文案统一来自
`resources/i18n.toml`。匿名页面可以先使用浏览器偏好,登录后语言保存为账户级偏好,并实时同步到该账户的所有 OBS 组件。目前支持简体中文和英语。
## 文档索引
- [总体架构与事件流](docs/architecture.md)
- [Rust 后端模块](apps/server-rust/README.md)
- [React 控制台、OBS 与 PWA](apps/overlay/README.md)
- [组件开发指南](docs/components/README.md)
- [`danmaku_overlay` 弹幕姬](docs/components/danmaku-overlay.md)
- [`song_request` 点歌姬](docs/components/song-request.md)
- [`gift_effect` 全屏礼物特效](docs/components/gift-effect.md)
- [`guard_effect` 大航海特效](docs/components/guard-effect.md)
- [`gift_menu` 礼物菜单](docs/components/gift-menu.md)
- [WebSocket 实时协议](docs/protocol.md)
- [租户、Secret 与部署安全](docs/security.md)
- [完整配置注释](config.toml.example)
## 后端结构
`src/main.rs` 只负责配置、组装、监听和优雅退出,可复用核心由 `src/lib.rs` 导出:
- `live/` 中的 provider adapter 把 Bilibili 消息归一化为 `domain.rs`
的强类型事件;`SourceSupervisor` 保证每个用户的固定直播源只有一个 listener。
- `components.rs`
注册组件定义、设置 schema/迁移、事件订阅、纯投影和独立副作用 handler;新礼物展示或点歌姬不需要修改弹幕姬队列核心。
- `realtime.rs` 按 component
UUID 建立独立广播通道;账户直播事件先按 owner 提供给全部启用组件,再由组件订阅筛选,副作用 handler 不依赖 OBS
WebSocket 是否在线。
- `auth.rs`、`repository.rs` 和 PostgreSQL
RLS 共同实现身份、凭据、直播源、组件与 token 的用户隔离;`http_api.rs`
从服务端会话推导 owner,不接受客户端自报 owner ID。
## 部署
源码目录需要保持为相邻 checkout,Compose 会把 `libilibili` 作为独立 BuildKit 上下文传入镜像:
```text
source/
├── libilibili/
└── lxc-streamutils/
```
先复制配置并生成独立密钥:
```sh
cp config.toml.example config.toml
openssl rand -base64 32 # 填入 security.data_encryption_key
openssl rand -hex 32 # 可作为一次性 admin.password
openssl rand -hex 32 # 填入兼容字段 admin.session_secret
docker compose up --build -d
```
Compose 将 `config.toml` 只读挂载到 `/app/config.toml`,应用通过 `--config`
读取它。容器使用 host 网络,默认只在 `127.0.0.1:9719` 监听,供同机 Nginx 访问;不要把 `9719`
直接暴露到公网。
容器默认以 `1000:1000` 非 root 身份、只读根文件系统运行,并丢弃全部 Linux capabilities。请将
`config.toml` 设为 `chmod 600`,并确保容器用户可读;如宿主机用户不是 `1000:1000`,启动前设置
`APP_UID` 与 `APP_GID`。
`[database].url`
指向 PostgreSQL。应用启动时自动执行版本化迁移,数据库保存账户、一次性邀请码、TOTP 注册状态、可撤销会话、恢复码摘要、用户直播源、组件设置、OBS 令牌摘要和审计记录。租户表同时使用 owner 复合外键与 PostgreSQL
RLS 约束,HTTP
API 也始终从当前会话取得 owner,客户端不能自行指定其他用户。点歌队列和完整历史同样保存在租户隔离表中。账户语言偏好也保存在强制 RLS 的
`account_preferences` 表中。
`security.data_encryption_key` 必须是独立生成并妥善备份的 32 字节 Base64 密钥。TOTP
Secret 与每个用户的 CookieCloud
Key/密码会在写入 PostgreSQL 前用它加密;丢失或擅自更换该密钥会导致已有账户和 CookieCloud 凭据无法解密。
## 开发与代码质量
Rust 使用仓库内 `rustfmt.toml`,前端和文档使用固定版本 Prettier。推荐提交前执行:
```sh
cargo fmt --manifest-path apps/server-rust/Cargo.toml
cargo clippy --manifest-path apps/server-rust/Cargo.toml --all-targets --no-deps -- -D warnings
cargo test --manifest-path apps/server-rust/Cargo.toml --all-targets
npm --prefix apps/overlay run format
npm --prefix apps/overlay run format:check
npm --prefix apps/overlay run docs:check
npm --prefix apps/overlay run build
docker compose config --quiet
git diff --check
```
`.editorconfig` 统一换行、缩进和文件末尾规则;`.prettierignore` 与 Docker/Git
ignore 会排除依赖、构建产物、PNG/SVG 和包含真实 Secret 的 `config.toml`。`libilibili`
是独立 crate;协议解析能力应在该项目中维护,本仓库只维护领域事件适配。
## 首次初始化与登录
首次部署且数据库中尚无账户时,打开:
```text
https://danmaku.luoxingci.com/control/setup
```
填写系统管理员用户名和 `config.toml` 中的
`admin.password`,扫描页面生成的 TOTP 二维码,再输入验证器中的 6 位动态码完成初始化。页面只显示一次恢复码,请立即离线保存。
`admin.password` 只是“允许创建第一个系统管理员”的一次性 bootstrap proof:
- 它不会保存为账户密码,也不能用于日常登录。
- 第一个账户创建后,bootstrap 接口永久拒绝再次初始化。
- 所有账户都是 passwordless 账户,以“用户名 + TOTP”登录;丢失验证器时可使用一次性恢复码。
- 会话使用随机令牌,数据库只保存摘要,可在服务端到期或撤销。
系统管理员可在 `/control/invitations`
创建和撤销邀请码。每个邀请码只能使用一次,并指定一个尚未占用的初始 Bilibili
`room_id`。受邀用户完成注册后,可以在 `/control/account`
自行切换到其他尚未被账户占用的直播间;切换不会改变组件、队列或 OBS 地址。受邀用户打开注册链接,选择用户名、扫描自己的 TOTP 二维码并确认动态码即可完成注册。普通用户不能创建邀请码。
每个用户在 `/control/account`
配置账户级直播监听和自己的 CookieCloud 同步 UUID/Key 与密码。该配置只创建一条上游连接,并供账户下全部现有和未来组件使用。地址必须位于部署管理员配置的
`security.cookiecloud_allowed_hosts`
白名单。服务禁止 HTTP 重定向并安全编码 Key 路径,避免用户凭据导致服务端任意请求。服务会先验证凭据,再将敏感字段按用户独立加密保存;Cookie、Key 和明文密码不会返回浏览器,也不会与其他账户共享。CookieCloud 中需要存在 Bilibili
`SESSDATA`。
### 旧单用户配置迁移
以下 TOML 段现在只用于第一次系统管理员初始化时的一次性兼容迁移:
- `[connection].room_id`:绑定首个系统管理员的房间。
- `[cookiecloud]`:导入首个系统管理员的用户级加密凭据。
- `[obs].access_token`:导入首个默认弹幕组件的旧 OBS 令牌。
- `[overlay]` 与 `[overlay.events]`:迁移旧 `overlay_settings`,没有旧记录时作为首个组件的回退值。
迁移完成后,运行时以 PostgreSQL 中的用户、直播源、组件和凭据为准。以后修改这些旧段不会改变现有账户,也不会成为其他受邀用户的默认值;新用户使用邀请中绑定的房间,并在控制台填写自己的 CookieCloud。
## 必须使用 HTTPS
公开部署账号、TOTP 和会话功能时必须在 Nginx(或可信反向代理)终止 HTTPS,并保持
`security.secure_cookies = true`。下面是核心反代设置;证书路径按实际 Certbot 配置填写:
```nginx
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name danmaku.luoxingci.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
http2 on;
server_name danmaku.luoxingci.com;
ssl_certificate /etc/letsencrypt/live/danmaku.luoxingci.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/danmaku.luoxingci.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:9719;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
```
只有在本机、无敏感数据的纯 HTTP 开发环境中才能临时设置
`secure_cookies = false`。不要用该选项把登录页面直接发布到公网。
## 控制台 PWA
通过 HTTPS 打开 `/control/` 后,受支持的浏览器会在控制台顶部显示“安装到设备”。`/control`
会由服务端永久重定向到这个规范地址。安装后的应用使用独立窗口,并继续采用“用户名 +
TOTP”登录;登录、注册与首次初始化在 PWA 内分别使用 `/control/login`、`/control/register` 和
`/control/setup`。原有 `/login`、`/register`、`/setup` 地址仍兼容普通浏览器书签。
PWA 只控制 `/control/`,不会控制、缓存或刷新 `/obs/*`
浏览器源。离线缓存仅包含 React 应用外壳、本地图标和构建后带哈希的静态资源;账户、TOTP、邀请码、CookieCloud、组件设置、OBS 令牌、直播事件和所有
`/api/*` 响应始终在线直连且由服务端返回
`Cache-Control: no-store`。离线时写操作会在浏览器端直接拒绝,不会排队或在恢复网络后重放。
部署新版本后,已打开的控制台会显示“更新可用”。更新不会自动接管或刷新页面,必须由用户点击确认;确认前请先保存设置以及只显示一次的邀请码、恢复码或刚轮换的 OBS 令牌。
## 组件与 OBS 地址
登录 `/control/`
后可以配置当前账户的直播源、管理组件、测试事件、调整弹幕样式,以及为每个组件单独生成或轮换只读 OBS 令牌。新的浏览器源地址格式是:
```text
https://danmaku.luoxingci.com/obs/<publicId>#token=<component-token>
```
`publicId` 是组件 UUID。`#token=...` 位于 URL
fragment,不会随最初的 HTTP 请求发送到 Nginx;OBS 页面随后通过 WebSocket 的第一帧向
`/api/v1/components/<publicId>/stream` 完成认证。令牌只带 `events:subscribe`
权限,不能调用管理或写入接口;轮换后旧令牌立即失效。
每个账户会自动拥有不可删除的点歌姬、全屏礼物特效、大航海特效和礼物菜单组件。观众发送 `点歌歌名` 或
`点歌 歌名`
加入队列;主播可从组件设置打开独立统计窗口,置顶、完成或取消队列项。点歌状态与历史持久化在 PostgreSQL,即使 OBS 未连接也不会丢失。
礼物特效组件在透明全屏浏览器源中展示从左向右飞行的礼物流星,并按礼物原始价值选择数量、尺寸和速度。该组件只订阅一次性
`live.gift`,不会接收大航海事件,也不会把连击更新重复播放为新礼物。密集礼物在每个 OBS 礼物组件中按有界 FIFO 逐个播放,当前动画结束前不会叠加下一笔特效。
大航海特效是独立的 `guard_effect` 组件,只订阅
`live.guard.buy`。舰长、提督和总督分别播放独立视频,或按主题展示月下星光庆祝;它拥有自己的实例、设置、OBS
token 和 WebSocket 通道。连续上舰事件同样按有界 FIFO 逐个完整播放,不会覆盖正在播放的视频。
礼物菜单读取账户当前直播间的礼物目录与图标,可把指定礼物、舰长/提督/总督或指定礼物单价映射为自定义直播内容。OBS 以横排行无限循环展示,实际投喂命中时自动定位、暂停并播放渐变星花高亮。
旧格式 `/obs?token=...` 与 `/ws?token=...` 已移除,避免 bearer
token 进入 Nginx 访问日志。升级后请在控制台为组件轮换令牌,并把旧 OBS 源替换为上述新地址;旧令牌一旦轮换便立即失效。
OBS 页面保持透明根背景,不使用固定 1920×1080 画布,并根据浏览器源的实际宽高自动适配。常用尺寸可从
`360×600`、`440×760` 或 `600×1080` 开始,也可以在 OBS 中自由拖拽缩放。
## 弹幕姬展示
每张消息卡片会从六套花纹组合中稳定选择一套,轮换使用对称花枝、横向自然藤纹和雏菊花簇,并改变上下、左右、镜像、配色与局部背景。连续的新卡避免使用相同款式,礼物连击更新保持原样式;透明消息墙本身不铺设全局装饰背景。粒子数量与速度、字号、事件类别、最大条数、自动收缩、卷轴展开时长、动效强度、低性能模式和礼物高亮阈值均可按组件在控制台调整,并实时推送给对应 OBS 源。
花边 SVG 已本地打包,来源及公版/CC0 许可记录在
`apps/overlay/public/assets/NOTICE.md`,OBS 运行时不会访问素材站点。
后端会为每个活动直播源缓存 Bilibili 礼物图片、GIF、币种和价格。目录刷新失败时保留上一次成功缓存;缺失条目会降级为直播事件自带的名称与价格。刷新间隔和请求超时可通过
`[gifts]` 调整。
普通混排表情和整条大表情会作为安全的文字/图片分段推送。消息自带图片地址优先;服务还会使用该用户 CookieCloud 中的
`SESSDATA`
刷新对应直播间的表情目录,在消息只提供唯一标识时补齐图片。加载失败时前端退回原始表情文字,刷新参数可通过
`[emoticons]` 调整。