From d89e0bfdf3f9c5b27192184d41b70af0cafa99b0 Mon Sep 17 00:00:00 2001 From: Aaron Bull Schaefer Date: Thu, 12 Feb 2026 09:25:38 -0800 Subject: [PATCH 1/2] Fix naming for upload build jobs to be distinct --- .github/workflows/upload-build-android.yml | 3 ++- .github/workflows/upload-build-ios.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/upload-build-android.yml b/.github/workflows/upload-build-android.yml index ee38e81..3cbecfd 100644 --- a/.github/workflows/upload-build-android.yml +++ b/.github/workflows/upload-build-android.yml @@ -1,4 +1,4 @@ -name: google play console +name: upload build on: workflow_call: @@ -47,6 +47,7 @@ on: jobs: upload_build: + name: google play console runs-on: ubuntu-latest environment: release permissions: diff --git a/.github/workflows/upload-build-ios.yml b/.github/workflows/upload-build-ios.yml index ae551f4..dc3003f 100644 --- a/.github/workflows/upload-build-ios.yml +++ b/.github/workflows/upload-build-ios.yml @@ -1,4 +1,4 @@ -name: app store connect +name: upload build on: workflow_call: @@ -24,6 +24,7 @@ on: jobs: upload_build: + name: app store connect runs-on: macos-latest environment: release permissions: From 5c976a4af1665552dc96a00ac7f80dfaa320fcef Mon Sep 17 00:00:00 2001 From: Aaron Bull Schaefer Date: Thu, 12 Feb 2026 10:43:01 -0800 Subject: [PATCH 2/2] Clarify environment-scoped config and validate missing settings This was only working coincidentally for our signing setup, the values weren't actually being passed through from the caller repository. --- .github/workflows/draft-release-flutter.yml | 104 ++++++++++++-------- .github/workflows/upload-build-android.yml | 50 +++++----- .github/workflows/upload-build-ios.yml | 43 ++++---- docs/flutter.md | 95 ++++++++++++++++-- 4 files changed, 203 insertions(+), 89 deletions(-) diff --git a/.github/workflows/draft-release-flutter.yml b/.github/workflows/draft-release-flutter.yml index b38c9f4..6852e45 100644 --- a/.github/workflows/draft-release-flutter.yml +++ b/.github/workflows/draft-release-flutter.yml @@ -1,5 +1,6 @@ name: draft release / flutter +# See docs/flutter.md for required `signing` environment variables and secrets. on: workflow_call: inputs: @@ -29,45 +30,6 @@ on: required: false type: string default: upload - secrets: - ANDROID_APP_SIGNING_KEYSTORE_B64: - description: > - Base64-encoded Android app signing keystore (PKCS12 or JKS) used by - Gradle to sign release APKs in CI. - required: true - ANDROID_APP_SIGNING_KEYSTORE_PASSWORD: - description: > - Password protecting the Android app signing keystore file. - required: true - ANDROID_APP_SIGNING_KEY_PASSWORD: - description: > - Password for the app signing key alias within the Android keystore. - required: true - ANDROID_UPLOAD_KEYSTORE_B64: - description: > - Base64-encoded Android upload keystore (PKCS12 or JKS) used by - Gradle to sign release App Bundles in CI. - required: false - ANDROID_UPLOAD_KEYSTORE_PASSWORD: - description: > - Password protecting the Android upload keystore file. - required: false - ANDROID_UPLOAD_KEY_PASSWORD: - description: > - Password for the upload key alias within the Android keystore. - required: false - IOS_SIGNING_CERT_B64: - description: > - Base64-encoded iOS distribution signing certificate (P12) used for App Store builds. - required: false - IOS_SIGNING_CERT_PASSWORD: - description: > - Password protecting the iOS signing certificate. - required: false - IOS_PROVISIONING_PROFILE_B64: - description: > - Base64-encoded iOS provisioning profile for App Store builds. - required: false permissions: contents: read @@ -130,6 +92,28 @@ jobs: - name: Install dependencies run: flutter pub get --enforce-lockfile + - name: Validate Android upload key secrets + if: ${{ inputs.build_android_aab }} + env: + ANDROID_UPLOAD_KEYSTORE_B64: ${{ secrets.ANDROID_UPLOAD_KEYSTORE_B64 }} + ANDROID_UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_UPLOAD_KEYSTORE_PASSWORD }} + ANDROID_UPLOAD_KEY_PASSWORD: ${{ secrets.ANDROID_UPLOAD_KEY_PASSWORD }} + run: | + set -euo pipefail + + if [[ -z "${ANDROID_UPLOAD_KEYSTORE_B64}" ]]; then + echo "::error::Missing ANDROID_UPLOAD_KEYSTORE_B64 in the signing environment." + exit 1 + fi + if [[ -z "${ANDROID_UPLOAD_KEYSTORE_PASSWORD}" ]]; then + echo "::error::Missing ANDROID_UPLOAD_KEYSTORE_PASSWORD in the signing environment." + exit 1 + fi + if [[ -z "${ANDROID_UPLOAD_KEY_PASSWORD}" ]]; then + echo "::error::Missing ANDROID_UPLOAD_KEY_PASSWORD in the signing environment." + exit 1 + fi + - name: Configure Android signing (upload key) if: ${{ inputs.build_android_aab }} env: @@ -146,6 +130,27 @@ jobs: if: ${{ inputs.build_android_aab }} run: flutter build appbundle + - name: Validate Android app signing secrets + env: + ANDROID_APP_SIGNING_KEYSTORE_B64: ${{ secrets.ANDROID_APP_SIGNING_KEYSTORE_B64 }} + ANDROID_APP_SIGNING_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_APP_SIGNING_KEYSTORE_PASSWORD }} + ANDROID_APP_SIGNING_KEY_PASSWORD: ${{ secrets.ANDROID_APP_SIGNING_KEY_PASSWORD }} + run: | + set -euo pipefail + + if [[ -z "${ANDROID_APP_SIGNING_KEYSTORE_B64}" ]]; then + echo "::error::Missing ANDROID_APP_SIGNING_KEYSTORE_B64 in the signing environment." + exit 1 + fi + if [[ -z "${ANDROID_APP_SIGNING_KEYSTORE_PASSWORD}" ]]; then + echo "::error::Missing ANDROID_APP_SIGNING_KEYSTORE_PASSWORD in the signing environment." + exit 1 + fi + if [[ -z "${ANDROID_APP_SIGNING_KEY_PASSWORD}" ]]; then + echo "::error::Missing ANDROID_APP_SIGNING_KEY_PASSWORD in the signing environment." + exit 1 + fi + - name: Configure Android signing (app signing key) env: ANDROID_SIGNING_ROLE: appsigning @@ -203,6 +208,27 @@ jobs: - name: Install dependencies run: flutter pub get --enforce-lockfile + - name: Validate iOS signing secrets + env: + IOS_SIGNING_CERT_B64: ${{ secrets.IOS_SIGNING_CERT_B64 }} + IOS_SIGNING_CERT_PASSWORD: ${{ secrets.IOS_SIGNING_CERT_PASSWORD }} + IOS_PROVISIONING_PROFILE_B64: ${{ secrets.IOS_PROVISIONING_PROFILE_B64 }} + run: | + set -euo pipefail + + if [[ -z "${IOS_SIGNING_CERT_B64}" ]]; then + echo "::error::Missing IOS_SIGNING_CERT_B64 in the signing environment." + exit 1 + fi + if [[ -z "${IOS_SIGNING_CERT_PASSWORD}" ]]; then + echo "::error::Missing IOS_SIGNING_CERT_PASSWORD in the signing environment." + exit 1 + fi + if [[ -z "${IOS_PROVISIONING_PROFILE_B64}" ]]; then + echo "::error::Missing IOS_PROVISIONING_PROFILE_B64 in the signing environment." + exit 1 + fi + - id: generate_password name: Generate ephemeral password run: | diff --git a/.github/workflows/upload-build-android.yml b/.github/workflows/upload-build-android.yml index 3cbecfd..fd5e42b 100644 --- a/.github/workflows/upload-build-android.yml +++ b/.github/workflows/upload-build-android.yml @@ -1,33 +1,12 @@ name: upload build +# See docs/flutter.md for required `release` environment variables and secrets. on: workflow_call: inputs: release_tag: required: true type: string - gcp_wif_provider: - description: > - Full resource name of the Google Cloud Workload Identity Federation - provider used for GitHub OIDC authentication - (format: projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID). - required: true - type: string - gcp_service_account: - description: > - Email address of the Google Cloud service account to impersonate via - Workload Identity Federation. This service account must be granted - the appropriate permissions in the Google Play Console for uploading - Android app builds (no static service account keys are used). - required: true - type: string - package_name: - description: > - The Android application ID of the app in Google Play (for example, - 'com.earthmanmuons.whatchord'). This must match the package name used - when the app was first created in the Play Console. - required: true - type: string track: description: > The Google Play release track to which the uploaded app bundle will be @@ -108,18 +87,39 @@ jobs: - name: Verify release assets run: verify-checksums --dir dist --pattern '*.aab' + - name: Validate Google Play authentication configuration + env: + GCP_WIF_PROVIDER: ${{ vars.GCP_WIF_PROVIDER }} + GCP_SERVICE_ACCOUNT: ${{ vars.GCP_SERVICE_ACCOUNT }} + GOOGLE_PLAY_PACKAGE_NAME: ${{ vars.GOOGLE_PLAY_PACKAGE_NAME }} + run: | + set -euo pipefail + + if [[ -z "${GCP_WIF_PROVIDER}" ]]; then + echo "::error::Missing GCP_WIF_PROVIDER in the release environment." + exit 1 + fi + if [[ -z "${GCP_SERVICE_ACCOUNT}" ]]; then + echo "::error::Missing GCP_SERVICE_ACCOUNT in the release environment." + exit 1 + fi + if [[ -z "${GOOGLE_PLAY_PACKAGE_NAME}" ]]; then + echo "::error::Missing GOOGLE_PLAY_PACKAGE_NAME in the release environment." + exit 1 + fi + - id: gcp_auth name: Configure Google Cloud authentication uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 with: - workload_identity_provider: ${{ inputs.gcp_wif_provider }} - service_account: ${{ inputs.gcp_service_account }} + workload_identity_provider: ${{ vars.GCP_WIF_PROVIDER }} + service_account: ${{ vars.GCP_SERVICE_ACCOUNT }} - name: upload aab uses: r0adkll/upload-google-play@935ef9c68bb393a8e6116b1575626a7f5be3a7fb with: serviceAccountJson: ${{ steps.gcp_auth.outputs.credentials_file_path }} - packageName: ${{ inputs.package_name }} + packageName: ${{ vars.GOOGLE_PLAY_PACKAGE_NAME }} releaseFiles: dist/*.aab track: ${{ inputs.track }} status: ${{ inputs.release_status }} diff --git a/.github/workflows/upload-build-ios.yml b/.github/workflows/upload-build-ios.yml index dc3003f..1be98f0 100644 --- a/.github/workflows/upload-build-ios.yml +++ b/.github/workflows/upload-build-ios.yml @@ -1,26 +1,12 @@ name: upload build +# See docs/flutter.md for required `release` environment variables and secrets. on: workflow_call: inputs: release_tag: required: true type: string - asc_issuer_id: - description: > - App Store Connect issuer identifier. Required for JWT authentication. - required: true - type: string - asc_key_id: - description: > - App Store Connect API key identifier. Required for JWT authentication. - required: true - type: string - secrets: - ASC_AUTH_KEY_B64: - description: > - Base64-encoded AuthKey file (.p8) used for App Store Connect API JWT authentication. - required: true jobs: upload_build: @@ -61,18 +47,39 @@ jobs: - name: Verify release assets run: verify-checksums --dir dist --pattern '*.ipa' + - name: Validate App Store Connect authentication configuration + env: + ASC_ISSUER_ID: ${{ vars.ASC_ISSUER_ID }} + ASC_KEY_ID: ${{ vars.ASC_KEY_ID }} + ASC_AUTH_KEY_B64: ${{ secrets.ASC_AUTH_KEY_B64 }} + run: | + set -euo pipefail + + if [[ -z "${ASC_ISSUER_ID}" ]]; then + echo "::error::Missing ASC_ISSUER_ID in the release environment." + exit 1 + fi + if [[ -z "${ASC_KEY_ID}" ]]; then + echo "::error::Missing ASC_KEY_ID in the release environment." + exit 1 + fi + if [[ -z "${ASC_AUTH_KEY_B64}" ]]; then + echo "::error::Missing ASC_AUTH_KEY_B64 in the release environment." + exit 1 + fi + - id: asc_auth name: Configure App Store Connect authentication env: ASC_AUTH_KEY_B64: ${{ secrets.ASC_AUTH_KEY_B64 }} - ASC_KEY_ID: ${{ inputs.asc_key_id }} + ASC_KEY_ID: ${{ vars.ASC_KEY_ID }} run: asc-auth-key-setup - name: Upload ipa env: API_PRIVATE_KEYS_DIR: ${{ steps.asc_auth.outputs.asc_key_dir }} - ASC_ISSUER_ID: ${{ inputs.asc_issuer_id }} - ASC_KEY_ID: ${{ inputs.asc_key_id }} + ASC_ISSUER_ID: ${{ vars.ASC_ISSUER_ID }} + ASC_KEY_ID: ${{ vars.ASC_KEY_ID }} run: | set -euo pipefail xcrun altool --upload-app --type ios \ diff --git a/docs/flutter.md b/docs/flutter.md index 32fb4b7..8055386 100644 --- a/docs/flutter.md +++ b/docs/flutter.md @@ -108,14 +108,95 @@ and uploads them to the release. | Name | Required | Default | | ----------------------------- | -------- | ---------- | | slug | true | — | +| build_android_aab | false | true | +| build_ios_ipa | false | true | | android_app_signing_key_alias | false | appsigning | | android_upload_key_alias | false | upload | -**Secrets** +**Environment Configuration** + +This workflow reads signing material from the `signing` environment in the +calling repository. Callers do not need to pass signing secrets through +`workflow_call`. + +**Required in `signing` environment (always)** + +- `secrets.ANDROID_APP_SIGNING_KEYSTORE_B64`: Base64-encoded Android app-signing + keystore (PKCS12 or JKS) used by Gradle when building release APKs. +- `secrets.ANDROID_APP_SIGNING_KEYSTORE_PASSWORD`: Password protecting the + Android app-signing keystore file. +- `secrets.ANDROID_APP_SIGNING_KEY_PASSWORD`: Password for the app-signing key + alias in the Android keystore. + +**Required in `signing` environment when `build_android_aab` is `true`** + +- `secrets.ANDROID_UPLOAD_KEYSTORE_B64`: Base64-encoded Android upload keystore + (PKCS12 or JKS) used by Gradle when building release App Bundles. +- `secrets.ANDROID_UPLOAD_KEYSTORE_PASSWORD`: Password protecting the Android + upload keystore file. +- `secrets.ANDROID_UPLOAD_KEY_PASSWORD`: Password for the upload key alias in + the Android keystore. + +**Required in `signing` environment when `build_ios_ipa` is `true`** + +- `secrets.IOS_SIGNING_CERT_B64`: Base64-encoded iOS distribution signing + certificate (`.p12`) for App Store builds. +- `secrets.IOS_SIGNING_CERT_PASSWORD`: Password protecting the iOS signing + certificate. +- `secrets.IOS_PROVISIONING_PROFILE_B64`: Base64-encoded iOS provisioning + profile for App Store builds. + +--- + +## upload-build-android.yml + +Uploads an Android App Bundle (`.aab`) from a release to Google Play via +Workload Identity Federation (WIF). + +**Inputs** + +| Name | Required | Default | +| -------------- | -------- | -------- | +| release_tag | true | — | +| track | false | internal | +| release_status | false | draft | + +**Environment Configuration** + +This workflow reads Google Play Console publishing configuration from the +`release` environment in the calling repository. + +**Required in `release` environment** + +- `vars.GCP_WIF_PROVIDER`: Full resource name of the Google Cloud Workload + Identity Federation provider + (`projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID`). +- `vars.GCP_SERVICE_ACCOUNT`: Email address of the Google Cloud service account + to impersonate via Workload Identity Federation. +- `vars.GOOGLE_PLAY_PACKAGE_NAME`: Android application ID in Google Play (for + example `com.earthmanmuons.whatchord`). + +--- + +## upload-build-ios.yml + +Uploads an iOS IPA from a release to App Store Connect using API key +authentication. + +**Inputs** + +| Name | Required | Default | +| ----------- | -------- | ------- | +| release_tag | true | — | + +**Environment Configuration** + +This workflow reads App Store Connect authentication settings from the `release` +environment in the calling repository. + +**Required in `release` environment** -- `ANDROID_APP_SIGNING_KEYSTORE_B64` -- `ANDROID_APP_SIGNING_KEYSTORE_PASSWORD` -- `ANDROID_APP_SIGNING_KEY_PASSWORD` -- `ANDROID_UPLOAD_KEYSTORE_B64` -- `ANDROID_UPLOAD_KEYSTORE_PASSWORD` -- `ANDROID_UPLOAD_KEY_PASSWORD` +- `vars.ASC_ISSUER_ID`: App Store Connect issuer identifier. +- `vars.ASC_KEY_ID`: App Store Connect API key identifier. +- `secrets.ASC_AUTH_KEY_B64`: Base64-encoded App Store Connect private key + (`AuthKey_*.p8`).