自有主播版 OTA · 接口与交付说明
当前站点:https://ds.lltas.cn
设备检查:https://ds.lltas.cn/v1/device/ota
在线总览:https://ds.lltas.cn/docs
# 自有主播版 OTA 服务
本服务实现 `own_creator` / `p4_32m_v2` 的登记、签名检查、发布选择和应用下载。设备更新默认入口 `/ota` 跳转到主播版 `/ota/creator`;普通版管理位于 `/ota/standard`。设备入口是 `POST /v1/device/ota`。普通版发布记录和主播版记录分别存储。
**代码和自动测试已交付,不代表已有可发布固件或已完成真机验收。** 当前交接材料没有主播固件源码、真实应用、bootloader 和对应实板报告。产品编译策略的准确配置键尚待固件工程交付;目前产品、板型和布局来自可信运维提供的元数据及构建证据,不能视为可信 CI 对二进制产品归属的证明。禁止给普通版应用换标签后发布。
## 设备实际流程
1. 操作者在小程序显式设置完整 `ota_url` 和需要的对话地址。手机访问域名和设备 Wi-Fi 可访问地址分别确认;手机接入 Tailscale 不等于硬件也能访问 Tailscale。
2. `bootstrap` 获取对话配置和 UTC 毫秒时间,始终 `firmware: null`,不要求激活或绑定。
3. 小程序发送 BLE `check_ota` 后,设备签名请求 `intent=check_ota`。只推荐比当前版本高、精确匹配且已发布的应用。
4. 小程序再次明确发送 `start_ota`,设备核对内存中 15 分钟候选的版本和 SHA 后自行下载、验证、写备用槽及重启。服务器没有强制安装接口,不发送 `force`。
5. 云端最多观察检查、候选和下载结果。下载 HTTP 200、断线及后续报告版本均不等于启动确认完成;小程序应重连并核对目标版本与 `boot_state=valid`。
旧设备必须先有线迁移匹配 bootloader、新分区表、应用、assets 并初始化 otadata。应用 OTA 不会完成分区迁移。登记的有线迁移记录须包含真实 bootloader SHA、Flash 型号与报告编号;发布证据与这些字段不一致时不给该设备下发。
## 部署与地址
在小智服务工作目录启动现有 `core.http_server` 即挂载新接口;即使普通配置来自远程管理接口,新主播路由仍存在。资源后台也提供同路径代理,保留原始 path/query 和 body,不重新编码签名输入。
独立持久数据在服务根目录的 `data/creator_ota/`:SQLite 登记、运维会话、nonce、审计与事件,以及不可变应用和证据文件。备份时一致备份数据库与文件;恢复后 nonce 数据不可清空,否则旧请求可能重放。真实密钥和运维登录文件不得提交源码、放进公开下载目录或打印到日志。
管理页“设备检查地址”存储完整 `/v1/device/ota` URL;`websocket_url` 优先于 `creator_ota.websocket_url` 和原有 `server.websocket`。未配置检查地址时仅开发模式从当前服务地址推导。每个地址都必须从设备实际网络验证可达。
默认 `production=false` 是隔离开发模式,仍要求登记设备、真实出厂密钥和 HMAC;不提供无密钥或匿名降级。生产设置 `production=true`、HTTPS 检查地址以及 WSS 对话地址。生产 HTTP 请求被拒绝。若 TLS 在可信反向代理终止,小智服务配置示例:
```yaml
creator_ota:
trusted_proxy_ips: ["127.0.0.1", "::1"]
websocket_token_ttl: 900
```
只列实际代理的连接源 IP,不使用任意网段。代理必须覆盖外来 `X-Forwarded-Proto` 并只传一个 `https` 值;后端应仅允许可信代理接入。服务不会信任任意客户端的转发头,也不接受逗号或重复的协议值。生产反代要保留原始路径、query 顺序/转义和正文;下载关闭压缩与缓存转换,转发精确 `Content-Length`,不要重定向。
MQTT 配置复用现有 gateway 和签名约定,传输 TLS 与 gateway 凭据有效期由既有 MQTT 服务配置负责。WS 的 `version` 固定为 `1`;启用 `server.auth.enabled` 且设备不在免验证白名单时签发默认 900 秒、最多 3600 秒的凭据,现有 WebSocket 验证器会实际检查到期时间。关闭鉴权或免验证名单中的设备返回空 token,不声称该连接具备短期凭据保护。生产上线需另行确认对话服务鉴权与 TLS。
## 运维账号
资源后台以 `--no-auth` 在受控内网启动时,主播版管理也直接进入,无需登录;页面显示“内网管理”,操作审计统一记录为 `local-network`。此模式不创建自动登录账号或浏览器会话。设备请求的出厂密钥签名、固件校验及发布证据门槛保持生效。
启用资源管理令牌的部署仍使用独立主播版运维账号登录。资源管理令牌、设备密钥和主播版账号是三种独立凭据;`viewer` 只能查看,`operator` 可登记、上传、修改和发布。密码采用 scrypt;会话 8 小时失效,可退出撤销。错误登录有持久限流,首次账号仅从本机工具建立。已有账号和审计在切换访问模式时保留。
在 `main/xiaozhi-server` 中运行:
```sh
python tools/creator_ota_admin.py bootstrap --username local-admin --save-login
python tools/creator_ota_admin.py create-account --username observer --role viewer --operator local-admin
python tools/creator_ota_admin.py create-account --username release-ops --role operator --operator local-admin
```
首条仅在尚无账号时有效,生成的登录信息保存为本机仅用户可读文件,不输出密码。省略 `--save-login` 时交互输入密码;创建其他账号也交互输入,不在命令行参数传密码。可用全局 `--server-root` 指向明确的服务目录。
## 上传材料和发布校验
上传仅接受标准未签名 ESP32-P4 应用镜像,最大 **8 MiB(8,388,608 字节)**。服务解析每个镜像段、描述 magic、芯片、内嵌版本、校验和及可选追加 SHA;拒绝 bootloader、merged、全 Flash 镜像、尾随数据和当前未支持的 Secure Boot 签名封装。文件版本必须是 1 至 4 段数字,例如 `2.1.1`,不支持 `-beta`。同一产品/布局/板型/硬件型号、同一版本不能替换内容;`2.1` 与 `2.1.0` 的比较遵循补零数字规则。
每个上传由三部分组成:
| multipart 字段 | 内容 |
| --- | --- |
| `file` | 实际应用 `.bin` |
| `metadata` | 下述 JSON 原文,以文本字段提交 |
| `evidence` | 可选草稿证据 ZIP;发布前必须补齐 |
最小元数据(以下仅为结构说明,不能当真实发布材料):
```json
{
"board": "dimensoul-p4-c6",
"hardware_model": "dimensoul_badge",
"hardware_versions": ["p4_hw_mvp"],
"product": "own_creator",
"partition_layout": "p4_32m_v2",
"build_commit": "填写真实完整40位Git提交",
"notes": "真实变更与适用范围"
}
```
证据 `metadata.evidence` 为 `schema=1`,包含与元数据相同的 `build_commit`、`board`、`hardware_model`、`product`、`partition_layout`,以及 `flash_model`、`record_ref`、真实 `app_sha256`、`bootloader_sha256`。`artifacts` 必须恰含以下四项,各为 `{ "path": "ZIP内相对路径", "sha256": "实际文件SHA256" }`:
| 键 | 实际文件 |
| --- | --- |
| `app_config` | 应用生成的 sdkconfig 原文 |
| `bootloader_config` | bootloader 生成的 sdkconfig 原文 |
| `bootloader` | 该次有线迁移对应 bootloader 二进制 |
| `hardware_record` | 绑定本次制品的实板报告 JSON |
两份配置都必须有 `CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH=y` 和 `CONFIG_IDF_EXPERIMENTAL_FEATURES=y`。仅 Flash 容量或类似名称的辅助开关不满足此门槛。每个证据文件最大 4 MiB,ZIP 总展开大小最大 16 MiB;拒绝路径穿越、符号链接、加密条目及重复文件名。
`hardware_record` JSON 必须提供 `record_ref`、`board`、`flash_model`、`bootloader_sha256`、`app_sha256`、`build_commit`,与证据字段一致;`report` 为原始过程、结果和适用范围文本(至少 80 字符)。服务器只验证文件、绑定与配置内容,无法自动证明操作员确实完成实板测试。已有 GD 32 MiB 样机结果不等于其他 Flash 或所有跨 16 MiB 应用已经验收。
使用 [打包工具](../main/xiaozhi-server/tools/creator_ota_package.py) 收集已有真实文件:
```sh
python tools/creator_ota_package.py \
--app /real/build/xiaozhi.bin \
--app-config /real/build/sdkconfig \
--bootloader-config /real/build/bootloader/sdkconfig \
--bootloader /real/build/bootloader/bootloader.bin \
--hardware-record /real/reports/board-acceptance.json \
--board dimensoul-p4-c6 --hardware-model dimensoul_badge \
--hardware-version p4_hw_mvp --build-commit REAL_FULL_GIT_COMMIT \
--flash-model REAL_FLASH_MODEL --record-ref REAL_REPORT_ID \
--output-dir /real/output/new-release
```
`/real/...` 和大写词是待替换路径/值,工具不会生成固件或实板报告。输出新的 `application.bin`、`metadata.json`、`evidence.zip`;不会覆盖已有输出、发布或烧录。工具做镜像头预检与证据绑定,上传端仍完整验证应用。
先上传草稿、补齐证据并复核目标型号/修订,再点击发布确认。每个产品/布局/板型/型号/硬件修订分别选择一个活动发布,修订间不会互相挤掉当前候选。灰度按发布和设备序列号稳定分桶;暂停或撤回同时阻止新检查和新的下载。已经开始的传输不能被当作成功安装;撤回不回滚已运行的设备。发生问题先暂停/撤回,保留证据与审计,再提供更高版本修复;服务器不向设备下发同版本或降级包,也不通过改 SHA 强制替换同版本。
## 设备签名协议
[OpenAPI](creator-ota-openapi.json) 描述设备与管理入口。[固定测试向量](creator-ota-signature-vectors.json) 使用公开测试密钥,仅离线校验,禁止把该密钥/身份登记到真实服务。
检查和下载均要求:`Serial-Number`、`Device-Id`、`Hardware-Version`、`Firmware-Version`、`X-Device-Timestamp`、`X-Device-Nonce`、`X-Device-Signature-Alg: HMAC-SHA256`、`X-Device-Signature`;`Activation-Id` 可为空。`Client-Id` 用于后续 WS 连接绑定,连接时须一致。出厂 `device_key` 的**原始 UTF-8 字节**作为 HMAC 密钥,不自动按 hex 解码。
签名串用单个 LF 分隔,没有尾随 LF:
```text
request:v1
method:POST
path:/v1/device/ota
body_sha256:<原始请求bytes的SHA256小写hex>
serial_number:<Serial-Number>
timestamp:<X-Device-Timestamp原值>
nonce:<X-Device-Nonce原值>
activation_id:<Activation-Id,可空>
```
结果 HMAC-SHA256,Base64URL 无 padding。下载改为 `GET`、URL 的原始 path/query 和空正文。query 顺序、`%2F` 等原始转义、正文空格和结尾换行都影响签名。路由不重序列化 JSON;未签名的 `Product`、`Board` 等头只用于兼容入口分流,授权以已验证正文和登记身份为准。旧 `/xiaozhi/ota/` 收到主播产品、签名头或已登记身份时也进入此校验,不能绕到普通版升级;主播下载须用新候选 URL。
可信秒时间戳窗口为 ±300 秒,nonce 每设备持久唯一。登记设备的 HMAC 正确但时间早于 2020 年的 `bootstrap` / `check_ota` 可进入最多 600 秒、5 次的冷启动窗口;可信时间建立后不再接受低时间戳,设备闲置超过一小时后的冷启动另计窗口但旧 nonce 仍不可重放。GET 下载不开放冷启动时间例外。每设备每分钟最多 30 次已签名请求(GET/POST 共用);超限返回 429。
成功检查响应为顶层对象,没有 `data` 包装。`firmware` 为 `null` 或包含 `version/url/size/sha256/hardware_model/hardware_version/board/product/partition_layout` 的候选。`server_time.timestamp` 是 UTC Unix 毫秒,不把 `timezone_offset` 加入 epoch。
失败返回 JSON `code`、`message` 和非 200 状态:签名缺失、错误、重放或时钟不合法为 401,未登记/停用/不匹配/未迁移/不适用发布为 403,限流 429,服务或制品异常 503;格式错误 400、过大 413、不存在 404、版本冲突 409。无更新才返回 200 和 `firmware:null`,错误不伪装成无更新。
下载 URL 精确到不可变发布对象,同样验设备 HMAC、当前适用范围和版本。版本授权使用该设备之前已验签 bootstrap/check 正文保存的 current_version,不信任下载请求中可被改写的 Firmware-Version;尚无可信检查观察时返回 403/check_required,须先完成检查。仅完整 GET 返回 200、精确 Content-Length、原始二进制和 identity 编码;Range/If-Range 拒绝,HEAD 为 405,不依赖分块传输、压缩或重定向。当前没有 CDN 签名 URL 模式。
## 可运行签名客户端
工具只用 Python 标准库;读取真实密钥文件或环境变量,输出会遮蔽协议 token/password,不输出设备密钥。先离线验证:
```sh
python tools/creator_ota_client.py verify-vectors
```
管理页可下载独立 [client.py](/ota/creator/client.py)、[package.py](/ota/creator/package.py) 和 [signature-vectors.json](/ota/creator/signature-vectors.json)。下载后放在同一文件夹,运行 `python client.py verify-vectors --file signature-vectors.json`;其余命令把脚本路径替换为下载的 client.py / package.py。
准备设备**实际完整请求 body**文件,保留 `serial_number`、`hardware_model`、`hardware_version`、`product`、`partition_layout`、`board_type`、`current_version` 和 `application` 等实际系统字段。模板不是签名正文替代品。使用真实登记身份:
```sh
python tools/creator_ota_client.py check \
--url https://ds.lltas.cn/v1/device/ota --body-file actual-request.json \
--serial-number ACTUAL_SERIAL --device-id ACTUAL_DEVICE_ID \
--hardware-version ACTUAL_REVISION --firmware-version 2.1.0 \
--key-file /private/device-key.txt
```
`bootstrap` 用相同参数但 body 的 `intent` 为 `bootstrap`。局域网 HTTP 联调必须显式加 `--allow-http`。该工具只发送请求;它不会让设备安装,也不输出可直接复制的 WS bearer。
下载候选时使用检查返回的精确 URL、size、SHA:
```sh
python tools/creator_ota_client.py download \
--url ACTUAL_CANDIDATE_HTTPS_URL --size ACTUAL_SIZE --sha256 ACTUAL_SHA256 \
--output new-candidate.bin --serial-number ACTUAL_SERIAL --device-id ACTUAL_DEVICE_ID \
--hardware-version ACTUAL_REVISION --firmware-version 2.1.0 \
--key-file /private/device-key.txt
```
客户端拒绝重定向、压缩、chunked、长度/SHA 不符,30 秒网络超时;成功仅报告下载完成并明确 `installation_confirmed=false`。
## 管理 API
`GET /api/creator-ota/auth-config` 返回当前部署是否要求登录。要求登录时,除登录与公开文档外,使用主播版账号得到的 `Authorization: Bearer <session>`;不能使用设备密钥或旧资源管理令牌替代。免登录内网模式允许直接访问管理 API,写操作通过固定本地运维身份校验并记录审计,拒绝其他网站发起的跨站写请求。OpenAPI 管理接口的认证声明随当前部署模式返回;设备接口始终要求 HMAC。管理 API 错误使用 FastAPI 的 `detail` 包装,设备接口错误保持顶层 code/message。
| 方法与路径 | 用途/正文 |
| --- | --- |
| `GET /api/creator-ota/auth-config` | 无需登录,返回 `required` 布尔值 |
| `POST /api/creator-ota/login` | JSON `username,password`;返回会话 |
| `POST /api/creator-ota/logout` | 撤销当前会话 |
| `GET /api/creator-ota/me` | 当前账号与角色 |
| `GET /api/creator-ota/snapshot` | 配置、发布、设备、检查下载事件和操作审计 |
| `PUT /api/creator-ota/settings` | JSON `public_url,websocket_url,production` 的部分更新 |
| `POST /api/creator-ota/devices` | 新增实际身份、`device_key` 与 `migration` 登记;重复序列号返回 409 |
| `PATCH /api/creator-ota/devices/{serial}` | 更新身份/迁移记录,或仅 `enabled` 停用/启用;不接受 device_key/secret 覆盖出厂密钥 |
| `POST /api/creator-ota/releases` | multipart `file,metadata,evidence`,先生成草稿 |
| `PATCH /api/creator-ota/releases/{id}` | multipart `metadata,evidence`,仅补充草稿证据/元信息,不换应用 |
| `POST /api/creator-ota/releases/{id}/publish` | 确认发布,可带 JSON `rollout_percent` |
| `POST /api/creator-ota/releases/{id}/pause` 或 `/resume` | 暂停/恢复;可带 `rollout_percent`(0–100) |
| `POST /api/creator-ota/releases/{id}/withdraw` | 撤回 |
| `GET /api/creator-ota/releases/{id}/download` | 运维下载原件;与设备下载权限入口分开 |
事件显示“最近检查”“候选可用”“下载已提供”等服务器可证实状态,不显示“在线”或“升级成功”。审计保存操作人、发布/撤回/灰度/设备变更及文件绑定;不得把密钥、签名、完整 token 或带签名 query 的 URL 放日志。
## 验证与未完成项
在项目根的 `xiaozhi-esp32` 目录运行:
```sh
../.venv-admin/bin/python -B -m unittest discover -s scripts/resource_admin/tests -p 'test_creator_ota_store.py'
../.venv-xiaozhi/bin/python -B -m unittest discover -s scripts/resource_admin/tests -p 'test_creator_ota_http.py'
../.venv-admin/bin/python -B -m unittest discover -s scripts/resource_admin/tests -p 'test_creator_ota_admin.py'
```
HTTP 套件使用真实 aiohttp 路由和真实临时 Store,仅本机临时端口/测试身份和合成文件;没有 aiohttp 的后台环境明确 skip。覆盖签名原文、空激活、重放、旧路由、匹配/迁移门槛、草稿/暂停/灰度/撤回、完整下载、生产代理边界和短期 WS 凭据。Store 与后台套件覆盖版本、镜像、证据、安全上传、会话与审计。固定向量可单独运行,不需要服务。
仍需提供并验收:主播固件源码及准确构建策略、两版真实有效应用、关联 bootloader/配置/实板报告、设备出厂身份、有线迁移记录;接着按原交付任务执行 BLE 分片/断线重连、Wi-Fi、显式跨槽更新、版本/boot_state 回读、拔网保旧版、写备用槽时断电、真实启动失败回滚和现场音视频观察。本仓库自动测试不替代固件/小程序离线测试或上述真机验收。