# CodePlanet 在线更新站部署规范

> **适用基址**：`https://www.conbos.cn/CodePlanetr/Update/`  
> **Manifest Schema**：[`docs/schemas/update-manifest-v1.schema.json`](../schemas/update-manifest-v1.schema.json)  
> **验签实现**：`UpdateManifestVerifier`（RSA-SHA256 + canonical JSON）  
> **公钥**：仓库 `resources/config/update.pub` → 安装包内 `{InstallRoot}/config/update.pub`  
> **R3 客户端行为**：Launcher 拉取 manifest → 验签 → **Apply 或 Skip**；Apply 流程含 SHA256、staging、`ProjectValidator`、保留 `logs/`；验签/Apply 失败 **不阻断** 本地旧版启动（T31）。

---

## 1. 重要区分：更新站 ≠ 整个 `bin/` 目录

| 概念 | 内容 | 部署位置 |
|------|------|----------|
| **产线安装包** | Launcher + Runtime/Ops + Qt DLL + `plugins/` + `config/` + `translations/` | 客户机 `{InstallRoot}/`（由安装程序或 `deploy_bin.ps1` 产出） |
| **在线更新站** | **仅** manifest 索引的 **增量包**（项目 zip、可选 Runtime 安装包、可选插件 DLL） | `https://www.conbos.cn/CodePlanetr/Update/` |

**不要把开发机整个 `bin/` 文件夹原样上传到 conbos。**  
更新站只发布 manifest 中 `zipUrl` / `setupUrl` / `plugins[].path` 所指向的文件。

---

## 2. 在线目录结构（规范）

```text
https://www.conbos.cn/CodePlanetr/Update/
├── manifest.json                          # 必需；RSA 签名索引
├── projects/                              # 项目包（T13 / T37）
│   └── {name}/
│       └── {name}-{version}.zip           # 例：demo/demo-1.0.0.zip
├── runtime/                               # 可选：Runtime 组件升级
│   └── CodeRuntimeSetup-{version}.exe
├── launcher/                              # 可选：Launcher 组件升级
│   └── CodePlanetrSetup-{version}.exe
└── editor/                                # 可选：Editor 组件升级
    └── CodeEditorSetup-{version}.exe
```

**本仓库 NSIS 产物（发布根 `dist/CodePlanet/`，与 manifest `setupUrl` 对齐）**：

| 用途 | 路径 |
|------|------|
| 全量安装 | `dist/CodePlanet/Download/CodeplanetrSetup-latest.exe` |
| Runtime 补丁 | `dist/CodePlanet/Update/runtime/CodeRuntimeSetup-x.x.x.exe` |
| Launcher 补丁 | `dist/CodePlanet/Update/launcher/CodePlanetrSetup-x.x.x.exe` |
| Editor 补丁 | `dist/CodePlanet/Update/editor/CodeEditorSetup-x.x.x.exe` |

构建：`powershell -File scripts\build_nsis_packages.ps1`（需 `D:\NSIS\makensis.exe`；先 `deploy_staging.ps1 -Config Release`）。

**可选扩展**（manifest 中 `plugins[].path` 为相对 URL 时）：

```text
├── plugins/
│   └── BuiltinFunctions-{version}.dll
```

**URL 规则**：

- Launcher 固定请求：`https://www.conbos.cn/CodePlanetr/Update/manifest.json`
- manifest 内 `projects[].zipUrl`、`runtime.setupUrl` 为 **相对 manifest 所在目录** 的路径（正斜杠 `/`）
- 示例：`"zipUrl": "projects/demo/demo-1.0.0.zip"` →  
  `https://www.conbos.cn/CodePlanetr/Update/projects/demo/demo-1.0.0.zip`

---

## 3. `manifest.json` 字段要求

### 3.1 必需顶层字段

| 字段 | 类型 | 说明 |
|------|------|------|
| `schemaVersion` | `1` | 固定为 1 |
| `publishedAt` | ISO 8601 字符串 | 发布时间，如 `2026-05-24T10:00:00Z` |
| `signature` | Base64 字符串 | 对 **canonical JSON** 的 RSA-SHA256 签名（见 §4） |
| `runtime` | object | 至少含 `minVersion` |
| `plugins` | array | 插件最低版本清单（T36：低于仅 Warning） |
| `projects` | array | 可更新的项目包列表 |

### 3.2 `runtime` 对象

| 字段 | 必需 | 说明 |
|------|------|------|
| `minVersion` | **是** | 语义版本，如 `2.0.0` |
| `setupUrl` | 否 | 相对路径，如 `runtime/CodeRuntimeSetup-2.0.1.exe` |
| `setupSha256` | 否 | 安装包 SHA256（hex 小写，64 字符）；**R3 客户端下载后校验，空值拒绝** |

### 3.3 `launcher` / `editor` 对象（R3 扩展，可选）

与 `runtime` 相同字段：`minVersion`、`setupUrl`、`setupSha256`。示例：

- `"setupUrl": "launcher/CodePlanetrSetup-1.5.1.exe"`
- `"setupUrl": "editor/CodeEditorSetup-1.5.1.exe"`

Launcher 在 Apply 成功或跳过项目更新后，对低于 manifest 版本的组件 **非阻断** 提示运行对应 setup（不阻断 Launch）。

### 3.4 `plugins[]` 元素

| 字段 | 必需 | 说明 |
|------|------|------|
| `id` | **是** | 如 `BuiltinFunctions` |
| `minVersion` | **是** | 如 `1.4.0` |
| `path` | **是** | 产线安装布局相对路径，如 `bin/plugins/BuiltinFunctions.dll`（用于版本对照，非更新站 URL） |

> R1.5 Launcher 仅做 manifest 验签与 **Warning**；插件 DLL 在线替换 Apply 留 R3。

### 3.5 `projects[]` 元素

| 字段 | 必需 | 说明 |
|------|------|------|
| `name` | **是** | 项目逻辑名，与 yslib 内 `project` 名一致 |
| `version` | **是** | 语义版本，如 `1.0.0` |
| `zipUrl` | **是** | 相对 manifest 目录，如 `projects/demo/demo-1.0.0.zip` |
| `zipSha256` | **是** | zip 文件 SHA256（hex 小写）；**空值拒绝 Apply** |

### 3.6 样例

见仓库：

- [`examples/update/manifest.sample.json`](../../examples/update/manifest.sample.json)（占位签名）
- [`examples/update/manifest.bad-signature.json`](../../examples/update/manifest.bad-signature.json)（验签失败测试）

---

## 4. 签名与 canonical JSON（必须一致）

签名步骤（运维/发布脚本须与客户端相同）：

1. 从 manifest **移除** `signature` 字段  
2. 按 **固定字段顺序** 生成 compact UTF-8 JSON（无多余空白）：  
   - 顶层：`schemaVersion` → `publishedAt` → `runtime` → `launcher`（若有）→ `editor`（若有）→ `plugins` → `projects`  
   - `runtime` / `launcher` / `editor` 内：`minVersion` → `setupUrl` → `setupSha256`  
   - `plugins[]` 每项：`id` → `minVersion` → `path`  
   - `projects[]` 每项：`name` → `version` → `zipUrl` → `zipSha256`  
3. 对 canonical 字节做 **SHA-256**  
4. **RSA PKCS#1 v1.5** 私钥签名 → Base64 写入 `signature`

客户端公钥路径（已安装机器）：

1. `{InstallRoot}/config/update.pub`  
2. 回退：`resources/config/update.pub`（开发仓库）

---

## 5. 项目 zip 包规范（`projects/{name}/{name}-{version}.zip`）

### 5.1 命名

| 部分 | 规则 | 示例 |
|------|------|------|
| 目录名 | `projects/{name}/` | `projects/demo/` |
| 文件名 | `{name}-{version}.zip` | `demo-1.0.0.zip` |
| manifest `zipUrl` | `projects/{name}/{name}-{version}.zip` | 与上完全一致 |

### 5.2 zip 内容（解压目标 = `USER/projects/{folder}/`）

zip **根目录** 应直接包含项目文件夹内容（不要多包一层无意义父目录）：

```text
demo-1.0.0.zip
├── demo.yslib
├── scripts/
│   └── …
├── assets/          # 若有
└── logs/            # 可选；覆盖安装时客户端须保留已有 logs/（R3 Apply）
```

**打包来源（开发机）**：

```powershell
# 示例：从仓库 USER 或 examples 种子打包
cd E:\HuaweiMoveData\Users\MateBookXPro\Desktop\CodePlanetr\USER\projects\demoV1.0
# 选中 demo.yslib、scripts 等，压缩为 demo-1.0.0.zip（根即为项目文件）
```

### 5.3 SHA256

```powershell
Get-FileHash -Algorithm SHA256 .\demo-1.0.0.zip
# 将 Hash 小写 hex 写入 manifest projects[].zipSha256
```

---

## 6. 产线安装包与 `bin/` 的对应关系（客户机，非更新站）

客户机 **首次安装** 时，由安装程序或运维脚本部署 **完整运行时**（可参考 `scripts/deploy_staging.ps1` 产出）：

```text
{InstallRoot}/                    # 例：C:\Program Files\CodePlanet\
├── CodePlanetr.exe               # 产线入口
├── CodeRuntime.exe               # R2 交付（R1.5 可暂缺）
├── CodeEditor.exe                # Ops（维护用，产线可选）
├── Qt5*.dll / platforms/ …       # windeployqt 产物
├── plugins/
│   └── BuiltinFunctions.dll
├── config/
│   ├── FunctionLib.ini
│   └── update.pub                  # 验签公钥（与 manifest 私钥配对）
├── translations/
│   └── codeplanet_en.qm
└── USER/                           # 用户项目与偏好（可随安装包带 demo 种子）
    └── projects/
        └── demo/
            └── demo.yslib
```

**从开发 `bin/` 到安装包的映射**：

| 开发 `bin/` 内容 | 客户机 `{InstallRoot}/` |
|------------------|---------------------------|
| `CodePlanetr.exe` | 同路径 |
| `CodeEditor.exe` | 同路径（Ops） |
| `CodeRuntime.exe` | 同路径（R2） |
| windeployqt 生成的 DLL 与子目录 | 与 exe 同级 |
| `plugins/BuiltinFunctions.dll` | `plugins/` |
| `config/*` | `config/` |
| `translations/codeplanet_en.qm` | `translations/` |
| （不复制）`CodePlanetTests.exe` | — |

**在线更新站不上传上述 exe/DLL 全量**（除非未来 manifest 扩展整包 `setupUrl` 指向 `runtime/CodePlanetSetup-x.exe`）。

---

## 7. 服务端技术要求

| 项 | 要求 |
|----|------|
| 协议 | **HTTPS**（TLS 1.2+）；Launcher 使用 Qt Network，需有效证书链 |
| `manifest.json` | `Content-Type: application/json`；建议禁用缓存或短 Cache-Control |
| 大文件 zip/exe | 支持 Range 可选；建议 CDN/静态托管 |
| CORS | 非浏览器客户端，无硬性 CORS 要求 |
| 首批（T37） | `manifest.json` + `projects/demo/demo-1.0.0.zip` |

---

## 8. Launcher 能力分界（R1.5 / R3）

| 能力 | R1.5 | R3 |
|------|------|-----|
| HTTPS 拉取 `manifest.json` | ✅ | — |
| RSA 验签 | ✅ | — |
| 验签失败 → 继续启动旧版 | ✅ | — |
| 断网 / SSL → 提示 U 盘，不阻断 | ✅ 已测 | — |
| 缺 zip Warning | ✅ 客户端逻辑 | E2E 见 R3 计划 |
| Apply 下载/解压/覆盖 | ❌ 占位 | ✅ 实装（2026-05-26） |
| **在线/U 盘 E2E 回归** | ❌ 不测 | ✅ 自动化 + 手动清单（见 R3 报告） |

---

## 9. 发布检查清单（运维）

- [ ] 生成项目 zip，计算 `zipSha256`
- [ ] 编写 manifest（无 signature）
- [ ] 按 §4 生成 canonical JSON 并 RSA 签名
- [ ] 上传 `manifest.json` 到 `/CodePlanetr/Update/`
- [ ] 上传 zip 到 manifest 中 `zipUrl` 对应路径
- [ ] 浏览器/curl 可访问 manifest 与 zip URL
- [ ] **R3 Apply 后**：按 [R3-更新Apply与E2E测试计划.md](../AST审计执行报告/R3-更新Apply与E2E测试计划.md) 执行 T-UPD 系列

---

## 10. 参考

- [`docs/附录-路径与日志约定.md`](../附录-路径与日志约定.md)
- [`docs/AST审计执行报告/R3-更新Apply与E2E测试计划.md`](../AST审计执行报告/R3-更新Apply与E2E测试计划.md)（R3 Apply 后统一回归）
- [`docs/项目目标与架构终点.md`](../项目目标与架构终点.md) §3.5
