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

3.5 KiB
Raw Blame History

自更新的信任锚(PRODUCTION 表)到底钉了什么

M308 纠偏。本文只做两件事:作废一条过时读数,并把「现在钉着什么、怎么当场地看一眼」写清楚。

M254 审计的「正式表为空」已作废

M254(CLAIMAUDIT)当时读到 crates/core/src/update/trust.rs 的 PRODUCTION 表在 PINNED-KEYS 标记之间是空的,于是断言「任何正式二进制都验不过任何清单」。那条读数 作废:commit 68362e5(feat(core): 钉入生产签名公钥(key_id ull-release))已经往 标记之间钉了一条:

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(发布流水线跑)。

当场看一眼(只读命令)

# ① 信任表里有几条(标记之间)
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)。