Files
soft_quay/docs/api.md
T

12 KiB

协议合约(Catalog / 软件包 / 许可证 / 事件 / CLI)

本项目没有自建在线 API;盒子只消费静态签名文件。本文定义所有跨边界数据结构和交互合约的目标形状。 实现前可细化,但不要在代码里另起一套不兼容结构。字段变化必须同步更新本文、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 版下载现代版包。

{
  "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",
      "category": "开发工具",
      "tags": ["工具", "JSON"],
      "icon": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
      "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": "..."
}

行为要求:

  • 网络成功:按“HTTPS 获取 → 验签 → Schema/字段/目标 channel 校验 → 替换缓存”处理;任一步失败都拒绝新内容,继续用最后一次验证且客户端可消费的缓存。
  • 网络失败:用缓存;清单过期给提示,但保留已安装软件的启动能力。
  • 下架:显式 status: deprecated | hidden,不用名称前缀。
  • 分类:category 是单一稳定分类,tags 是搜索/多标签展示数据;两者都不得为空。
  • 过滤:先校验 manifest channel,再按 min_os、architectures 和 packages 过滤;不兼容软件可见说明但不可下载。
  • 状态:active 可安装;deprecated 可见但不可新装/更新;hidden 不进入目录结果,但本地已安装记录仍由本地状态模块保留。
  • URL:Catalog、package、homepage、tutorial 只接受无用户信息、无 fragment 的绝对 HTTPS URL。
  • 架构:MVP Schema 接受 386 / amd64;一个 app 的 architectures 必须与 packages 键一一对应。当前两个客户端发布目标仍是 amd64。
  • 系统版本:min_os v1 只允许 windows-7-sp1、windows-10、windows-11;更高系统可消费更低最低版本的软件。
  • Schema:客户端协议文件为 schemas/manifest.schema.json;标准包元数据为 schemas/app.schema.json。运行时还会执行 JSON Schema 难以表达的重复 ID、架构映射和 channel 目标一致性检查。

1.1 Catalog 签名域

客户端采用以下签名域,供发布器实现对齐:

  1. 输入必须是单个 UTF-8 JSON object;重复字段、尾随 JSON、浮点/指数数字直接拒绝。
  2. 读取顶层 signature(标准 Base64 编码的 64 字节 Ed25519 签名),然后从对象中移除该字段。
  3. 对剩余值递归规范化:对象键按 Unicode 字符串升序排列;数组保持原顺序;字符串按 JSON 转义;数字仅允许 JSON 整数并保持其合法十进制写法;不保留无意义空白。
  4. Ed25519 直接签名/验证上述规范 JSON 字节。

T-201 已把该签名域接入正式客户端加载链路并用客户端测试向量覆盖。softbox-catalog 发布端仍必须补跨实现向量测试;密钥 ID/轮换字段尚未定稿,在单公钥协议升级前不得另造签名域。

2. 标准软件包协议 v1(ZIP)

json-parser_1.4.2_windows_amd64.zip
├─ app.json      # 身份、版本、入口、兼容性
├─ files.json    # 解压后文件清单(v1.1 强制,v1 推荐)
└─ payload/      # 实际安装到 current/ 的程序文件

2.1 app.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

{
  "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>/。

{
  "schema_version": 1,
  "id": "json-parser",
  "version": "1.4.2",
  "architecture": "amd64",
  "channel": "stable",
  "files": [
    {
      "path": "JsonParser.exe",
      "size": 3456789,
      "sha256": "0000000000000000000000000000000000000000000000000000000000000000"
    }
  ]
}

规则:

  • Schema 位于 schemas/installed-app.schema.json;未知字段、非法 SemVer、ID/目录不匹配、非 386|amd64 架构、非 stable channel、安全相对路径以外的文件名、重复路径和非法 SHA-256 均拒绝。
  • files 必须是数组;v1 可以为空,完整文件清单由 T-302 安装整合时从已验证包写入。
  • 写入使用 app 目录内临时文件 + installed-app.json.backup 原子替换;主文件缺失时可读取中断遗留 backup,但所有读取都重新严格校验。
  • SemVer 比较遵循 2.0.0:major/minor/patch 与 prerelease 参与 precedence,build metadata 不影响更新判断。

2.4.1 本地可见状态推导

状态事实由后台/存储层预先收集,UI Layout 不扫描磁盘。单一可见状态按以下优先级推导:

  1. 存在 install-transaction.json 或其 backup → rollback_pending。
  2. 存在活跃任务状态 → queued|downloading|verifying|extracting|installing|failed。
  3. 已检测到进程运行 → running。
  4. Catalog 判定不可兼容 → incompatible。
  5. 无 installed-app.json → not_installed。
  6. 本地 SemVer 低于 Catalog → update_available;否则 → installed。Catalog 中已隐藏/下架且无可比较版本时,保留 installed。

2.5 安装切换事务(Phase 1 原型)

apps/<id>/install-transaction.json 用于断电恢复:

{
  "schema_version": 1,
  "phase": "staging_activated",
  "had_current": true
}

phase 只允许:prepared、current_backed_up、staging_activated、rollback_required、committed。日志使用临时文件 + 同目录 backup 原子替换;恢复时同时检查日志与 current/staging/backup 实际状态。未完成健康检查的 current 不视为可信:有旧版时恢复 backup,首次安装则撤销 current。

3. 许可证(服务端签发 → 本地离线验证)

{
  "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

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(盒子自更新助手)

SoftBoxUpdater.exe --pid <主程序PID> --staging <暂存目录> --target <目标目录>

等待主进程退出 → 备份旧版 → 切换新版 → 启动新版 SoftBox → 失败时恢复备份。退出码:0 成功;非 0 失败并写日志。

5.3 子软件推荐参数(v1 推荐,非强制)

<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 后自行退出。

待实现时确认

  • 清单密钥 ID/轮换字段定稿后同步 schemas/。
  • 错误码完整枚举表。
  • 图标资源的分发方式(内嵌哈希 vs 独立 URL)。
  • 撤销名单的结构与宽限期时长。
  • machine_hash 的标识来源清单与加权算法(平台层内部文档)。