Merge branch 'feat/m149-msabake-bake-approved-client-id-int'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好一个文件) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s

This commit is contained in:
AskaEth committed 2026-11-06 21:21:30 +08:00
commit 87ab8783a3
8 files changed
+76 -32

No files matched your search

+14
View File
@@ -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=<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
+6
View File
@@ -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
+15 -11
View File
@@ -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=<id>` before launching, or build with
it (`ULL_MSA_CLIENT_ID=<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=<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=<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=<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=<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=<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_<version>_<platform>_<arch>` and names the binary
+10 -7
View File
@@ -68,12 +68,13 @@ Unlucky Launcher 是一个用 Rust 写的免费开源 **Minecraft: Java 版**启
- **第三方社区认证服**(authlib-injector 一类)。
- **本地离线档案**,用于隔离测试。
**微软登录需要一个 Azure 公共客户端 id,而发布版二进制里没有。** 这个 id 不在仓库里,CI 也不注入,所以
启动前设 `ULL_MSA_CLIENT_ID=<id>`(或构建时注入:`ULL_MSA_CLIENT_ID=<id> cargo build`,它通过
`option_env!` 烧进二进制)。没有它时点登录**不会联网**,弹窗直接说清是这一项没配:标题「微软登录未配置」,
正文点名 `ULL_MSA_CLIENT_ID=<id>`(设好或构建时注入后要**重启启动器**——已在运行的进程读不到新设的环境
变量),并带 `代码:msa_not_configured`;它**不给重试按钮**,因为在同一个进程里重试必然是同一个错。其它
登录失败仍是老样子:一句分类文案 + `代码:<码>`,完整原因只在启动器日志里。第三方社区服与本地档案不需要 id。
**微软登录在官方构建里开箱即用。** 发布版二进制已内置它需要的 Azure 公共客户端 id(公共客户端 id 不是
凭据,本来就随二进制一起分发),所以登录前不需要任何配置。设 `ULL_MSA_CLIENT_ID=<id>` 仍可覆盖内置值:
变量在启动时读取,改完要**重启启动器**。从源码构建时该变量有什么就烘什么(`ULL_MSA_CLIENT_ID=<id> cargo build`,
经 `option_env!` 烧进二进制)。两处都没有 id 的构建点登录**不会联网**,弹窗直接说清是这一项没配:标题
「微软登录未配置」,正文点名 `ULL_MSA_CLIENT_ID=<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=<id>
cargo build`);官方发布产物已内置,见[账户](#账户)。
- `unlucky-launcher-iced`——GUI,同时也是 CLI:同一个文件认上面列的全部子命令。名字里的 `iced` 是 cargo
的 bin 名,不是下载名——`packaging/make_artifacts.py` 把同一次构建落成
`unlucky-launcher_<版本>_<平台>_<架构>`,归档里的二进制名写成 `unlucky-launcher`。
+4 -4
View File
@@ -133,10 +133,10 @@ accessToken 一律脱敏(输出里出现的是 <redacted:token>)。",
**分文件**存放,先写凭据后写元数据、原子写、凭据文件 POSIX 侧 0600。
凭据永不打印:list 只输出名称/轨道/UUID。
add msa 走微软 device-code(用户码打在 stderr,去 https://www.microsoft.com/link 输码)。
它需要一个 Azure 公共客户端 id —— 登录链路是完整的,缺的只是这一项配置;id 不进仓库:
ULL_MSA_CLIENT_ID=<id> %prog% accounts add msa 每次运行时给(环境变量优先)
ULL_MSA_CLIENT_ID=<id> cargo build 构建时注入,二进制自带
两处都没有时命令立刻报「微软登录未配置」(含怎么配),**不会去联网**。
微软登录所需配置已内置在官方构建里,装完即可用;仍可用环境变量覆盖(优先于内置值):
ULL_MSA_CLIENT_ID=<id> %prog% accounts add msa 每次运行时给(优先于内置值)
ULL_MSA_CLIENT_ID=<id> cargo build 自己从源码构建时注入,二进制自带
两处都没有时(只有自建且不自带 id 才会这样)命令立刻报「微软登录未配置」(含怎么配),**不会去联网**。
authlib-injector 账户的密码登录留给 GUI(密码进 argv 会泄露,CLI 不开这条通道)。",
),
"sessions" => Some("%prog% sessions list [--limit N] 列出会话索引(按开始时间倒序,缺省 20 条)"),
+10 -3
View File
@@ -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=<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 却构造成功"
+8
View File
@@ -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=<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 本身是
+9 -7
View File
@@ -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=<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=<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=<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=<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