资讯详情

App Store Connect CLI 签名对账(signing reconcile)完全指南:基于归档与设备清单的确定性、只增式 Ad Hoc 配置漂移修复

📅 2026/9/29 8:02:33 | 华诺云谱 👁 阅读
App Store Connect CLI 签名对账(signing reconcile)完全指南:基于归档与设备清单的确定性、只增式 Ad Hoc 配置漂移修复
【免费下载链接】App-Store-Connect-CLIFast, scriptable CLI for the App Store Connect API. Automate TestFlight, builds, submissions, signing, analytics, screenshots, subscriptions, and more项目地址https://gitcode.com/gh_mirrors/ap/App-Store-Connect-CLI点击查看免费下载asc signing reconcile是 App-Store-Connect-CLI 中一个以归档archive和设备清单为输入、采用计划-应用plan/apply两段式执行的签名对账命令组用于把已存在的 Provisioning Profile 是否包含新注册设备、是否覆盖归档内每个内嵌 target、是否在请求的时间窗口内仍然有效这类问题变成可审计、可恢复、可幂等重试的确定性操作。读完本文你将掌握plan/apply两个子命令的完整用法、设备清单 JSON 的严格格式、四种只增动作registerDevice / createBundleId / createProfile / downloadProfile的触发条件以及底层如何用 SHA-256 指纹、计划哈希、原子写入和 CMS 内容校验来保证对账过程的安全与可追溯。命令定位为何需要 reconcile 而不是直接 fetch/syncasc signing fetch只能查找或创建单个provisioning profile它无法证明一个已存在的 profile是否包含新注册的设备、是否与归档内每一个内嵌 target扩展、Watch、App Clip匹配、是否在请求的时间窗口内仍然有效。因此signing reconcile是紧挨着signing fetch与signing sync的附加型additive、以工件artifact为支撑的命令组。从 命令注册源码 可以看到signing命令下共挂载六个子命令fetch、keychain、reconcile、resign、run、syncreconcile本身又由SigningReconcilePlanCommand()与SigningReconcileApplyCommand()两个叶命令组成见 signing_reconcile.go。命令组完整契约如下同时见于 signing_reconcile.go 的示例帮助文本asc signing reconcile plan \ --archive-path .asc/artifacts/App.xcarchive \ --devices-file .asc/distribution/devices.json \ [--certificate CERTIFICATE_ID] \ [--minimum-validity-days 7] \ [--max-mutations 32] \ [--state-dir .asc/distribution/signing] \ [--overwrite] asc signing reconcile apply \ [--plan .asc/distribution/signing/plan.json] \ --confirm两个叶命令都支持标准的--output json|table|markdown输出格式通过shared.BindOutputFlags绑定见 signing_reconcile.go。各选项的默认值与取值范围源码确认选项默认值约束源码--archive-path必填必须以.xcarchive结尾见 validateSigningReconcilePlanFlags--devices-file必填严格 JSON v1 设备清单见下文--certificate空自动选择显式指定 iOS 分发证书资源 ID缺失或不符合作业会形成 blocker--minimum-validity-days7取值范围0到3650reconcileMaximumValidityDays见 常量定义 与校验逻辑--max-mutations32至少为1计划内只增型远端变更动作的上限--state-dir.asc/distribution/signing计划/回执/已下载 profile 的状态目录--overwritefalse允许覆盖已存在的plan.json否则已存在时报os.ErrExist--planapply.asc/distribution/signing/plan.json必须存在且哈希校验通过--confirmapplyfalse缺失时报--confirm is required用法错误见 apply 命令工作流语义plan 只读、apply 只增Planning 是只读的它绝不修改 App Store Connect 远端状态只做归档盘点、设备解析、证书筛选、profile 候选评估然后把结果写成权限为 mode-0600 的plan.json。Apply 则是有条件的执行器读取并校验plan.json含计划哈希重算在每次计划内的变更动作之前重新读取远端状态只执行计划中记录的附加型additive动作下载并逐一校验每个被选中或新建的 profile写入 mode-0600 的receipt.json以及profiles/UUID.mobileprovision。每个动作完成后都会写入部分回执partial receipt。重试时每个幂等的 ensure/verification 动作都会针对当前状态重新执行——回执只是进展的证据evidence of progress而不是跳过校验的授权authority to skip checks。设备清单输入格式与严格校验设备输入是严格 JSON会拒绝未知字段DisallowUnknownFields、重复 UDID、非 iOS 设备以及空设备列表。清单格式schemaVersion必须为 1{schemaVersion:1,devices:[{name:Test iPhone,udid:...,platform:IOS}]}从 decodeSigningDevicesFile 的实现可见更多细节name必须为 1–128 个可打印字符禁止控制字符与双向格式控制字符safeReconcileDeviceNameudid规范化去掉-、:转大写后长度须在 8–48 之间原始长度 8–64platform仅接受IOS大小写不敏感统一转大写后比较重复 UDID规范化后直接报错解析后的设备按指纹fingerprint排序保证后续哈希与计划输出的确定性。设备与计划输入是受保护的、有界常规文件在 Unix 上必须为 mode 0600 或更严格0o177掩码检查且不允许跟随任何符号链接组件。读取过程中会做读前/读后 stat 一致性检查readProtectedFileBounded防止 TOCTOU 类替换。受保护输入与解析失败被归类为用法错误usage errorexit 2诊断信息对路径与值做了安全化原始设备名与 UDID 会在远端 preflight 失败信息中被脱敏为[redacted]sanitizeReconcileError。隐私设计计划与输出中不出现原始 UDID无论是plan.json还是正常输出都不会包含原始 UDID。设备引用一律使用SHA-256 派生的 16 位十六进制指纹fingerprintDevice见 signing_reconcile.go以及已知时的 App Store Connect 资源 ID设备名也只以nameSha256形式进入计划。这样即使 plan 工件被泄漏也无法直接还原设备身份。错误分类与阻塞状态校验失败输入、计划不匹配、选项非法→ 用法错误exit 2远端或工件失败网络、API 错误、写盘失败→ 普通非零错误一个合法但被阻塞的计划会被成功写出readyfalseapply 会拒绝执行它executeSigningReconcileApplyPlanWithUsage。底层 API 契约reconcile 用到的 App Store Connect 端点离线 OpenAPI 快照见 docs/openapi/latest.json 与 docs/openapi/paths.txt定义了这里用到的精确操作GET /v1/devices按filter[platform]分页解析已启用设备POST /v1/devices必填name、udid、platformGET /v1/bundleIds?filter[identifier]...对缺失的显式 iOS 标识符用POST /v1/bundleIds必填name、identifier、platformGET /v1/certificates分页并过滤为 iOS 分发类型GET /v1/bundleIds/{id}/profiles随后分页GET /v1/profiles/{id}/certificates与/devicesPOST /v1/profiles类型IOS_APP_ADHOC、一个 bundle ID、一个选中的证书、期望的设备集GET /v1/profiles/{id}提供 base64 的 profile 内容用于校验与下载。分页拉取在代码中通过asc.WithDevicesLimit(200)、WithCertificatesLimit(200)等选项配合Links.Next循环实现getAllReconcileDevices、getAllReconcileCertificates。关键约束App Store Connect API 中的 profile 是不可变的。因此 reconcile 在设备集变化时创建后继 profilesuccessor并保留每一个旧 profile。它永远不会发送DELETE或PATCH不会启用或重命名设备不会创建证书也不会变更 capability。归档盘点如何识别内嵌 target 与签名 entitlements归档盘点逻辑位于 signing_reconcile_archive.go读取归档根Info.plist的ApplicationProperties校验ApplicationPath不会逃逸归档validateSigningArchiveRelativePath并取出签名 teamTeam键。主应用main application的Info.plist必须无歧义地声明iPhoneOS平台CFBundleSupportedPlatforms若非空必须只有一个且等于iPhoneOSDTPlatformName若非空必须为iphoneos两者都缺失则无法验证平台直接拒绝validateSigningArchivePlatform。非 iOS 或无法验证的归档平台会在规划 iOS 资源之前被拒绝。通过discoverEmbeddedSigningTargets递归发现内嵌 target主应用的PlugIns/*.appexapp 扩展、Watch/*.appWatch 应用及其PlugIns/*.appexWatch 扩展、AppClips/*.appApp Clip全部按稳定路径排序。对每个 target 读取Info.plist的CFBundleIdentifier与CFBundleExecutable用/usr/bin/codesign -d --entitlements :-提取签名可执行文件的 entitlementsreadCodesignEntitlements。实现细节上为避免codesign拒绝/dev/fd代码对象代码会把已打开的 no-follow 句柄复制到私有临时目录mode 0700再执行 codesign。归档盘点要求所有 target 必须使用同一个 teamentitlements 中的com.apple.developer.team-identifier必须等于归档 team重复 bundle identifier 会被拒绝每个 target 的application-identifierentitlements 必须与其CFBundleIdentifier自洽。缺 App ID 时的能力基线判断缺失的显式 App ID只有在基线 entitlements 集下才可以被计划创建。目标若要求一个尚未注册的 capability则构成 blocker——因为本命令不改变 capability。基线集signingCapabilitiesForEntitlements包括application-identifier、com.apple.application-identifier、com.apple.developer.team-identifier、keychain-access-groups、get-task-allow、beta-reports-active可映射为 capability 的 entitlements如aps-environment→PUSH_NOTIFICATIONS、com.apple.developer.associated-domains→ASSOCIATED_DOMAINS、com.apple.developer.healthkit→HEALTHKIT等要求对应 capability 已存在带值型设置value-specific settings的 entitlements 如 App Groupscom.apple.security.application-groups、Apple Pay 商家标识com.apple.developer.in-app-payments、Network Extensionscom.apple.developer.networking.networkextension、Wallet pass 标识com.apple.developer.pass-type-identifiers则始终作为不可验证项阻塞——仅凭 capability 存在无法证明这些带值 entitlements 正确宁可阻塞也不冒险创建不可用的 profile。规划plan阶段设备、证书与 profile 的判定规则设备解析planDesiredDevicessigning_reconcile_plan.go把清单中的每个 UDID 与远端已启用设备做规范化匹配恰好 1 个已启用匹配 → 记录资源 ID 与状态多个已启用匹配 → blocker设备解析到多个启用资源存在但被禁用 → blocker只增命令不会重新启用设备完全不存在 → 生成registerDevice动作POST /v1/devices。证书选择selectReconcileCertificateWithFingerprintsigning_reconcile_plan.go只考虑活跃、未过期、且有效期跨越--minimum-validity-days窗口的 iOS 分发证书类型IOS_DISTRIBUTION或DISTRIBUTIONActivated仅当显式为 false 时拒绝多于一个合格证书 → blocker除非--certificate显式选定一个显式指定的证书缺失或不合格 → blocker选中的证书会被解析 DERx509.ParseCertificate并把证书的SHA-256 指纹、team IDSubject OU要求恰好一个绑定进计划certificatePlanRef证书内容有效期与 API 过期时间不一致也会被拒绝。此外计划会校验证书 team 与归档 team 一致不一致即 blockerexecuteSigningReconcilePlan。Profile 可复用性判定一个既有 profile 只有在同时满足以下条件时才可复用getProfileCandidates类型为IOS_APP_ADHOC且状态为 active有效期跨过最小窗口minimumExpiration now minimumValidityDays属于精确的App IDfindExactReconcileBundleID通过filter[identifier]分页并做精确匹配使用选中的证书profile 的 certificate 关联恰好为 1 个且等于计划证书 ID设备集恰好等于期望的已启用设备集其经认证的 CMS 内容pkcs7.ParseVerify在语义上包含 target 的签名 entitlements 与选中的证书指纹profileContentMatchesTarget。设备超集superset不会被复用因为那会扩大超出显式输入的分发范围。合格 profile 按更晚过期时间优先、其次资源 ID排序。若没有合格者计划包含一个确定性 profile 创建动作。Profile 名称是 bundle、证书与设备集的哈希ASC Ad Hoc bundle 12位指纹见 deterministicProfileName因此重试会收敛converge——同样的输入总是得到同样的名称与同样的候选结果。App ID seed 校验对于已存在的 App IDseedId必须与归档 target 的AppIDPrefix由application-identifierentitlements 去掉.bundleID后缀得到精确匹配validateReconcileBundleSeed。apply 阶段会在并发创建收敛后、以及 profile 创建前各重复一次该检查。计划哈希与 mutation 上限hashSigningReconcilePlansigning_reconcile.go对除去generatedAt与哈希自身之外的整个计划工件做 JSON 序列化后取 SHA-256。计划哈希覆盖归档 target 描述符、team 与 entitlements、期望设备指纹、选中的证书、观察到的远端前置条件、有序动作、变更上限与输出路径。动作计数registerDevice/createBundleId/createProfile若超过--max-mutations计划会进入 blocker 状态。应用apply阶段哈希校验、远端重解析与幂等重试apply 的执行流程executeSigningReconcileApplyPlanWithUsage拒绝readyfalse或带 blocker 的计划结构校验计划validateSigningApplyPlanaction ID 唯一性、device:/bundle:/profile:/download:ID 与目标/设备/证书的一致性、mutation 计数与动作一致性重新盘点本地输入verifySigningLocalInputs重读 devices 文件、重新解析归档比对 team、targetsentitlements 用精确 JSON 数值语义比较与设备集 SHA-256重新解析远端前置条件证书复选ID SHA-256 有效期窗口必须与计划逐字段相等、设备解析、每个 target 的规划复跑只接受单调、已满足的漂移冲突响应后是精确重读而非盲目重试ensureReconcileDevice/ensureReconcileBundleID在 POST 后若结果不确定会立即重新精确查找收敛见 signing_reconcile_apply.go逐动作执行每步持久化部分回执registerDevice拒绝 PATCH 已禁用设备createBundleId创建ASC identifier命名的 iOS bundlecreateProfile先复验 capability、再检查合格候选、最后POST /v1/profiles409/不确定响应仅在精确重读证明确定性名称与合格性后才接受ensureReconcileProfile全部动作完成后回执标记completetrue。数值精确性与回执绑定apply 在比较计划与重新解析的 entitlements 数值时使用json.Numberbig.Rat做超越 float64 无损范围的整数精确比较而不是四舍五入exactSigningNumber。恢复回执在持久化前会被重新绑定到哈希保护的计划状态目录loadOrStartSigningReceiptWithUsage校验receipt.StateDir/ReceiptPath与计划精确一致见 signing_reconcile_apply.go因此回执字段无法把恢复写入重定向到其他位置。下载 profile 的原子发布下载的 profile 以create-only不覆盖方式写入writeSigningStateJSON 的CreateNewFileAtomic。重试只有在既有文件字节的 SHA-256 与要写入内容完全一致时才允许复用同一 UUID 文件名测试 TestWriteVerifiedProfileRejectsDifferentContentForExistingUUID 覆盖此行为。原子发布只容忍目录持久性同步不支持这类平台/文件系统错误在无替换发布成功后其他同步失败保持致命。输出、回执与可观测性plan与apply的终端输出分别是计划摘要与回执摘要renderSigningPlan# plan 输出列Ready | Plan Hash | Targets | Devices | Mutations | Blockers | Plan Path # apply 输出列Complete | Plan Hash | Actions | Receipt Path结合--output json可获得完整工件便于 Agent 或脚本解析。状态目录最终包含plan.json计划mode 0600、receipt.json回执mode 0600、profiles/UUID.mobileprovision已校验的 profile 文件。兼容性、测试覆盖与设计取舍兼容性这是附加型additive表面——既有 signing 命令及其输出保持不变见 signing.go 的命令组结构。测试覆盖signing_reconcile_test.go 与配套的signing_reconcile_adapter_test.go、signing_reconcile_archive_test.go、signing_reconcile_protected_unix_test.go测试从命令边界 RED 开始覆盖严格输入校验先于认证TestSigningReconcilePlanValidatesBeforeAuth、受保护有界输入、确定性哈希与排序、GET-only 规划、分页、App ID 与 profile 创建载荷、幂等恢复、entitlement/profile 校验、伪造 CMS 拒绝TestDecodeReconcileMobileProvisionRejectsForgedCMS、内嵌证书绑定、精确设备集隐私、同 UUID 内容冲突、文件模式与符号链接/路径包含。另有聚焦包测试、命令测试、内置二进制冒烟测试、生成的命令文档与仓库门禁完成验证。设计取舍原文明确说明API 不支持更新既有 profile删除陈旧 profile 会让工作流变成破坏性操作因此刻意排除接受原始 bundle/设备 flag 而非归档与版本化输入文件会更短但会遗漏内嵌 target 且让 Agent 重试难以审计自动启用 capability 会减少 blocker但会实质性扩大账户变更范围保留给单独的显式工作流仓库中另见 signing-sync 相关设计 与 capability reconcile 输出。典型使用流程在 macOS 上signing reconcile依赖codesign读取签名 entitlements因此仅支持 darwin见 validateSigningReconcilePlatform建议流程# 1) 准备严格格式的设备清单mode 0600 chmod 600 .asc/distribution/devices.json # 2) 只读规划检查并输出计划不修改远端 asc signing reconcile plan \ --archive-path .asc/artifacts/App.xcarchive \ --devices-file .asc/distribution/devices.json \ --output json # 3) 人工/Agent 审查计划确认 readytrue、动作集合与 mutation 数量 # 4) 确认并应用只增动作 下载校验后的 profile asc signing reconcile apply \ --plan .asc/distribution/signing/plan.json \ --confirm # 5) 用已校验 profile 进行签名/导出可选 asc signing run --identity ./signing/App.p12 --profile .asc/distribution/signing/profiles/UUID.mobileprovision -- xcodebuild -exportArchive结合 docs/design/read-only-mode.md 与 docs/design/flag-value-indirection.md 等设计文档可以把plan阶段嵌入只读审计流水线把apply --confirm作为人工把关后的发布步骤。若计划因 blocker证书歧义、缺失 capability、App ID seed 不匹配、设备已禁用等无法就绪应先修正输入或显式指定--certificate再重新规划——不要修改计划文件本身因为任何手工改动都会使planHash校验失败并强制重新规划。赞分享【免费下载链接】App-Store-Connect-CLIFast, scriptable CLI for the App Store Connect API. Automate TestFlight, builds, submissions, signing, analytics, screenshots, subscriptions, and more项目地址https://gitcode.com/gh_mirrors/ap/App-Store-Connect-CLI点击查看免费下载相关推荐App-Store-Connect-CLI agent-native ad hoc 分发从 Xcode 归档到可验证 OTA 安装的全链路设计App Store Connect CLI agent native ad hoc 分发从 Xcode 归档到可验证 OTA 安装的全链路设计 本篇技术指南围asc signing runApp Store Connect CLI 的临时签名执行环境Ephemeral Signing设计与实战asc signing runApp Store Connect CLI 的临时签名执行环境Ephemeral Signing设计与实战 导读 asc swagmi 的 WagmiProviderReact 应用接入 Ethereum 的上下文 Provider 完整指南wagmi 的 WagmiProviderReact 应用接入 Ethereum 的上下文 Provider 完整指南 本篇指南聚焦 wagmiReacti上一篇思源宋体CN7种字重免费商用字体完全指南下一篇Hasura GraphQL Engine CLI Migrations v3 镜像入口点自动迁移与元数据应用实战指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑