Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 42 additions & 4 deletions .github/workflows/android-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -229,10 +229,48 @@ jobs:
MEAWALLET_NEXUS_PASSWORD: ${{ secrets.MEAWALLET_NEXUS_PASSWORD_ANDROID }}
ANDROID_VERSION_NAME: ${{ steps.version.outputs.name }}
run: |
# Monotonic (run_number always increases). +10000 clears legacy
# codes already on Play from earlier manual/local uploads (small
# console codes plus git-commit-count builds up to ~8600).
export ANDROID_VERSION_CODE=$((10000 + GITHUB_RUN_NUMBER))
# versionCode = <major><build><attempt>, base 10000000/10000.
#
# The BASE is the resolved version, NOT github.run_number: inside a
# workflow_call run_number is the CALLER's counter, so the first
# release-native.yml run numbered itself 1 and produced versionCode
# 10001 against the live 10048 — Play refused it ("does not allow any
# existing users to upgrade"). The version is the one counter that is
# monotonic across every entry point.
#
# The ATTEMPT digits exist because the base alone is not unique. A run
# that uploads to Play and then fails a later step (the Capgo publish,
# or the sibling iOS job) leaves the tag unwritten ON PURPOSE, so the
# recovery run resolves the SAME version — and Play rejects a
# versionCode it has already seen, which would wedge the very release
# the re-run is trying to repair. run_number covers a fresh dispatch
# and run_attempt covers "Re-run failed jobs", where run_number does
# not move. Both only ever increase, so every upload of one version
# gets a strictly higher code than the last.
#
# Spacing: 10000 per build dominates any attempt value below it, so
# codes stay ordered across builds; the guard below keeps it that way
# rather than letting a wrap silently invert the ordering. It also
# clears the legacy codes on Play (small console codes, git-commit-count
# builds to ~8600, and the run_number era up to 10048).
#
# Caveat: run_number is per-workflow, so a break-glass dispatch of THIS
# workflow numbers itself from its own counter (~49) rather than
# release-native.yml's. Within one build that can order a later
# break-glass upload above a subsequent release-native one; cut the next
# build rather than re-uploading the same version if that ever bites.
IFS=. read -r V_MAJOR V_BUILD V_OTA <<< "$ANDROID_VERSION_NAME"
ATTEMPT=$((GITHUB_RUN_NUMBER * 10 + GITHUB_RUN_ATTEMPT - 1))
if [ "$ATTEMPT" -ge 10000 ]; then
echo "::error::attempt component $ATTEMPT has outgrown its 4 digits and would collide with the next build — widen the versionCode spacing" >&2
exit 1
fi
export ANDROID_VERSION_CODE=$((V_MAJOR * 10000000 + V_BUILD * 10000 + ATTEMPT))
if [ "$ANDROID_VERSION_CODE" -le 10048 ]; then
echo "::error::versionCode $ANDROID_VERSION_CODE does not clear the live 10048 — Play would refuse the upload" >&2
exit 1
fi
echo "versionCode $ANDROID_VERSION_CODE (version $ANDROID_VERSION_NAME, run $GITHUB_RUN_NUMBER attempt $GITHUB_RUN_ATTEMPT)"
pnpm native:release

- name: Upload to Google Play
Expand Down
77 changes: 10 additions & 67 deletions .github/workflows/ios-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -197,99 +197,46 @@ jobs:

- name: Build web + sync iOS
env:
# MeaWallet MPP SDK (Apple Pay push provisioning): postsync
# vendors the xcframework only when these are set, so the
# build stays green until the secrets are provisioned.
# MeaWallet issues separate iOS and Android Nexus credentials,
# and an Environment secret holds one value per name — sharing
# a name would send whichever pair was configured last to both
# platforms and fail the other one's SDK download.
MEAWALLET_NEXUS_USER: ${{ secrets.MEAWALLET_NEXUS_USER_IOS }}
MEAWALLET_NEXUS_PASSWORD: ${{ secrets.MEAWALLET_NEXUS_PASSWORD_IOS }}
# postsync stamps project.pbxproj; without this it would stamp
# package.json's version, which no longer tracks releases.
IOS_MARKETING_VERSION: ${{ steps.version.outputs.name }}
run: |
node scripts/native-build.js && npx cap sync ios
if [ -f scripts/native-ios-postsync.js ]; then node scripts/native-ios-postsync.js; fi

- name: Write MeaWallet config
# mea_config (encrypted, non-sensitive per MeaWallet, but kept out
# of git) — the Copy MeaWallet Config build phase bundles it when
# present; no-op until the secret is provisioned.
env:
MEA_CONFIG: ${{ secrets.MEAWALLET_CONFIG_BASE64 }}
run: |
if [ -n "$MEA_CONFIG" ]; then
echo "$MEA_CONFIG" | base64 -d > ios/App/App/mea_config
echo "mea_config written"
else
echo "MEAWALLET_CONFIG_BASE64 not set — push provisioning stays stubbed"
fi

- name: Install Apple distribution certificate
uses: apple-actions/import-codesign-certs@5142e029c445c10ffc7149d172e540235a065466 # v7
with:
p12-file-base64: ${{ secrets.IOS_DIST_CERT_P12_BASE64 }}
p12-password: ${{ secrets.IOS_DIST_CERT_PASSWORD }}

- name: Install provisioning profiles
- name: Install provisioning profile
id: provision
env:
IOS_PROVISIONING_PROFILE_BASE64: ${{ secrets.IOS_PROVISIONING_PROFILE_BASE64 }}
IOS_PROVISIONING_PROFILE_EXT_BASE64: ${{ secrets.IOS_PROVISIONING_PROFILE_EXT_BASE64 }}
IOS_PROVISIONING_PROFILE_EXT_UI_BASE64: ${{ secrets.IOS_PROVISIONING_PROFILE_EXT_UI_BASE64 }}
run: |
PROFILE_DIR="$HOME/Library/MobileDevice/Provisioning Profiles"
mkdir -p "$PROFILE_DIR"

# The archive is manual-signed, so EVERY bundle it embeds needs its
# own profile installed and named in ExportOptions — a missing
# extension profile fails the archive, not just the extension.
install_profile() {
local b64="$1" out="$2" var="$3"
if [ -z "$b64" ]; then
echo "::error::$var is empty — the app and both Wallet extensions each need their profile"
exit 1
fi
echo "$b64" | base64 -d > "/tmp/$out.mobileprovision"
# Decode the profile to read its UUID + Name (no extra secret needed).
security cms -D -i "/tmp/$out.mobileprovision" > "/tmp/$out.plist"
local uuid name
uuid=$(/usr/libexec/PlistBuddy -c 'Print :UUID' "/tmp/$out.plist")
name=$(/usr/libexec/PlistBuddy -c 'Print :Name' "/tmp/$out.plist")
cp "/tmp/$out.mobileprovision" "$PROFILE_DIR/$uuid.mobileprovision"
echo "$out=$name" >> "$GITHUB_OUTPUT"
echo "Installed provisioning profile: $name ($uuid)"
}

install_profile "$IOS_PROVISIONING_PROFILE_BASE64" profile IOS_PROVISIONING_PROFILE_BASE64
install_profile "$IOS_PROVISIONING_PROFILE_EXT_BASE64" ext IOS_PROVISIONING_PROFILE_EXT_BASE64
install_profile "$IOS_PROVISIONING_PROFILE_EXT_UI_BASE64" extui IOS_PROVISIONING_PROFILE_EXT_UI_BASE64

echo "$IOS_PROVISIONING_PROFILE_BASE64" | base64 -d > /tmp/profile.mobileprovision
# Decode the profile to read its UUID + Name (no extra secret needed).
security cms -D -i /tmp/profile.mobileprovision > /tmp/profile.plist
PROFILE_UUID=$(/usr/libexec/PlistBuddy -c 'Print :UUID' /tmp/profile.plist)
PROFILE_NAME=$(/usr/libexec/PlistBuddy -c 'Print :Name' /tmp/profile.plist)
cp /tmp/profile.mobileprovision "$PROFILE_DIR/$PROFILE_UUID.mobileprovision"
echo "name=$PROFILE_NAME" >> "$GITHUB_OUTPUT"
echo "Installed provisioning profile: $PROFILE_NAME ($PROFILE_UUID)"
# A profile without aps-environment ships an app that can never
# register with APNs, even when the entitlements file asks for it.
# Only the app carries push; the extensions legitimately do not.
PROFILE_APS=$(/usr/libexec/PlistBuddy -c 'Print :Entitlements:aps-environment' /tmp/profile.plist 2>/dev/null || true)
if [ "$PROFILE_APS" != "production" ]; then
echo "::error::provisioning profile aps-environment is '${PROFILE_APS:-missing}' — enable Push Notifications on the App ID, regenerate the profile, and update IOS_PROVISIONING_PROFILE_BASE64"
exit 1
fi

# The app and the extensions share group.me.peanut.wallet; if the app's
# profile predates that capability the archive signs but the extension
# can never read the store the app writes.
if ! /usr/libexec/PlistBuddy -c 'Print :Entitlements:com.apple.security.application-groups' /tmp/profile.plist 2>/dev/null | grep -q 'group.me.peanut.wallet$'; then
echo "::error::the app profile is missing the group.me.peanut.wallet app group — regenerate it after enabling the App Group on me.peanut.wallet and update IOS_PROVISIONING_PROFILE_BASE64"
exit 1
fi

- name: Archive & export IPA
env:
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
PROFILE_NAME: ${{ steps.provision.outputs.profile }}
PROFILE_NAME_EXT: ${{ steps.provision.outputs.ext }}
PROFILE_NAME_EXT_UI: ${{ steps.provision.outputs.extui }}
PROFILE_NAME: ${{ steps.provision.outputs.name }}
# Monotonic, always-increments-even-on-rerun. TestFlight requires each
# upload's build number to exceed the last.
IOS_BUILD_NUMBER: ${{ github.run_number }}
Expand Down Expand Up @@ -325,10 +272,6 @@ jobs:
<dict>
<key>me.peanut.wallet</key>
<string>${PROFILE_NAME}</string>
<key>me.peanut.wallet.PushProvisioningExtension</key>
<string>${PROFILE_NAME_EXT}</string>
<key>me.peanut.wallet.PushProvisioningExtensionUI</key>
<string>${PROFILE_NAME_EXT_UI}</string>
</dict>
<key>uploadSymbols</key>
<true/>
Expand Down
Loading
Loading