diff --git a/docs/ci-multi-account-signing-design.html b/docs/ci-multi-account-signing-design.html new file mode 100644 index 0000000..c4bd639 --- /dev/null +++ b/docs/ci-multi-account-signing-design.html @@ -0,0 +1,181 @@ + + +
+ + +Apple 多签名账号(中国 CN / 美国 US / …)× 多 App(pangolin / dudu / jiu …)· 按配置切换 · 2026-09-07
+ +signing.env 里声明 SIGNING_ACCOUNT=us|cn 一行即完成切换;工作流据此从 gitea 取对应账号的密钥集。新增账号=在 gitea 存一套 SIGN_<ACCT>_*;新增 App=拷工作流骨架 + 填 signing.env + 传本仓 profile。零证书重复、切换即改一行。
+BYL4KQHMTN)与 US(Yanmei AI LLC 44WULXM6SV)两套签名同时可用,互不干扰。signing.env 单源 + gen-signing.mjs 保证,见 迁移 Runbook)。两组正交的拆分,是整个设计的地基:
+身份(非机密,入 git,单源):Team ID / 实体名 / Bundle ID / App Group。已在各仓 scripts/signing.env。
+密钥(机密,存 gitea):证书 .p12 / 私钥密码 / ASC .p8 / 描述文件 base64。
account 级(一个 Apple 账号一套,跨该账号所有 App 共享):Developer ID / Apple Distribution 证书、ASC API Key。
+app 级(每个 App 各一份,因 bundle ID 不同):Provisioning Profiles。
关键洞察:证书是账号级的(一张 Developer ID 证书能签该账号下任意 App),描述文件是 App 级的(绑定具体 bundle ID)。所以证书按账号存一次共享,描述文件按 App 存本仓——这条拆分让「多账号 × 多 App」不产生 N×M 的密钥爆炸。
+ +| 层 | 放哪 | 内容 | 粒度 |
|---|---|---|---|
| ① 身份单源 | 各 App 仓 scripts/signing.env(入 git) | Team/实体/Bundle/AppGroup + SIGNING_ACCOUNT=us|cn(新增字段,即切换开关) | per-app |
| ② 账号密钥集 | gitea 用户级 secret(跨仓共享) | SIGN_<ACCT>_*:Developer ID .p12 + 密码、Apple Distribution .p12 + 密码、ASC Key ID/Issuer/p8 | per-account |
| ③ App 描述文件 | gitea 仓库级 secret | macOS App/Sysext + iOS App/Ext 四个 *_PROVISION*_BASE64 | per-app |
当前你的 gitea 是单用户账号拥有所有仓,「用户级」即事实上的「组织级共享层」;「仓库级」覆盖用户级同名值。Android RELEASE_KEYSTORE/KEY_PASSWORD 属 ③ 的 per-app,不变。
# 美国账号 +SIGN_US_DEVELOPER_ID_P12 # Developer ID Application .p12 base64(macOS) +SIGN_US_DEVELOPER_ID_PASSWORD +SIGN_US_APPLE_DIST_P12 # Apple Distribution .p12 base64(iOS) +SIGN_US_APPLE_DIST_PASSWORD +SIGN_US_ASC_KEY_ID # 8G78KGHL5C +SIGN_US_ASC_ISSUER_ID +SIGN_US_ASC_KEY_P8 # AuthKey_*.p8 base64 +# 中国账号(把现有同类值改名到此前缀,或保留旧名让 cn 分支指向旧名) +SIGN_CN_DEVELOPER_ID_P12 / _PASSWORD / SIGN_CN_APPLE_DIST_P12 / … / SIGN_CN_ASC_*+
MACOS_APP_PROVISION_PROFILE_BASE64 +MACOS_SYSEXT_PROVISION_PROFILE_BASE64 +IOS_APP_PROVISIONING_PROFILE_BASE64 +IOS_PACKETTUNNEL_PROVISIONING_PROFILE_BASE64+
描述文件绑定 bundle ID,天然 per-App;放仓库级,各 App 互不影响。
+ +现有 deploy-client.yml 每个 build job 本就有「secret → env 变量」映射层(compile-*.sh 消费)。切换只需把固定的 secrets.X 换成按 SIGNING_ACCOUNT 条件选择。
- name: Resolve signing account + id: acct + run: # 从提交进仓的 signing.env 读单源,避免再设一处 gitea 变量 + . scripts/signing.env + echo "account=${SIGNING_ACCOUNT:?signing.env 缺 SIGNING_ACCOUNT}" >> "$GITHUB_OUTPUT"+
- name: Compile (macOS) + env: + ACCT: ${{ steps.acct.outputs.account }} + MACOS_DEVELOPER_ID_CERT_P12_BASE64: ${{ env.ACCT == 'us' + && secrets.SIGN_US_DEVELOPER_ID_P12 || secrets.SIGN_CN_DEVELOPER_ID_P12 }} + MACOS_DEVELOPER_ID_CERT_PASSWORD: ${{ env.ACCT == 'us' + && secrets.SIGN_US_DEVELOPER_ID_PASSWORD || secrets.SIGN_CN_DEVELOPER_ID_PASSWORD }} + APPSTORE_API_KEY_ID: ${{ env.ACCT == 'us' + && secrets.SIGN_US_ASC_KEY_ID || secrets.SIGN_CN_ASC_KEY_ID }} + APPSTORE_API_ISSUER_ID: ${{ env.ACCT == 'us' + && secrets.SIGN_US_ASC_ISSUER_ID || secrets.SIGN_CN_ASC_ISSUER_ID }} + APPSTORE_API_KEY_P8_BASE64: ${{ env.ACCT == 'us' + && secrets.SIGN_US_ASC_KEY_P8 || secrets.SIGN_CN_ASC_KEY_P8 }} + # ③ 描述文件是仓库级、per-app、与账号无关 → 直接引用 + MACOS_APP_PROVISION_PROFILE_BASE64: ${{ secrets.MACOS_APP_PROVISION_PROFILE_BASE64 }} + MACOS_SYSEXT_PROVISION_PROFILE_BASE64: ${{ secrets.MACOS_SYSEXT_PROVISION_PROFILE_BASE64 }} + run: bash scripts/ci/compile-macos.sh "$REF_NAME"+
compile-macos.sh 内部已 source signing.env 拿 Team/Bundle,与上面注入的证书/描述文件对齐 → 天然一致。iOS job 同理(SIGN_<ACCT>_APPLE_DIST_*)。
把「解析账号 + 导入证书到临时钥匙串 + 装描述文件」抽成一个 composite action(.gitea/actions/apple-sign/action.yml)或共享脚本,各 App 的 workflow 调用它,只传 account + 各自 profile。
# 某 App 的 deploy-client.yml 里 +- uses: ./.gitea/actions/apple-sign + with: + account: ${{ steps.acct.outputs.account }} + # 证书/ASC 由 action 内部按 account 从 SIGN_<ACCT>_* 取(secret 需显式透传)+
gitea/GitHub 的 composite action 不自动继承 secret,须由调用方 with/env 显式传入 → §5 的三元映射留在调用方,action 收「已解析的值」。新增 App = 拷 workflow 骨架 + 写 signing.env(含 SIGNING_ACCOUNT) + 传 4 个仓库级 profile;账号侧零改动。
| 场景 | 做什么 |
|---|---|
| 新增一个签名账号(如再开个欧洲实体) | 在 gitea 用户级存一套 SIGN_<NEW>_*;§5 的三元链加一档(或改 §7.1 的映射表法)。 |
| 新增一个 App(同已有账号) | 拷 workflow 骨架;App 仓 signing.env 填 SIGNING_ACCOUNT + 身份;建该 App 的 4 个描述文件传仓库级。证书/ASC 复用账号级,不新增。 |
| 切换某 App 的账号 | 改该仓 signing.env 的 SIGNING_ACCOUNT(+ Team/Bundle 等身份、跑 gen-signing.mjs);换该仓的 4 个描述文件为新账号的。证书 secret 不用碰。 |
三元 a && x || y 只宜 2 档。多账号改用一个 shell 映射步骤,把 SIGN_<ACCT>_* 落成通用 env(需把该账号所有 secret 传进该步,用 case "$ACCT" 选)。或每账号一个 --env-file。本质是把「选择」从 YAML 表达式挪进脚本,可扩展到任意账号数。
不动 workflow:把 US 证书/ASC/描述文件用现有通用名(DEVELOPER_ID_P12…)存到 pangolin 仓库级。仓库级覆盖用户级中国值 → pangolin CI 用美国、其他 App 用户级中国不变。今天就能发版。
代价:还没有 SIGNING_ACCOUNT 开关,是「按仓库物理隔离」而非「按配置切换」;后续升 Phase 1 要把这批 secret 改名。
落 SIGN_<ACCT>_* 账号层 + signing.env 的 SIGNING_ACCOUNT + workflow 三元映射 + composite action。第 2 个 App 迁移、或想要「配置切换」时做。
建议:pangolin 想尽快发版就先 Phase 0;若你不急发版、想一步到位,直接 Phase 1(多改一次 workflow,免日后改名返工)。两者产物完全一致,差别只在 secret 组织。
+ +pangolin-apple-signing-assets)→ ✅。&& || + secrets.* 在 env::GitHub 兼容语法,act_runner 应支持;上线前用一个 dummy secret 验一次。./.gitea/actions/*):forgejo runner 支持度需实测;不支持则退回「共享 shell 脚本」(scripts/ci/apple-sign-common.sh)。vars.*:本设计改用 signing.env 读 SIGNING_ACCOUNT,不依赖 vars,规避版本差异。关联:Apple 账号迁移 Runbook · 签名单源见 scripts/signing.env + scripts/gen-signing.mjs · 现网 secret 清单/层级见记忆 pangolin-apple-signing-assets / pangolin-client-release-ci。