apple signing: every signable target (extensions, App Group check), build keychain unlock over SSH, api confirm docs

This commit is contained in:
CRTkafa 2026-10-24 13:02:35 +08:00
parent 5b6e0992ca
commit 445ab1adbc
17 changed files with 832 additions and 42 deletions

View file

@ -19,7 +19,7 @@ when the owner said so for this run). The MCP server offers only `apple_signing_
1. **Read.** `storebound apple signing status`: is the bundle id registered (else `storebound apple bundle`), which
capabilities it has vs `apple.signing.capabilities`, the distribution certificates of the team and whether this
Mac holds a private key for one (a key file in `.storebound/signing/` or an identity in the keychain), App Store
profiles per bundle id (app + `apple.signing.extraBundleIds` for extensions) and whether Xcode has them installed.
profiles per bundle id (the app and every extension / widget / watch app / App Clip found in the Xcode project, or `apple.signing.extensions`; `extraBundleIds` adds more) and whether Xcode has them installed. `signing ensure` registers the extensions' bundle ids, enables the capabilities their entitlements need and (manual style) makes their profiles; an App Group the profile lacks is reported with the developer portal link and steps (the API cannot assign a group; then `signing ensure --refresh-profile`, store-pitfalls 121).
It also compares three places a capability must be and names what is missing where: the App ID, the config's
`apple.signing.capabilities` and the iOS project's `.entitlements` (`aps-environment` = PUSH_NOTIFICATIONS,
`com.apple.developer.game-center` = GAME_CENTER, associated domains, Sign in with Apple, iCloud, app groups, ...;
@ -57,7 +57,12 @@ when the owner said so for this run). The MCP server offers only `apple_signing_
number**: later steps use exactly it. Any archive or export failure exits non-zero, so `apple build && apple upload`
stops there; an export failure shows the IDEDistribution log's real error lines. xcodebuild runs with Apple's tool
folders first on PATH (a Homebrew rsync breaks Xcode's IPA step: "exportArchive Copy failed"; `storebound doctor`
flags it).
flags it). Over SSH or in CI (no GUI session) codesign fails with errSecInternalComponent on a locked keychain:
`apple build` stops first and names the fixes. `STOREBOUND_KEYCHAIN_PASSWORD=... storebound apple build
--keychain-unlock` unlocks the login keychain and sets its key partition list (the password is read from the
environment and never printed); `--keychain <path>` signs with a build keychain (`--keychain <path> --keychain-unlock`
unlocks that one; `signing ensure --keychain <path>` imports the key + certificate into it). With manual signing it
also stops when a target's App Group is missing from its installed profile (store-pitfalls 121).
- Capacitor: the archive ships the web assets `npx cap sync` copied last. Set `build.prebuild` (e.g.
`"npm run build && npx cap sync"`; per store `apple.prebuild` / `play.prebuild`) and `apple build` / `play build`
run it first (once per `flow run`; `--no-prebuild` skips it). Then the copied `public/index.html` must be newer

View file

@ -42,7 +42,9 @@ storebound api apple salesReports_getCollection --param ... --out report.gz #
on stderr (store-pitfalls 78). Never pipe or redirect a write; use the dedicated command the message names.
An agent without a terminal sends a plain (non-owner) write with `--yes --confirm <operationId>`, the id equal
to the one the call resolved to (store-pitfalls 92), after showing the dry run. `--confirm` never applies to
owner-only operations.
owner-only operations. Both flags are needed, and `--confirm` repeats the operation id:
`storebound api apple bundleIds_createInstance --body bundle.json --yes --confirm bundleIds_createInstance`
(without `--yes` it stays a dry run; a `--confirm` that differs from the id is refused).
3. Owner-only operations (cli/api/gate.ts: every DELETE, submissions and their cancel, release requests, phased
release changes, product page test start / stop, price schedules and price migrations, review replies, tester and
user invitations, Play `edits.commit`, product activation, refunds, revokes and cancels, app signing) also ask for

View file

@ -1229,8 +1229,13 @@ that fails without the fix (see CONTRIBUTING.md).
(and read relative to the working directory, not the project).
- **Do:** run the build in the Mac's logged-in session: a CI agent started there, or from SSH a `.command` file opened
with `open -a Terminal <file>` (unset SSH_CONNECTION SSH_CLIENT SSH_TTY inside). `--allow-ssh` after
`security unlock-keychain` skips the check.
- **Guard:** `remoteBuildSession` in `cli/lib/xcode.ts` stops `apple build` before the archive; `credPath` in
`security unlock-keychain` skips the check. To build over SSH or in CI anyway: export `STOREBOUND_KEYCHAIN_PASSWORD`
(never printed, never an argument of storebound) and add `--keychain-unlock` (unlocks the login keychain and runs
`security set-key-partition-list -S apple-tool:,apple:,codesign:`), or `--keychain <path> --keychain-unlock` for a
build keychain that holds the Apple Distribution key + certificate (`apple signing ensure --keychain <path>` imports
them); `--keychain` also signs the archive with `OTHER_CODE_SIGN_FLAGS=--keychain <path>` and puts the keychain on the search list.
- **Guard:** `remoteBuildSession` in `cli/lib/xcode.ts` stops `apple build` before the archive, and its message names
these fixes (`sshBuildRefusal`, `cli/lib/keychain.ts`; `tests/signing-targets.test.ts`); `credPath` in
`cli/lib/credentials.ts` resolves relative credential paths from the project and `ascKeyFile` hands xcodebuild an
absolute path (`tests/build-sign.test.ts`).
@ -1454,3 +1459,30 @@ that fails without the fix (see CONTRIBUTING.md).
attributes first, `text=` last); `shots capture --dry` prints them per size, `--dry --probe` opens the pages and
names the MISS with the size and every candidate's match count.
- **Guard:** `tests/shots-gaps.test.ts`, `tests/browser/feature-fit.browser.ts` (rotate), `cli/shots/selectors.ts`.
||||||| parent of ae8d773 (apple signing: every signable target (extensions, App Group check), build keychain unlock over SSH, api confirm docs)
## 121. An app extension (share extension, widget, watch app) could not be signed: bundle id not registered, capabilities missing, App Group not in the profile
- **Symptom:** an iOS app with a share extension archived, then failed at export / codesign: "No profile for team 'X'
matching 'com.example.app.share' found", "Provisioning profile ... doesn't include the App Groups capability" or
"doesn't match the entitlements file's value for the com.apple.security.application-groups entitlement". `apple
signing ensure` knew only the app's bundle id and refused (`not registered`) for `extraBundleIds`.
- **Cause:** every signable target of the archive (app, app extension, widget, watch app, App Clip) is its own bundle id
with its own capabilities and profile. And Apple's API can switch the APP_GROUPS capability on for a bundle id but
cannot assign a group container to it: the profile carries `com.apple.security.application-groups` only after the
group was ticked in the developer portal (Identifiers > the bundle id > App Groups > Configure).
- **Do:** `storebound apple signing ensure --dry` lists every target found in the Xcode project (PRODUCT_BUNDLE_IDENTIFIER
and CODE_SIGN_ENTITLEMENTS of the Release configuration) and what it would register, enable and create; without
`--dry` it registers the extension bundle ids, enables the capabilities its entitlements need (the entitlement-to-
capability table; `keychain-access-groups` needs none) and, for manual signing, creates an App Store profile per
target. It then decodes each profile and compares its App Groups with the target's entitlements: a gap fails with
the developer portal deep link `https://developer.apple.com/account/resources/identifiers/bundleId/edit/<id>` and the
steps to tick the group (drive it in the signed-in browser; never press Remove / Delete / Revoke). After that
`storebound apple signing ensure --refresh-profile` makes the profile again (a normal run reuses the active one, so it
does not pile up profiles while the group is unassigned). `apple build` (manual signing) stops before the archive on the
same gap (`--allow-app-group-gap` overrides). A variable bundle id (set in an xcconfig) is not resolvable: list the
extension in `apple.signing.extensions` (`bundleId`, `capabilities`, `appGroups`), which replaces the detected ones.
- **Guard:** `cli/lib/targets.ts` (pbxproj targets, App Group comparison, portal link and steps), `discoverTargets` in
`cli/apple/signing.ts`, `tests/signing-targets.test.ts` (fixture pbxproj with an app, a share extension and app group
entitlements, a decoded profile plist, a fake App Store Connect). There is no `console plan` form for the portal page:
the steps are in the error and the warning.