Files
Unlucky-Launcher/docs/update-trust-anchor.md
T
AskaEth af82658d08 fix(core,app-iced): M308 Linux/macOS 的 .tar.gz 真的能自替换 + 重试按 retryable 分流 + 三处文档纠偏
M307 盘查抓到的一条真缺陷 + 两处交互/文档不一致,本条只修它们,不重做通道:

① Linux/macOS 自更新的真缺陷(选 A:真做)
   `job::self_replaceable` 只认裸 exe,而发布矩阵里那两平台的启动器是 `.tar.gz`(清单记作
   `portable-zip`)⇒ 查得到更新、按钮画得出来、点下去必失败。新增 `core::update::unpack`:
   **归档字节过完长度/摘要双检之后**,从归档里取出启动器本体(tar 头 mode 有可执行位 /
   `.exe` 名字的唯一普通文件),再交给原有的 `install_staged`(原子替换 + 回滚一字未改)。
   零新依赖(flate2/tar 早就是 core 的依赖,`core::game::java` 解 Temurin 用同一套)。
   三条具名错误:`LauncherMissing` / `LauncherNotExecutable`(魔数与 target 不符)/
   `LauncherSizeMismatch`(0 字节 / 超上限 / 与头声明不符);zip 容器**明确拒绝**(没有可靠的
   「哪个条目可执行」判据,不猜)。`target.rs` 的 `PackageFormat` 文档改成与判据逐字一致。

② 重试按钮按 `retryable` 分流
   验签类(`is_retryable == false`)不画「重试」,正文换成新 key `UpdateBannerFailedFinal`
   (「这次更新已被拒绝(重试没用):{reason}」)。文案抽到 `pages/update/failure.rs`
   (`pages/update.rs` 的红线),判据落在**真 Widget 树**上(探针 tag + 递归计数)。

③ 三处文档纠偏 + 同一条谎话的其它落点(都在 scope 内)
   `trust.rs`(正式表已有 `ull-release`,commit 68362e5)、`check_signing_ready.py` 自述、
   `target.rs`;顺带 `packaging/README.md`、`docs/{signing,signing-runbook}.md`、
   `check_test_key_off.py`;新文档 `docs/update-trust-anchor.md` 记「M254 审计的『正式表为空』
   已作废」。

判据:core 新增 `unpack_cases`(Linux tar.gz / macOS tar.gz 走完整条链 + 五种拒绝)、
`job_cases` 的形态表改成两种;app-iced 两条真 Widget 树用例(按钮在 / 不在)+ 一条取词入口用例
+ 一条源码守卫。i18n 加一条 key 并**重出金表**(`Key::ALL` 853 行,新行落在 index 834)。
2026-11-13 23:38:17 +08:00

64 lines
3.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 自更新的信任锚(`PRODUCTION` 表)到底钉了什么
M308 纠偏。本文只做两件事:**作废一条过时读数**,并把「现在钉着什么、怎么当场地看一眼」写清楚。
## M254 审计的「正式表为空」已作废
M254(CLAIMAUDIT)当时读到 `crates/core/src/update/trust.rs` 的 `PRODUCTION` 表在
`PINNED-KEYS` 标记之间**是空的**,于是断言「任何正式二进制都验不过任何清单」。**那条读数
作废**:commit `68362e5`(`feat(core): 钉入生产签名公钥(key_id ull-release)`)已经往
标记之间钉了一条:
```text
PinnedKey { key_id: "ull-release", public_key_hex: "1c3687ec…" }
```
从那以后,**由 `ull-release` 私钥签过的清单,正式构建会照常验签通过**。M308 之前 `trust.rs`
顶部那段文档还写着「正式表当前是空的(fail-closed)」,与代码直接矛盾——这正是 M307 盘查抓到的
三处文档不一致之一,M308 已改。
「表里有钥」**不**降低 fail-closed 的性质:签名缺失 / 签名文件坏 / `key_id` 不认得 / 签名对不上
(`UpdateErrorKind` 的签名五兄弟)一律拒装,绝不「没钥就放行」。表里那把钥只是把「验得过」这条
路打开,不是把闸门拆掉。
## 现在的表长什么样
| 表 | 内容 | 谁看得见 |
|---|---|---|
| `PRODUCTION` | 1 条:`ull-release` | **所有**构建(含官方发布) |
| `TEST_ONLY` | 1 条:`test-only-key`(种子公开写在 `tests/support.rs`) | 只在 `cfg(test)` / `update-test-source` / `update-test-key` 三档构建里**编进去**;其余构建里它是空表,那串十六进制连字节都不在产物里 |
三档守门脚本:`packaging/check_test_source_off.py` / `check_test_key_off.py`(发布流水线跑)。
## 当场看一眼(只读命令)
```bash
# ① 信任表里有几条(标记之间)
python3 packaging/sign_manifest.py --check-pinned-keyring
# ② 就绪度播报(三行:表非空 / 环境里有钥且同源 / 最近一次发布痕迹)
python3 packaging/check_signing_ready.py
# ③ 直接读表
sed -n '/PINNED-KEYS/,/PINNED-KEYS/p' crates/core/src/update/trust.rs
```
`check_signing_ready.py` 的 ① 那一行的判据是「标记之间有几条 `PinnedKey`」,**不是**「表是不是
空的」——所以哪天有人把标记之间清空,它会立刻喊未就绪(那句文案里的「空表」说的是那一天)。
## 加/换钥的纪律(没变)
只能**旧钥签新钥**:那次发布的清单同时带旧钥签名,changelog 点名新钥的 `key_id` 与指纹,
然后往 `PINNED-KEYS` 标记之间加一条。步骤全文见 `packaging/docs/signing.md`。
## 同一轮纠偏的其它两处(M308)
* `crates/core/src/update/target.rs` 的 `PackageFormat` 文档此前写「portable-zip / appimage /
portable-exe 才走自替换」,与 `job::self_replaceable`(只认裸 exe)直接矛盾;现在判据是
**便携 exe + 便携归档**两种,两边逐字一致。
* Linux/macOS 的 `.tar.gz` 以前「查得到更新、按钮画得出来、点下去必失败」;M308 在
`crates/core/src/update/unpack.rs` 里补了「校验通过之后拆出启动器本体」这一跳,于是那两平台
的自更新**真的能装**。顺序不变:清单的 `size`/`sha256` 仍然只对**归档字节**做双检。
* 界面「重试」按钮现在按 `retryable` 分流:验签类失败不画重试,改说「这次更新已被拒绝
(重试没用)」(`Key::UpdateBannerFailedFinal`)。