Files
soft_quay/docs/api.md
T

217 lines
9.3 KiB
Markdown

# 协议合约(Catalog / 软件包 / 许可证 / 事件 / CLI)
> 本项目没有自建在线 API;盒子只消费静态签名文件。本文定义所有跨边界数据结构和交互合约的目标形状。
> 实现前可细化,但不要在代码里另起一套不兼容结构。字段变化必须同步更新本文、[04-architecture.md](04-architecture.md) 和 `schemas/` 下的 JSON Schema。
## 通用约定
- 传输:HTTPS;所有 JSON 使用 UTF-8。
- 时间格式:ISO 8601(UTC)。
- 签名:Ed25519;客户端只内置公钥;示例中的 `"signature": "..."` 均为占位符。
- 版本号:语义化版本(SemVer)。
- 软件 ID:`^[a-z0-9-]+$`,永久稳定,发布后不得更改。
## 1. Catalog 清单(远端 → 盒子)
现代版与 Win7 版使用不同 channel 文件:`manifest-modern.json` / `manifest-win7.json`。更新器必须校验 channel,禁止 Win7 版下载现代版包。
```json
{
"schema_version": 1,
"channel": "modern",
"generated_at": "2026-07-16T00:00:00Z",
"min_box_version": "1.0.0",
"apps": [
{
"id": "json-parser",
"name": "JSON解析工具",
"description": "示例简介",
"version": "1.2.0",
"channel": "stable",
"status": "active",
"tags": ["工具", "JSON"],
"icon": "sha256:...",
"homepage": "https://example.com",
"tutorial": "https://example.com/tutorial",
"min_os": "windows-7-sp1",
"architectures": ["386", "amd64"],
"entry_exe": "JsonParser.exe",
"requires_admin": false,
"packages": {
"amd64": {
"url": "https://download.example.com/json-parser-1.2.0-amd64.zip",
"size": 12345678,
"sha256": "...",
"signature": "..."
}
}
}
],
"signature": "..."
}
```
行为要求:
- 网络成功:验签通过才替换本地缓存;验签失败**拒绝**,继续用最后一次验证成功的缓存。
- 网络失败:用缓存;清单过期给提示,但保留已安装软件的启动能力。
- 下架:显式 `status: deprecated | hidden`,不用名称前缀。
- 过滤:按 `min_os` 与 `architectures` 过滤;不兼容软件可见说明但不可下载。
### 1.1 Phase 1 签名域原型
T-101 验证采用以下签名域,供客户端与后续发布器实现对齐:
1. 输入必须是单个 UTF-8 JSON object;重复字段、尾随 JSON、浮点/指数数字直接拒绝。
2. 读取顶层 `signature`(标准 Base64 编码的 64 字节 Ed25519 签名),然后从对象中移除该字段。
3. 对剩余值递归规范化:对象键按 Unicode 字符串升序排列;数组保持原顺序;字符串按 JSON 转义;数字仅允许 JSON 整数并保持其合法十进制写法;不保留无意义空白。
4. Ed25519 直接签名/验证上述规范 JSON 字节。
这是 Phase 1 风险原型结论。T-201 正式接入时必须与 `softbox-catalog` 发布端做跨实现向量测试,再冻结 Schema、密钥 ID/轮换字段和版本兼容策略;在此之前不得另造签名域。
## 2. 标准软件包协议 v1(ZIP)
```text
json-parser_1.4.2_windows_amd64.zip
├─ app.json # 身份、版本、入口、兼容性
├─ files.json # 解压后文件清单(v1.1 强制,v1 推荐)
└─ payload/ # 实际安装到 current/ 的程序文件
```
### 2.1 app.json
```json
{
"schema_version": 1,
"id": "json-parser",
"name": "JSON解析工具",
"vendor": "MyCompany",
"version": "1.4.2",
"channel": "stable",
"min_os": "windows-7-sp1",
"architecture": "amd64",
"entrypoint": "JsonParser.exe",
"working_directory": ".",
"product_id": "product-json-parser",
"supports_trial": true,
"requires_admin": false,
"data_policy": "local-app-data",
"update_policy": "managed-by-softbox"
}
```
校验规则:`entrypoint`/`working_directory` 必须是 payload 内安全相对路径;`id`、`version`、`channel`、`architecture` 必须与 Catalog 记录一致;`schema_version` 高于盒子支持范围时拒绝安装并提示升级盒子(盒子始终支持当前与前一个 Schema)。
### 2.2 files.json
```json
{
"schema_version": 1,
"files": [
{ "path": "JsonParser.exe", "size": 3456789, "sha256": "..." }
]
}
```
完整 ZIP 的 SHA-256 由签名 Catalog 保存(不写入包内部);files.json 用于解压后复核关键文件与修复功能。
### 2.3 安全限制(必须拒绝)
绝对路径;`../` 穿越;符号链接/重解析点逃出 staging;写入其他软件或盒子目录;覆盖 `data/` 与 `licenses/`;包内自动执行脚本(install.bat/PowerShell 钩子);未验证 SHA-256/签名的包被执行;解压文件数、总体积或压缩比无上限;entrypoint 指向 payload 之外。
T-102 Phase 1 原型进一步固定:
- ZIP 名称只接受 UTF-8 `/` 分隔的规范相对路径;拒绝反斜杠、盘符、冒号/NTFS ADS、NUL、`.`/`..` 和大小写折叠后的重复输出路径。
- 顶层只允许必需的 `app.json`、可选 `files.json` 与 `payload/`;只把 `payload/` 内容写入全新的 staging。
- 拒绝符号链接、设备/管道等特殊文件和加密条目。
- 原型默认上限:10,000 个条目、总展开 4 GiB、单条及总体压缩比 200:1。T-302 按真实包体分布复核后再冻结。
- entrypoint 使用 payload 内相对路径表示,不得自带 `payload/` 前缀,且必须精确对应 ZIP 中的普通文件。
### 2.4 安装记录 installed-app.json(本地)
记录实际安装的软件 ID、版本、架构、channel 和文件清单;与 `current/`、`staging/`、`backup/` 同级存放于 `apps/<id>/`。
## 3. 许可证(服务端签发 → 本地离线验证)
```json
{
"schema_version": 1,
"license_id": "lic-...",
"machine_hash": "...",
"products": ["product-json-parser"],
"issued_at": "2026-07-16T00:00:00Z",
"perpetual": true,
"update_policy": "updates-until-2027-12-31",
"rebind_policy": "self-service-1-per-90d",
"signature": "..."
}
```
- machine_hash 由平台层多个稳定硬件标识清洗生成;许可证中**不保存**原始序列号和 MAC。
- 客户端用内置 Ed25519 公钥离线验签;许可证保存于 `licenses/`,与程序文件、用户配置分离;更新不得覆盖。
- 撤销名单同样签名并缓存,网络失败保留宽限期。
- 盒子负责导入/展示/管理;**子软件必须用 sdk 的 licensing 逻辑独立再验证**(签名 + machine_hash + product_id),决定正式版/试用版/授权错误。
## 4. application 事件合约(core → UI)
后台任务不直接修改 Gio 控件,只发布事件;UI 按 `request_id` + `app_id` 更新 ViewModel 并 `Invalidate`。
| 事件 | 触发时机 | 负载 | 结果 |
| --- | --- | --- | --- |
| CatalogRefreshed | 清单验签并缓存成功 | catalog 摘要、generated_at | 列表刷新 |
| CatalogRejected | 清单验签失败 | 原因码 | 提示 + 继续用缓存 |
| DownloadStarted | 下载任务开始 | request_id, app_id | 状态 → downloading |
| DownloadProgress | 进度更新 | request_id, app_id, done, total, speed | 进度条刷新 |
| DownloadPaused | 用户暂停 | request_id, app_id | 状态 → queued(暂停态) |
| DownloadCompleted | 下载并校验通过 | request_id, app_id | 状态 → verifying/extracting |
| DownloadFailed | 失败(网络/哈希/磁盘) | request_id, app_id, error_code | 状态 → failed + 可重试 |
| InstallCompleted | 原子切换成功 + 健康检查通过 | app_id, version | 状态 → installed |
| InstallRolledBack | 切换失败恢复 backup | app_id, error_code | 状态 → rollback 完成提示 |
| AppStarted / AppExited | 进程启动/退出检测 | app_id, pid | 状态 → running / installed |
| LicenseChanged | 许可证导入/撤销 | products | 授权视图刷新 |
错误码为稳定英文枚举(如 `hash_mismatch`, `zip_path_escape`, `disk_full`, `app_running`, `signature_invalid`),UI 负责本地化文案。
## 5. CLI 参数合约
### 5.1 SoftBox.exe
```bash
SoftBox.exe # 正常启动
SoftBox.exe --open-app <id> # 打开并定位到指定软件
SoftBox.exe --update-app <id> # 触发指定软件更新流程
SoftBox.exe --repair-app <id> # 按 files.json 修复安装(V1.1)
```
子软件的「检查更新」按钮调用以上参数,不自建下载器;盒子未运行时子软件可启动它。
### 5.2 SoftBoxUpdater.exe(盒子自更新助手)
```bash
SoftBoxUpdater.exe --pid <主程序PID> --staging <暂存目录> --target <目标目录>
```
等待主进程退出 → 备份旧版 → 切换新版 → 启动新版 SoftBox → 失败时恢复备份。退出码:0 成功;非 0 失败并写日志。
### 5.3 子软件推荐参数(v1 推荐,非强制)
```bash
<App>.exe --softbox-info # 输出软件 ID、版本、架构、协议版本 JSON,退出码 0
<App>.exe --softbox-health # 基本环境自检,成功退出码 0
```
产品自定义参数不得占用 `--softbox-` 前缀。
## 6. 进程退出协议(更新前)
v1:进程快照判断运行 → 提示用户保存关闭 → 等待正常退出 → 超时取消更新,**不默认强杀**。
V1.1:命名管道 `\\.\pipe\softbox.<app-id>`,盒子发送 `{"command": "prepare_update", "request_id": "..."}`,子软件保存数据回复 `ready` 后自行退出。
## 待实现时确认
- 清单签名封装格式(签名域、密钥轮换字段)定稿后同步 `schemas/`。
- 错误码完整枚举表。
- 图标资源的分发方式(内嵌哈希 vs 独立 URL)。
- 撤销名单的结构与宽限期时长。
- machine_hash 的标识来源清单与加权算法(平台层内部文档)。