From 57017ee04678dc3e6de214025b7c44ebb0946582 Mon Sep 17 00:00:00 2001 From: suzmii Date: Sun, 20 Sep 2026 19:11:30 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8DMac=E5=85=A5=E5=8F=A3?= =?UTF-8?q?=E8=AF=AF=E7=94=A8--no-sign=E5=AF=BC=E8=87=B4=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=8C=85=E6=97=A0=E7=AD=BE=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移除build-macos-ci的--no-sign:该标志会连带跳过updater的minisign签名 改为剥离APPLE_*凭据跳过Apple签名,保持更新包签名能力 构建清单按codesign实测记录appleSigned与签名类型 补充回归断言与运维、更新规范口径 --- .../scripts/build-macos-ci.mjs | 24 +++++++++++++++---- .../scripts/prepare-macos-codex.test.mjs | 5 ++++ ...方案】AGC客户端更新检查与下载-2026-08-31.md | 2 +- ...发运维】本地开发验证与生产运维-2026-05-15.md | 2 +- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/apps/ai-game-creator-shell/scripts/build-macos-ci.mjs b/apps/ai-game-creator-shell/scripts/build-macos-ci.mjs index 56cab0bd2..651e342d0 100644 --- a/apps/ai-game-creator-shell/scripts/build-macos-ci.mjs +++ b/apps/ai-game-creator-shell/scripts/build-macos-ci.mjs @@ -1,5 +1,5 @@ import assert from 'node:assert/strict'; -import { execFileSync } from 'node:child_process'; +import { execFileSync, spawnSync } from 'node:child_process'; import { createHash } from 'node:crypto'; import fs from 'node:fs'; import os from 'node:os'; @@ -24,7 +24,9 @@ import { * → 生成分区清单 latest.json → 用产物内烘焙的公钥验签 → 按 dry-run 决定是否上传 OSS。 * * 边界: - * - Apple 签名与公证暂缺,产物为未签名 + 未公证(`--no-sign`),必须显式记录而非静默通过; + * - Apple 签名与公证暂缺:本入口剥离 `APPLE_*` 凭据让 Tauri 跳过 Apple 签名,但**不能传 + * `--no-sign`** —— 该标志同时会跳过 updater 的 minisign 签名,产物就没有 `.sig`; + * 未签名 + 未公证必须显式记录而非静默通过; * - 更新包签名(TAURI_SIGNING_PRIVATE_KEY,minisign)是硬需求:缺了客户端一律拒绝安装, * 因此构建前要求凭据存在,构建后用内置公钥复核 `.sig` 才允许继续上传; * - 未通过验签绝不写 OSS:上传顺序为更新包、签名、首装包,全部成功后才覆盖渠道清单指针。 @@ -78,7 +80,8 @@ const args = [ '--bundles', 'app', '--ci', - '--no-sign', + // 刻意不传 `--no-sign`:它会连带跳过 updater 签名,而客户端强制校验更新包签名。 + // Apple 侧改为剥离 APPLE_* 凭据,未配置身份时 Tauri 不签名也不失败。 // 基础配置已开启;这里显式声明,避免被其它配置来源关掉后静默失去更新能力。 '--config', '{"bundle":{"createUpdaterArtifacts":true}}', @@ -173,6 +176,18 @@ const commit = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: repoRoot, encoding: 'utf8', }).trim(); +// Apple 签名状态必须实测:剥离 APPLE_* 后 Tauri 通常跳过签名,但节点若装了 Developer ID +// 证书仍可能签上,硬编码 appleSigned=false 会把「其实签了」写成假事实。 +const signatureProbe = spawnSync('codesign', ['-dv', '--verbose=2', app], { + encoding: 'utf8', +}); +const signatureText = `${signatureProbe.stdout ?? ''}${signatureProbe.stderr ?? ''}`; +const appleSigned = /Authority=Developer ID Application/u.test(signatureText); +const appleSignatureKind = appleSigned + ? 'developer-id' + : /Signature=adhoc/u.test(signatureText) + ? 'adhoc' + : 'unsigned'; fs.writeFileSync( path.join(artifacts, 'build-manifest.json'), `${JSON.stringify( @@ -182,7 +197,8 @@ fs.writeFileSync( target: context.target, channel: context.channel, // Apple 签名与公证暂缺:显式记录为未验证项,不静默通过。 - appleSigned: false, + appleSigned, + appleSignatureKind, notarized: false, dryRun, uploaded: !dryRun, diff --git a/apps/ai-game-creator-shell/scripts/prepare-macos-codex.test.mjs b/apps/ai-game-creator-shell/scripts/prepare-macos-codex.test.mjs index 076dfef7c..4673179f7 100644 --- a/apps/ai-game-creator-shell/scripts/prepare-macos-codex.test.mjs +++ b/apps/ai-game-creator-shell/scripts/prepare-macos-codex.test.mjs @@ -111,4 +111,9 @@ test('macOS release entry verifies the updater signature before uploading', () = assert.ok(verifyIndex < uploadIndex, '必须先验签再上传,验不过不得写 OSS'); // 无签名私钥时禁止构建:未签名的更新包会被客户端一律拒绝。 assert.ok(entry.includes('TAURI_SIGNING_PRIVATE_KEY')); + // `--no-sign` 会连带跳过 updater 的 minisign 签名,产物将没有 .sig,入口不得传它。 + assert.ok( + !entry.includes("'--no-sign'"), + '--no-sign 会同时跳过 updater 签名,产物缺少 .sig', + ); }); diff --git a/docs/technical/【技术方案】AGC客户端更新检查与下载-2026-08-31.md b/docs/technical/【技术方案】AGC客户端更新检查与下载-2026-08-31.md index 7b8c395ae..6ec06d077 100644 --- a/docs/technical/【技术方案】AGC客户端更新检查与下载-2026-08-31.md +++ b/docs/technical/【技术方案】AGC客户端更新检查与下载-2026-08-31.md @@ -172,7 +172,7 @@ - 旧客户端迁移桥:保留一个版本周期。渠道清单上线后,发布管线同时把旧的 `agc/latest.json`(sha256 格式)指向 `dev-win` 最新安装包,让已发布客户端自动升级到新协议;下个周期整条删除。 - 签名密钥:由本仓库维护者生成并保管,私钥保存在仓库外(`%USERPROFILE%\.tauri\genarrative-agc-updater.key`),只有公钥进入客户端配置;Jenkins 用受保护凭据 `AgcUpdaterSigningKey` 与 `AgcUpdaterSigningKeyPassword` 注入为 Tauri 打包器读取的 `TAURI_SIGNING_PRIVATE_KEY` 与 `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`,本机可用 `TAURI_SIGNING_PRIVATE_KEY_PATH` 指向同一私钥。当前密钥不带密码;首次发布前仍可重新生成,首次发布后不可更换。 - macOS 发布方式:已接入专用 macOS Jenkins 节点(label `genarrative-agc-macos`,EXCLUSIVE 单 executor),由 `Jenkinsfile.ai-game-creator-shell-macos-build` 执行 `scripts/build-macos-ci.mjs` 完成 universal 构建、双架构隔离 smoke、universal DMG、分区清单生成、更新包验签与 OSS 上传。`AGC_RELEASE_DRY_RUN` 默认为开,只有显式关闭才会写入 OSS。 -- macOS 代码签名与公证暂缺:产物为未签名 + 未公证,`--no-sign` 保留,构建清单与 `latest.json` 显式记录 `appleSigned=false` / `notarized=false`,首装需用户在 Gatekeeper 中手动放行。该限制作为已知未验证项记录,不静默通过;「安装 → 重启接管新版本」的自动更新闭环仍需实机验收。 +- macOS 代码签名与公证暂缺:产物为未签名 + 未公证,构建入口剥离 `APPLE_*` 凭据跳过 Apple 签名,不传 `--no-sign`(它还会跳过 updater 的 minisign 签名,产物将没有 `.sig`);构建清单实测记录 `appleSigned` 与签名类型,`latest.json` 侧固定记录 `notarized=false`,首装需用户在 Gatekeeper 中手动放行。该限制作为已知未验证项记录,不静默通过;「安装 → 重启接管新版本」的自动更新闭环仍需实机验收。 - 更新包验签门禁:构建完成、上传 OSS 之前,用产物内烘焙的 `plugins.updater.pubkey` 复核 `<更新包>.sig`(Tauri 使用 minisign 的 `ED` 预哈希模式)。keyId 不一致或校验失败立即失败关闭,禁止上传——客户端校验失败会直接拒绝安装,且公钥发布后不可更换。 待办: diff --git a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md index a7b58241f..f3ca67a69 100644 --- a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md +++ b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md @@ -736,7 +736,7 @@ Job 名为 `Genarrative-Agc-MacOS-Build`,SCM 直接读取仓库内上述 Jenki 发布凭据全部走 Jenkins 全局凭据,并在 `withCredentials` 内注入当前进程:`AgcUpdaterSigningKey`(与 `AgcUpdaterSigningKeyPassword`)映射为 `TAURI_SIGNING_PRIVATE_KEY` / `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`,`AliyunAccessKeyId` / `AliyunaccessKeySecret` 映射为 `AGC_OSS_ACCESS_KEY_ID` / `AGC_OSS_ACCESS_KEY_SECRET`;私钥与凭据不写入 workspace、日志或归档产物。上传顺序为更新包、签名、首装包,三者全部成功后才覆盖 `agc/-mac/latest.json` 指针;`AGC_RELEASE_DRY_RUN` 默认开启,dry-run 只打印将上传的对象、不写任何 OSS 对象。Mac 节点需要 `ossutil`(实测 1.7.19 原生 arm64 可用,装在 `~/.local/bin`,已在 Job 的 PATH 内),可用 `OSSUTIL_BIN` 指定命令名或绝对路径。首次发布建议显式指定 `AGC_RELEASE_VERSION`,避免按渠道高水位递增时出现版本链回退。 -产物边界:macOS 代码签名与公证暂缺,构建保持 `--no-sign`,构建清单显式记录 `appleSigned=false` / `notarized=false`。用户首次安装需要在 Gatekeeper 中手动放行;更新包校验本身只依赖 minisign 签名,因此未签名不阻断自动更新的校验环节,但「安装 → 重启接管新版本」的实机闭环仍未验证,不得以构建成功替代。 +产物边界:macOS 代码签名与公证暂缺,构建通过剥离 `APPLE_*` 凭据让 Tauri 跳过 Apple 签名,**不得使用 `--no-sign`**——该标志会连带跳过 updater 的 minisign 签名,产物缺少 `.sig` 会直接卡在验签门禁(首次实跑即命中该坑)。构建清单按 `codesign -dv` 实测记录 `appleSigned` / `appleSignatureKind`(如 `adhoc`),并固定记录 `notarized=false`。用户首次安装需要在 Gatekeeper 中手动放行;更新包校验本身只依赖 minisign 签名,因此未签名不阻断自动更新的校验环节,但「安装 → 重启接管新版本」的实机闭环仍未验证,不得以构建成功替代。 该 Job 的 Rust 编译走 sccache 对象缓存,与 `Genarrative-Api-Build`、`Genarrative-Stdb-Module-Build` 一致:`RUSTC_WRAPPER=sccache`、`CARGO_INCREMENTAL=0`、`SCCACHE_CACHE_SIZE=20G`。`SCCACHE_DIR` 默认落在稳定缓存根 `~/caches/genarrative-jenkins/agc-macos/sccache`,可用 `GENARRATIVE_AGC_MACOS_CACHE_ROOT` 覆盖;缓存根固定在 HOME 下而不是 `WORKSPACE` 内,避免 workspace 重建后无改动也触发近似冷构建。节点是 8 逻辑核(4P+4E)的共用机器,`CARGO_BUILD_JOBS` 因此做成 Job 参数(默认 `6`),既不全占也让出余量;首次构建 sccache 必然全未命中(实测 0 命中 / 407 未命中),此后的构建才逐步吃到缓存。节点未安装 sccache 时管线打印提示并回退到真实 `rustc`,不阻断构建。universal 双架构各自独立编译,依赖 crate 的复用收益约为单架构的两倍;命中情况由构建末尾的 `sccache --show-stats` 输出,供判断是否需要预热或调整缓存上限。