From 5597a4cc18a51313d2e25357cb7d7487ff570dc0 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Fri, 6 Nov 2026 21:08:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(release,docs):=20M149=20=E5=8F=91=E5=B8=83?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E7=83=98=E5=85=A5=E5=BE=AE=E8=BD=AF=E7=99=BB?= =?UTF-8?q?=E5=BD=95=20id=EF=BC=88=E4=BB=93=E5=BA=93=20Secret=EF=BC=89?= =?UTF-8?q?=EF=BC=8C=E6=96=87=E6=A1=A3=E6=94=B9=E4=B8=BA=E3=80=8C=E5=AE=98?= =?UTF-8?q?=E6=96=B9=E6=9E=84=E5=BB=BA=E5=BC=80=E7=AE=B1=E5=8D=B3=E7=94=A8?= =?UTF-8?q?=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 构建侧:release-build.yml 声明 on.workflow_call.secrets.ULL_MSA_CLIENT_ID(required: false), cargo build 步以 env 注入它,option_env! 把它烘进二进制;release.yml 的 build job 按名显式透传 (与 PUBLISH_TOKEN 同一模式,不用 inherit)。真值放仓库 Secret,不进任何 git 文件;Secret 未录入 或 Actions 之外求值为空串时,client_id.rs 的 blank_env_value_counts_as_missing 保证「空白=没配」, 不会拿空 id 去联网。 文档:README 两版 / help.rs 的 accounts topic / packaging/release-notes.md / packaging/docs/build-and-ci.md 统一成「官方构建已内置登录所需配置、开箱即用,仍可用环境变量 ULL_MSA_CLIENT_ID 覆盖;自建想自带就构建时注入」的功能性口径 —— 不出现 id 本身,也不出现归属描述。 client_id.rs 的模块注释与测试注释同步(原文写「id 不进仓库(用户定案)」,已随 Secret 方案改写), 并记下「别把 ULL_MSA_CLIENT_ID 导出到跑 cargo test 的 shell」——presenter 的 M132 入口锚会在 已烘焙 id 的构建里故意红。 实测:ULL_MSA_CLIENT_ID= cargo build --bin ull 后,清空 env 跑 ull accounts add msa 打出 用户码与链接(无「未配置」提示);同二进制设一个假 env id 则 AADSTS700016 点名该假 id(env 覆盖生效)。 单测:core auth 28、unlucky-cli 全套、i18n 30、presenter 222 全绿;M132 锚在烘焙构建里按设计红、 在不带 id 的构建里绿。cargo fmt --all --check 通过。 --- .github/workflows/release-build.yml | 14 +++++++++++ .github/workflows/release.yml | 6 +++++ README.md | 26 ++++++++++++--------- README.zh-CN.md | 17 ++++++++------ crates/cli/src/cli/help.rs | 8 +++---- crates/core/src/auth/microsoft/client_id.rs | 13 ++++++++--- packaging/docs/build-and-ci.md | 8 +++++++ packaging/release-notes.md | 16 +++++++------ 8 files changed, 76 insertions(+), 32 deletions(-) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 45a9ccd..f4e633d 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -29,6 +29,15 @@ on: type: string required: false default: "" + # (M149)微软登录用的 Azure 公共客户端 id(公共客户端 id 随二进制一起分发,不按高密级处理,但**不进 git + # 文件**:真值放仓库 Secret,用户在仓库设置里录入)。`required: false` 是刻意的——调用方没传(本地、 + # 没有 runner 的镜像站、以及 Secret 尚未录入时)求值为空串,`client_id.rs` 把空白当「没配」, + # 于是非官方构建不烘 id,官方构建才烘。**调用方必须显式传**(`secrets: inherit` 或按名传); + # 没传时这里是空,不会报错。 + secrets: + ULL_MSA_CLIENT_ID: + description: "Azure 公共客户端 id,烘进发布二进制(构建期 option_env!);留空 = 不烘" + required: false env: CARGO_TERM_COLOR: always @@ -119,10 +128,15 @@ jobs: # M118(D26):**Slint 版停发** —— GUI 只有 `unlucky-launcher-iced`(`crates/app` 已 exclude,不进 workspace)。 # 产物名由 make_artifacts.py 的 `asset_stem()` 生成 `<名字>_<版本号>_<平台>_<架构>`;不传 `--target`(M140 起全原生)。 + # (M149)`ULL_MSA_CLIENT_ID` 经 `option_env!` 烘进二进制(`crates/core/src/auth/microsoft/client_id.rs`): + # 官方产物自带微软登录所需的配置,装完即可登录;运行期同名环境变量仍优先于它,可覆盖。 + # 值来自仓库 Secret(不进 git 文件);Secret 没配时这里是空串,client_id.rs 把空白当「没配」, + # 于是非官方构建不烘 id。想自己烘一份就从源码构建:`ULL_MSA_CLIENT_ID= cargo build`。 - name: cargo build --release --locked shell: bash env: RUSTFLAGS: ${{ inputs.rustflags }} + ULL_MSA_CLIENT_ID: ${{ secrets.ULL_MSA_CLIENT_ID }} run: | set -euo pipefail cargo build --release --locked --bin unlucky-launcher-iced --bin ull diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d090b33..7ea7b91 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -103,6 +103,12 @@ jobs: os: ${{ matrix.os }} build_dir: ${{ matrix.build_dir }} rustflags: ${{ matrix.rustflags }} + secrets: + # (M149)微软登录的公共客户端 id:值来自仓库 Secret,被调方把它烘进发布二进制(`option_env!` 注入)。 + # 可复用工作流拿不到调用方未显式传入的 secret,故必须按名传(与下面 PUBLISH_TOKEN 同一模式, + # 不用 `inherit`:那会把仓库全部 secret 一起透进被调方)。Secret 未录入时求值为空串,被调方把空白 + # 当「没配」——产物只是不带 id,不会拿空 id 去联网。 + ULL_MSA_CLIENT_ID: ${{ secrets.ULL_MSA_CLIENT_ID }} release: name: GitHub Release diff --git a/README.md b/README.md index 5ae698a..a799e98 100644 --- a/README.md +++ b/README.md @@ -83,16 +83,17 @@ Three tracks on one page: - **Third-party community auth servers** (authlib-injector style). - **Local offline profiles**, for isolated testing. -**Microsoft sign-in needs an Azure public client id, and no release binary carries one.** The id is -not in this repo and CI injects none, so set `ULL_MSA_CLIENT_ID=` before launching, or build with -it (`ULL_MSA_CLIENT_ID= cargo build`, which bakes it in via `option_env!`). Without one, sign-in -stops before any network call and the alert says exactly what is missing: the title "Microsoft -sign-in is not configured", a body naming `ULL_MSA_CLIENT_ID=` (set it, or build with it, then -restart the launcher — a running process does not pick up a new variable), and -`Code: msa_not_configured`. It offers no Retry button, because retrying in that same process cannot -help. Every other sign-in failure keeps the older shape: one category line plus its own `Code:` -line, with the full reason only in the launcher log. Community auth servers and local profiles need -no id. +**Microsoft sign-in works out of the box in official builds.** Release binaries carry the Azure public +client id they need (a public client id, not a credential — it ships inside the binary), so there is +nothing to configure before signing in. Setting `ULL_MSA_CLIENT_ID=` still overrides it: the +variable is read at startup, so restart the launcher after changing it. Building from source bakes in +whatever that variable holds at build time (`ULL_MSA_CLIENT_ID= cargo build`). A build with no id +anywhere stops before any network call, and its alert says exactly what is missing: the title +"Microsoft sign-in is not configured", a body naming `ULL_MSA_CLIENT_ID=` (set it, or build with +it, then restart), and `Code: msa_not_configured`. It offers no Retry button, because retrying in that +same process cannot help. Every other sign-in failure keeps the older shape: one category line plus +its own `Code:` line, with the full reason only in the launcher log. Community auth servers and local +profiles need no id. ### Look and language @@ -243,7 +244,7 @@ upstream cargo. On Linux, X11 is the preferred path today and Wayland is the lon Capability differences are feature gates with runtime probing, never a compile-time "this platform gets less". -First run with a Microsoft account? Set `ULL_MSA_CLIENT_ID` first — see +First run with a Microsoft account? Nothing to configure — official builds carry the client id; see [Accounts](#accounts). Nothing else needs configuring. ## Build from source @@ -266,6 +267,9 @@ cargo deny check licenses bans sources # dependency licences (re What the build produces: +- A build from source carries no Microsoft client id by default. Set `ULL_MSA_CLIENT_ID` at build time + to bake one in (`ULL_MSA_CLIENT_ID= cargo build`); official release binaries already carry one + — see [Accounts](#accounts). - `unlucky-launcher-iced` — the GUI, and the CLI in the same file: it answers every subcommand listed above. `iced` is cargo's bin name, not a download name — `packaging/make_artifacts.py` stages that same build as `unlucky-launcher___` and names the binary diff --git a/README.zh-CN.md b/README.zh-CN.md index 798e20b..eb2bc09 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -68,12 +68,13 @@ Unlucky Launcher 是一个用 Rust 写的免费开源 **Minecraft: Java 版**启 - **第三方社区认证服**(authlib-injector 一类)。 - **本地离线档案**,用于隔离测试。 -**微软登录需要一个 Azure 公共客户端 id,而发布版二进制里没有。** 这个 id 不在仓库里,CI 也不注入,所以 -启动前设 `ULL_MSA_CLIENT_ID=`(或构建时注入:`ULL_MSA_CLIENT_ID= cargo build`,它通过 -`option_env!` 烧进二进制)。没有它时点登录**不会联网**,弹窗直接说清是这一项没配:标题「微软登录未配置」, -正文点名 `ULL_MSA_CLIENT_ID=`(设好或构建时注入后要**重启启动器**——已在运行的进程读不到新设的环境 -变量),并带 `代码:msa_not_configured`;它**不给重试按钮**,因为在同一个进程里重试必然是同一个错。其它 -登录失败仍是老样子:一句分类文案 + `代码:<码>`,完整原因只在启动器日志里。第三方社区服与本地档案不需要 id。 +**微软登录在官方构建里开箱即用。** 发布版二进制已内置它需要的 Azure 公共客户端 id(公共客户端 id 不是 +凭据,本来就随二进制一起分发),所以登录前不需要任何配置。设 `ULL_MSA_CLIENT_ID=` 仍可覆盖内置值: +变量在启动时读取,改完要**重启启动器**。从源码构建时该变量有什么就烘什么(`ULL_MSA_CLIENT_ID= cargo build`, +经 `option_env!` 烧进二进制)。两处都没有 id 的构建点登录**不会联网**,弹窗直接说清是这一项没配:标题 +「微软登录未配置」,正文点名 `ULL_MSA_CLIENT_ID=`(设好或构建时注入后重启),并带 +`代码:msa_not_configured`;它**不给重试按钮**,因为在同一个进程里重试必然是同一个错。其它登录失败仍是 +老样子:一句分类文案 + `代码:<码>`,完整原因只在启动器日志里。第三方社区服与本地档案不需要 id。 ### 外观与语言 @@ -197,7 +198,7 @@ Linux x64 / arm64(基线判定在 `crates/core/src/platform/os_info.rs`)。W (MSVC / GNU)构建,构建方式与上游 cargo 一致。Linux 上 X11 是当前首选路径,Wayland 是大方向。能力差异 一律用 feature gate + 运行期探测表达,绝不做成「这个平台天生少一块」的编译期裁剪。 -第一次用微软账户?先设 `ULL_MSA_CLIENT_ID`——见[账户](#账户)。其它都不需要配置。 +第一次用微软账户?不用配置——官方构建已内置客户端 id,见[账户](#账户)。其它都不需要配置。 ## 从源码构建 @@ -218,6 +219,8 @@ cargo deny check licenses bans sources # 依赖许可(CI 必 编出来的 bin: +- 从源码构建默认不带微软客户端 id:想让产物自带,就在构建时设 `ULL_MSA_CLIENT_ID`(`ULL_MSA_CLIENT_ID= + cargo build`);官方发布产物已内置,见[账户](#账户)。 - `unlucky-launcher-iced`——GUI,同时也是 CLI:同一个文件认上面列的全部子命令。名字里的 `iced` 是 cargo 的 bin 名,不是下载名——`packaging/make_artifacts.py` 把同一次构建落成 `unlucky-launcher_<版本>_<平台>_<架构>`,归档里的二进制名写成 `unlucky-launcher`。 diff --git a/crates/cli/src/cli/help.rs b/crates/cli/src/cli/help.rs index ea3a448..9a1ce60 100644 --- a/crates/cli/src/cli/help.rs +++ b/crates/cli/src/cli/help.rs @@ -133,10 +133,10 @@ accessToken 一律脱敏(输出里出现的是 )。", **分文件**存放,先写凭据后写元数据、原子写、凭据文件 POSIX 侧 0600。 凭据永不打印:list 只输出名称/轨道/UUID。 add msa 走微软 device-code(用户码打在 stderr,去 https://www.microsoft.com/link 输码)。 -它需要一个 Azure 公共客户端 id —— 登录链路是完整的,缺的只是这一项配置;id 不进仓库: - ULL_MSA_CLIENT_ID= %prog% accounts add msa 每次运行时给(环境变量优先) - ULL_MSA_CLIENT_ID= cargo build 构建时注入,二进制自带 -两处都没有时命令立刻报「微软登录未配置」(含怎么配),**不会去联网**。 +微软登录所需配置已内置在官方构建里,装完即可用;仍可用环境变量覆盖(优先于内置值): + ULL_MSA_CLIENT_ID= %prog% accounts add msa 每次运行时给(优先于内置值) + ULL_MSA_CLIENT_ID= cargo build 自己从源码构建时注入,二进制自带 +两处都没有时(只有自建且不自带 id 才会这样)命令立刻报「微软登录未配置」(含怎么配),**不会去联网**。 authlib-injector 账户的密码登录留给 GUI(密码进 argv 会泄露,CLI 不开这条通道)。", ), "sessions" => Some("%prog% sessions list [--limit N] 列出会话索引(按开始时间倒序,缺省 20 条)"), diff --git a/crates/core/src/auth/microsoft/client_id.rs b/crates/core/src/auth/microsoft/client_id.rs index d7194fb..9a5a648 100644 --- a/crates/core/src/auth/microsoft/client_id.rs +++ b/crates/core/src/auth/microsoft/client_id.rs @@ -2,8 +2,9 @@ //! //! ## 为什么单独一块(M117) //! -//! 这个 id 不进仓库(用户定案),所以它可以来自两处:运行期环境变量 [`CLIENT_ID_ENV`], -//! 或构建期 `option_env!` 注入([`CLIENT_ID_BUILD`])。**两处都没有**时登录是「没配置」, +//! 这是**公共客户端 id**(非机密,随二进制一起分发),可以来自两处:运行期环境变量 [`CLIENT_ID_ENV`], +//! 或构建期 `option_env!` 注入([`CLIENT_ID_BUILD`])——官方发布构建在构建时烘入它(M149), +//! 所以官方产物开箱即用;环境变量仍优先,可覆盖。**两处都没有**时登录是「没配置」, //! 不是「没实现」——这两件事在用户眼里完全不同,文案与日志都必须分清: //! //! | 现象 | 该说什么 | @@ -20,6 +21,9 @@ pub const CLIENT_ID_ENV: &str = "ULL_MSA_CLIENT_ID"; /// 构建期可注入的 client id(`ULL_MSA_CLIENT_ID= cargo build`)。 +/// +/// 官方发布构建就是这么烘的(`.github/workflows/release-build.yml` 给构建步设了同名环境变量,M149; +/// 值来自仓库 Secret,不进 git 文件),所以官方产物走到这里恒为 `Some`;从源码自建时才可能为空。 pub const CLIENT_ID_BUILD: Option<&str> = option_env!("ULL_MSA_CLIENT_ID"); /// 两处都没配时给用户看的那句话(CLI / GUI / 启动日志共用,**不含任何真实 id**)。 @@ -180,7 +184,10 @@ mod tests { assert_eq!(message, MISSING_CLIENT_ID_MESSAGE); assert!(!message.contains("未支持")); } - // 构建期注入过 id 时唯一合法(本仓库 CI/本地都不注入,走不到这里)。 + // 构建期注入过 id 时唯一合法(`ULL_MSA_CLIENT_ID` 也设进 `cargo test` 的环境就会走到这里; + // 官方发布构建即烘了 id,但门禁里的 `cargo test` 不带该变量,故常态走上面那条。 + // 注意:把该变量导出到跑测试的 shell 会让 presenter 的 M132 入口锚**故意红** + // (它要求「两处都没有 id」的构建,见 crates/presenter/src/host/accounts/tests.rs)。 Ok(_) => assert!( CLIENT_ID_BUILD.is_some_and(|value| !value.trim().is_empty()), "没配 id 却构造成功" diff --git a/packaging/docs/build-and-ci.md b/packaging/docs/build-and-ci.md index 2183176..e590630 100644 --- a/packaging/docs/build-and-ci.md +++ b/packaging/docs/build-and-ci.md @@ -75,6 +75,14 @@ python packaging/make_artifacts.py --slot windows-arm64 \ 逐字搬进 `.github/workflows/release-publish.yml`(同 `workflow_call`,`secrets.GITHUB_TOKEN` 改为调用方显式透传 的 secret)。触发(`on`)、`env`、顶层 `permissions`、`concurrency` 与 `plan` 的槽位表**一字未动**;改槽位仍只改 `release.yml` 那一处(`PUBLISH_SLOTS` 白名单在 `release-publish.yml`,两个 arm64 修复时要改的仍是那两行)。 +- **M149 微软登录 id 的烘焙**:`release-build.yml` 的 `cargo build` 步带 `env.ULL_MSA_CLIENT_ID`(值 + `${{ secrets.ULL_MSA_CLIENT_ID }}`),`option_env!` 把它烘进二进制,于是发布产物装完即可登录。id **不进 + git 文件**:真值放仓库 Secret,由 `release.yml` 的 `build` job 按名显式透传(可复用工作流拿不到未显式传入的 + secret;这里刻意不用 `secrets: inherit`——那会把仓库全部 secret 一起透进被调方)。Secret 未录入、或在整个 + Actions 之外求值时该表达式为空串,`client_id.rs` 把空白当「没配」⇒ 非官方构建只是不烘 id,不会拿空 id 去 + 联网;本地自烘仍走 `ULL_MSA_CLIENT_ID= cargo build`。**别把这个 env 导出到跑 `cargo test` 的 shell**: + presenter 的 M132 入口锚会在「已烘焙 id」的构建里**故意红**(它要求一个两处都没有 id 的构建)。 +- 发布说明模板(`release-notes.md`)的 "Known limitations" 里,微软登录一条已改为「开箱即用、env 可覆盖」。 Windows 为什么用 MSVC 静态 CRT 而不是本机这套 GNU 工具链:两者都属"标准工具链"(D21), 但发布产物要**不依赖 VC++ 可再发行包**,`+crt-static` 把 CRT 静态链进 exe,Win10+ 的 UCRT 本身是 diff --git a/packaging/release-notes.md b/packaging/release-notes.md index b567b5c..3a3949b 100644 --- a/packaging/release-notes.md +++ b/packaging/release-notes.md @@ -100,14 +100,16 @@ Get-FileHash .\unlucky-launcher_{{VERSION}}_windows_x64.exe -Algorithm SHA256 ## Known limitations of this build -- Microsoft (MSA) sign-in needs an Azure public client id, and release binaries do not carry one: set - `ULL_MSA_CLIENT_ID=` before launching, or build with that variable set. Without it sign-in stops +- Microsoft (MSA) sign-in works out of the box: this build carries the Azure public client id it needs + (a public client id, not a credential — it ships inside the binary), so there is nothing to configure + before signing in. Setting `ULL_MSA_CLIENT_ID=` before launching still overrides it; the variable + is read at startup, so restart the launcher after changing it. A build with no id anywhere stops before any network call, and the GUI banner says so plainly: the title "Microsoft sign-in is not - configured", a body naming `ULL_MSA_CLIENT_ID=` (set it, or build with it, then restart the - launcher — a running process does not pick up a new variable) and `Code: msa_not_configured`, with - no Retry button. Every other sign-in failure keeps the generic banner plus its own `Code:` line, - with the full reason only in the launcher log. `ull accounts add msa` prints the same fix on the - command line. Offline profiles and authlib-injector servers need no id. + configured", a body naming `ULL_MSA_CLIENT_ID=` (set it, or build with it, then restart) and + `Code: msa_not_configured`, with no Retry button. Every other sign-in failure keeps the generic + banner plus its own `Code:` line, with the full reason only in the launcher log. `ull accounts add + msa` prints the same fix on the command line. Offline profiles and authlib-injector servers need no + id. - Binaries are not code-signed / notarized yet: Windows SmartScreen and macOS Gatekeeper warn on first launch. The checksums above are the integrity reference for now. - When the macOS slot ships it is the bare binary (single-file promise), not a `.app` bundle: no Dock