7.0 KiB
Recording Transfer and Playback Runbook
任务:S24 - 录音搬运与播放
完成时间:2026-06-21 20:20 +08:00 当前模式:S56 - Server B 本机录音闭环(2026-08-27)
0. 当前生产架构(S56)
录音不再经过 B 到 A 的专用 SSH。Server B 上的 RTPEngine Recording Daemon 先写入
/dev/shm/voip_rec/incoming,本机 finalizer 将稳定文件原子改名为
/dev/shm/voip_rec/ready/**/*.ready,Recording Worker 再复制、校验并持久化到
/data/recordings,最后写入数据库并删除本机 .ready 源文件。
B RTPEngine -> B incoming -> B finalizer -> B ready
-> B Recording Worker -> B /data/recordings + MySQL
旧的 B -> A SSH 配置仅作为短期回滚材料保留,不参与当前服务。S24 以下章节保留为历史实施记录。
1. 目标
S24 完成录音闭环的最小能力:
- Server B 通过 Tailscale 私网从 Server A 拉取
.ready录音文件。 - 拉取后校验文件大小和 SHA-256。
- 本地
.part-*临时文件校验成功后原子改名。 - 校验和数据库标记成功后才允许删除 Server A 源
.ready文件。 - API 提供受 RBAC 保护的播放入口,通过 Nginx
X-Accel-Redirect进入内部/_recordings/location,支持 Range。
2. 代码产物
| 文件 | 说明 |
|---|---|
apps/worker-recording/src/transfer.ts |
录音扫描、SSH 拉取、大小校验、SHA-256、原子落盘、源文件安全删除、recordings 入库 |
apps/worker-recording/src/main.ts |
Recording Worker 主循环 |
apps/worker-recording/src/transfer.spec.ts |
搬运、哈希、删除保护和路径安全测试 |
apps/api/src/modules/recordings/* |
GET /api/v2/recordings/:id/play 播放入口 |
apps/api/src/modules/recordings/recordings.service.spec.ts |
Nginx 内部路径和路径穿越防护测试 |
infra/server-a/s19/scripts/lisglosips-recording-finalize |
A 端 finalizer 仓库副本,ready 目录改为 0770,便于专用拉取用户校验后删除源文件 |
3. Worker 环境变量
S56 当前生产配置:
DATABASE_URL
RECORDING_SOURCE_MODE=local
RECORDING_LOCAL_READY_DIR=/dev/shm/voip_rec/ready
RECORDING_LOCAL_ROOT=/data/recordings
RECORDING_SCAN_INTERVAL_MS=10000
RECORDING_MAX_FILES_PER_SCAN=50
RECORDING_DELETE_SOURCE_AFTER_COPY=true
RECORDING_SOURCE_MODE=ssh 仍受代码支持,以下变量只用于回滚到旧架构:
DATABASE_URL
RECORDING_SOURCE_MODE=ssh
RECORDING_REMOTE_HOST=lisglosips-a
RECORDING_SSH_CONFIG=/etc/lisglosips/recording/ssh_config
RECORDING_REMOTE_READY_DIR=/dev/shm/voip_rec/ready
RECORDING_LOCAL_ROOT=/data/recordings
RECORDING_SCAN_INTERVAL_MS=10000
RECORDING_MAX_FILES_PER_SCAN=50
RECORDING_DELETE_SOURCE_AFTER_COPY=true
SSH 模式本地开发可使用 .codex-private/ssh/config;生产回滚时才使用 /etc/lisglosips/recording/ 下的受控 key 和 known_hosts。
4. 服务器变更
Server B
- S56 新增
lisglosips-recording-finalize.timer,每 5 秒检查本机稳定录音文件。 - Recording Worker 以
lisglo-recorder身份运行,通过SupplementaryGroups=rtpengine访问 ready 目录。 - systemd 只开放
/dev/shm/voip_rec/ready与/data/recordings所需写权限。 - Worker 使用
RECORDING_SOURCE_MODE=local,运行期不再启动ssh子进程。 - 新增
/etc/lisglosips/recording/a_pull_ed25519,权限0640 root:lisglosips。 - 新增
/etc/lisglosips/recording/known_hosts。 - 录音持久化目录仍为
/data/recordings,由lisglo-recorder:lisglosips管理。 - 备份目录:
/var/backups/lisglosips-s24/20260621T121100Z。
Server A
- 新增专用用户
lisglo-rec-pull,加入rtpengine组。 - 安装 B 端录音拉取公钥到
/var/lib/lisglo-rec-pull/.ssh/authorized_keys。 - 将
/dev/shm/voip_rec/ready调整为0770 rtpengine:rtpengine。 - 更新
/usr/local/sbin/lisglosips-recording-finalize,让 ready 根目录和 ready 子目录保持0770。 - 备份目录:
/var/backups/lisglosips-s24/20260621T121130Z。
未调整 SSH 管理端口,未禁止 SSH key 登录,未重启 OpenSIPS、RTPEngine、Nginx 或 API。
5. 播放路径
API:
GET /api/v2/recordings/:id/play
Permission: recordings.play
API 只查询状态为 READY 的录音,返回:
X-Accel-Redirect: /_recordings/<storage-key>
Content-Type: audio/wav 或 audio/mpeg
Cache-Control: private, no-store
Nginx 已在 S05 配置:
location /_recordings/ {
internal;
alias /data/recordings/;
add_header Accept-Ranges bytes always;
}
外部直接访问 /_recordings/... 返回 404,只能经 API 鉴权后内部转发。
6. 验证结果
本地:
corepack pnpm@10.33.0 exec vitest run apps/worker-recording/src/transfer.spec.ts apps/api/src/modules/recordings/recordings.service.spec.ts通过,5 条测试。corepack pnpm@10.33.0 --filter @lisglosips/worker-recording build通过。corepack pnpm@10.33.0 typecheck通过。corepack pnpm@10.33.0 lint通过。corepack pnpm@10.33.0 build通过。
服务器冒烟:
- A 生成测试文件
/dev/shm/voip_rec/incoming/s24-smoke/s24-smoke.wav。 - A finalizer 生成
/dev/shm/voip_rec/ready/s24-smoke/s24-smoke.wav.ready。 - B 使用
lisglo-recorder和专用 SSH key 从 A 私网拉取。 - B 落盘
/data/recordings/s24-smoke/s24-smoke.wav,大小31,属主lisglo-recorder:lisglosips。 - A/B SHA-256 一致:
c72dd3909a317ad55817435b7ed5a0db6947455ffc66e09a2dde787fe4a8c348。 - 校验成功后 A 源
.ready文件已删除。 - 外部直接访问
https://127.0.0.1/_recordings/s24-smoke/s24-smoke.wav返回404,内部录音 location 未暴露。
7. 当前限制
S24 记载的 placeholder 限制已经失效;B 已运行完整 monorepo API/Worker release。S56 不迁移 A 的历史录音,也不自动删除旧 SSH key;旧凭据待本地闭环稳定运行并完成真实录音验收后再单独清理。
8. 回滚
Server A:
sudo cp -a /var/backups/lisglosips-s24/20260621T121130Z/lisglosips-recording-finalize /usr/local/sbin/lisglosips-recording-finalize
sudo chmod 0755 /usr/local/sbin/lisglosips-recording-finalize
sudo userdel -r lisglo-rec-pull
sudo chmod 0750 /dev/shm/voip_rec/ready
Server B:
sudo systemctl disable --now lisglosips-recording-finalize.timer
sudo cp /var/backups/lisglosips-s56/<timestamp>/recording-worker.env /etc/lisglosips/recording-worker.env
sudo cp /var/backups/lisglosips-s56/<timestamp>/10-s28-recording.conf /etc/systemd/system/lisglosips@recording-worker.service.d/10-s28-recording.conf
sudo systemctl daemon-reload
sudo systemctl restart lisglosips@recording-worker
代码回滚:
- 恢复
apps/worker-recording/src/main.ts到骨架版本。 - 删除或恢复
apps/worker-recording/src/transfer.ts、transfer.spec.ts。 - 删除
apps/api/src/modules/recordings/并从AppModule移除RecordingsModule。 - 恢复
apps/worker-recording/package.json、tsconfig.json、pnpm-lock.yaml。 - 重新执行
corepack pnpm@10.33.0 build。