# CodePlanet U 盘离线更新部署规范

> **客户端扫描路径**：各已挂载卷根目录下的 `CodePlanetrUpdate/manifest.json`（`AppPaths::usbUpdateManifestPath()`）  
> **验签规则**：与在线站 **完全相同**（同一 `update.pub`、同一 canonical JSON、同一 RSA-SHA256）  
> **优先级**：Launcher **先 U 盘、后在线**（U 盘 manifest 可读则不再请求 conbos）  
> **R3 客户端行为**：与在线站相同验签规则；**Apply 已实装**（见 §6）。

---

## 1. 重要区分：U 盘 ≠ 整个 `bin/` 目录

| 概念 | 内容 |
|------|------|
| **U 盘更新盘** | `CodePlanetrUpdate/` 下的 **manifest + 索引内的 zip/安装包** |
| **产线安装包** | 客户机 `{InstallRoot}/` 全量运行时（见 [在线更新站部署规范.md](./在线更新站部署规范.md) §6） |

U 盘用于 **离线分发项目包**（及可选 Runtime 安装包），**不是**拷贝开发机整个 `bin/` 文件夹。

---

## 2. U 盘目录结构（规范）

`zipUrl` / `setupUrl` 均为 **相对 `CodePlanetrUpdate/manifest.json` 所在目录** 的路径：

```text
{U盘根}:/
└── CodePlanetrUpdate/
    ├── manifest.json
    ├── projects/
    │   └── {name}/
    │       └── {name}-{version}.zip
    ├── runtime/                              # 可选
    │   └── CodeRuntimeSetup-{version}.exe
    ├── launcher/                             # 可选
    │   └── CodePlanetrSetup-{version}.exe
    └── editor/                               # 可选
        └── CodeEditorSetup-{version}.exe
```

离线制作也可直接拷贝本仓库 `dist/CodePlanet/Update/{runtime,launcher,editor}/` 下 NSIS 产物到 U 盘对应子目录（与 manifest `setupUrl` 一致）。

### 2.1 首批示例（T37 对齐）

```text
E:/
└── CodePlanetrUpdate/
    ├── manifest.json
    └── projects/
        └── demo/
            └── demo-1.0.0.zip
```

### 2.2 与旧简图差异

早期文档曾写扁平路径 `projects/demo-1.0.0.zip`。**以 manifest 内 `zipUrl` 为准**；Launcher 代码使用：

```cpp
const QDir usbRoot = QFileInfo(usbManifestPath).absoluteDir();
const QString zipPath = usbRoot.filePath(zipUrl);
```

因此推荐 **`projects/{name}/{name}-{version}.zip`**，与在线站布局 **一致**（便于同一 manifest 复用于线上与 U 盘）。

---

## 3. `manifest.json`

字段、签名、canonical JSON 规则 **与在线站完全相同**。  
见：[在线更新站部署规范.md](./在线更新站部署规范.md) §3～§4。

**同一份已签名 manifest** 可同时：

1. 置于 `https://www.conbos.cn/CodePlanetr/Update/manifest.json`  
2. 复制到 `U:/CodePlanetrUpdate/manifest.json`

相对路径 `zipUrl` 不变；只需保证 U 盘上对应 zip 文件存在。

---

## 4. 项目 zip 规范

与在线站 §5 相同：

| 项 | 规则 |
|----|------|
| 命名 | `{name}-{version}.zip` |
| 路径 | `CodePlanetrUpdate/projects/{name}/{name}-{version}.zip` |
| 内容 | 解压到 `USER/projects/{folder}/`；含 `demo.yslib`、`scripts/` 等 |
| 校验 | manifest 中 `zipSha256` 为 zip 的 SHA256（hex 小写） |
| 覆盖策略（R3 Apply） | 解压时 **保留目标目录已有 `logs/`** |

### 4.1 打包命令示例（PowerShell）

```powershell
$projectDir = "E:\HuaweiMoveData\Users\MateBookXPro\Desktop\CodePlanetr\USER\projects\demoV1.0"
$outZip = "E:\CodePlanetrUpdate\projects\demo\demo-1.0.0.zip"
New-Item -Force -ItemType Directory (Split-Path $outZip) | Out-Null
Compress-Archive -Path "$projectDir\*" -DestinationPath $outZip -Force
Get-FileHash -Algorithm SHA256 $outZip
```

---

## 5. 公钥与客户端

- U 盘 **不包含** 私钥；仅携带 manifest 与数据包  
- 验签公钥来自 **已安装软件**：`{InstallRoot}/config/update.pub`  
- 验签失败 → Launcher 拒绝 **该次更新建议**，**不阻断** 用本地旧项目/旧版启动（T31）

---

## 6. Launcher 行为（R3）

```mermaid
flowchart TD
  start[UpdateCheck] --> usb{U盘 manifest 存在?}
  usb -->|是| readUsb[读取 U 盘 manifest.json]
  usb -->|否| online[HTTPS 拉取 conbos manifest]
  readUsb --> verify[RSA 验签]
  online --> verify
  verify -->|失败| skip[记录日志; 继续 Launch 旧版]
  verify -->|成功| zipCheck{U盘: zip 是否存在?}
  zipCheck -->|缺失| warn[Warning + 继续旧版]
  zipCheck -->|齐全| suggest[建议更新 UI]
  suggest --> applyChoice{用户 Apply?}
  applyChoice -->|Skip| launchOld[Launch 当前版本]
  applyChoice -->|Apply| sha256[zip SHA256]
  sha256 --> staging[解压到 staging]
  staging --> validator[ProjectValidator]
  validator --> replace[原子替换; 保留 logs/]
  replace -->|失败| rollback[回滚备份]
  replace -->|成功| launchNew[Launch 新版本]
```

| 场景 | 预期（R3） |
|------|----------------|
| U 盘无 `CodePlanetrUpdate/` | 走在线；在线失败则跳过 |
| manifest 验签失败 | 日志 `Update rejected`；**可 Launch** |
| manifest 有效但 zip 缺失 | 日志缺包；**可 Launch** |
| manifest 有效且 zip 齐全 | 显示 Apply / Skip |
| Apply 成功 | 项目目录更新；`logs/` 保留；Launch 新 yslib |
| Apply 失败 | 可读错误；旧项目可运行；备份在 `%LOCALAPPDATA%/CodePlanet/update/backup/` |

---

## 7. 制作 U 盘步骤（运维）

1. 格式化 U 盘（FAT32/exFAT，便于产线 Windows 识别）  
2. 创建 `CodePlanetrUpdate/` 及 `projects/{name}/`  
3. 打包项目 zip，计算 SHA256  
4. 编写 manifest → canonical JSON → RSA 签名  
5. 复制 `manifest.json` 与 zip 到 U 盘  
6. 插入产线 PC，启动 Launcher，确认日志/界面行为  
7. （可选）使用 `examples/update/manifest.bad-signature.json` 验证验签失败不阻断  

---

## 8. 测试

**在线/U 盘 E2E 统一在 R3 Apply 实装后执行**，见 [`R3-更新Apply与E2E测试计划.md`](../AST审计执行报告/R3-更新Apply与E2E测试计划.md)。

---

## 9. 参考

- [在线更新站部署规范.md](./在线更新站部署规范.md)
- [`docs/schemas/update-manifest-v1.schema.json`](../schemas/update-manifest-v1.schema.json)
- [`docs/附录-路径与日志约定.md`](../附录-路径与日志约定.md)
