#!/bin/bash # verify-asc-key.sh — confirm the App Store Connect API key actually works. # # Run this once after creating the key, before trusting release.sh to upload: # # ./mobile/scripts/verify-asc-key.sh # # It calls `altool --list-providers`, which authenticates and returns the teams the key can # see. Nothing is uploaded, nothing is created, no build number is consumed — so it is safe # to run as often as you like. A bad key fails here in seconds instead of fifty seconds into # an upload with an opaque error. set -uo pipefail MOBILE="$(cd "$(dirname "$0")/.." && pwd)" CFG="$MOBILE/ios-signing.config" G=$'\033[32m'; Y=$'\033[33m'; R=$'\033[31m'; DIM=$'\033[2m'; N=$'\033[0m' ok() { echo " ${G}✓${N} $*"; } warn() { echo " ${Y}!${N} $*"; } bad() { echo " ${R}✗${N} $*" >&2; } [ -f "$CFG" ] || { bad "no $CFG — see mobile/README.md → iOS one-time setup"; exit 1; } # shellcheck source=/dev/null . "$CFG" if [ -z "${ASC_KEY_ID:-}" ] || [ -z "${ASC_ISSUER_ID:-}" ]; then warn "ASC_KEY_ID / ASC_ISSUER_ID not set in ios-signing.config" echo " ${DIM}Uploads stay manual (Xcode ▸ Organizer). Everything else still works.${N}" exit 0 fi # altool locates the key by NAME, not path — AuthKey_.p8 in one of its search dirs. KEYFILE="$HOME/.appstoreconnect/private_keys/AuthKey_${ASC_KEY_ID}.p8" echo " key id $ASC_KEY_ID" echo " issuer $ASC_ISSUER_ID" echo " key file $KEYFILE" if [ ! -f "$KEYFILE" ]; then bad "that file does not exist" echo " ${DIM}The filename must match the key id EXACTLY, including the AuthKey_ prefix${N}" echo " ${DIM}and the .p8 extension. Apple's download is already named correctly — move it:${N}" echo " ${DIM} mv ~/Downloads/AuthKey_${ASC_KEY_ID}.p8 ~/.appstoreconnect/private_keys/${N}" exit 1 fi ok "key file present ($(wc -c < "$KEYFILE" | tr -d ' ') bytes)" # A .p8 that got mangled (opened and re-saved, or downloaded as HTML) fails auth with a # generic error. Checking the PEM header names the real problem instead. head -1 "$KEYFILE" | grep -q "BEGIN PRIVATE KEY" \ && ok "looks like a valid PEM private key" \ || { bad "not a PEM private key — re-download it from App Store Connect"; exit 1; } # Tighten permissions: this file can upload builds as your team. chmod 600 "$KEYFILE" 2>/dev/null # NOT --list-providers: it rejects API-key auth outright ("list-providers does not support # APIKey authentication"), so it can never verify a key like this one. --validate-app is the # real check — it authenticates AND runs Apple's acceptance checks on the binary, without # uploading anything or consuming the build number. IPA="$(ls -t "$MOBILE/../dist-release/"*.ipa 2>/dev/null | head -1)" if [ -z "$IPA" ]; then warn "no .ipa in dist-release/ to test with — run ./release.sh --ios first" exit 0 fi echo echo " authenticating with $(basename "$IPA")…" OUT="$(xcrun altool --validate-app -f "$IPA" -t ios --api-key "$ASC_KEY_ID" --api-issuer "$ASC_ISSUER_ID" 2>&1)" RC=$? if [ $RC -eq 0 ]; then ok "authenticated, and the build passed Apple's validation" echo ok "release.sh will now upload iOS builds automatically" exit 0 fi # Authentication and validation fail the same way at the shell level, so separate them — # "your key is wrong" and "your binary is wrong" need completely different fixes. if echo "$OUT" | grep -qiE "Unable to authenticate|Authentication failed|NOT_AUTHORIZED|invalid.*(key|issuer)"; then bad "authentication FAILED — the key or issuer is wrong" echo "$OUT" | grep -iE "error|message" | head -6 | sed 's/^/ /' echo " ${DIM}· Issuer ID is the UUID above the key TABLE, not the key's own id${N}" echo " ${DIM}· Key needs App Manager or Admin access${N}" exit 1 fi # "Cannot determine the Apple ID from Bundle ID" is what Apple actually says when the key # authenticated fine but no app record exists yet — it reads like a failure and is not one. if echo "$OUT" | grep -qiE "Cannot determine the Apple ID|no.*app.*with.*bundle|cannot be found|No suitable application"; then ok "authentication WORKED — the key is good" warn "but this app does not exist in App Store Connect yet" echo " ${DIM}Create it: App Store Connect → Apps → + → New App${N}" echo " ${DIM}Bundle ID: $(python3 -c "import json;print(json.load(open('$MOBILE/capacitor.config.json'))['appId'])" 2>/dev/null)${N}" exit 0 fi bad "validation failed" echo "$OUT" | grep -iE "error|message|warning" | head -10 | sed 's/^/ /' exit 1