mirror of
https://github.com/bulwarkmail/native.git
synced 2026-10-31 08:06:36 +00:00
469 lines
21 KiB
YAML
469 lines
21 KiB
YAML
name: Release Android
|
|
|
|
on:
|
|
release:
|
|
types: [published]
|
|
workflow_dispatch:
|
|
inputs:
|
|
attach_to_release_tag:
|
|
description: "Optional release tag to attach the APK to (leave empty to skip upload)"
|
|
required: false
|
|
default: ""
|
|
|
|
# Signing is described in docs/android-release.md.
|
|
env:
|
|
# Certificate SHA-256 of android/app/debug.keystore, the stock React Native
|
|
# template key (CN=Android Debug, SHA-1 5E:8F:16:06:...:F6:25). It is public,
|
|
# so Android 9+ must never see it as the signer of a published build.
|
|
DEBUG_CERT_SHA256: fac61745dc0903786fb9ede62a962b399f7348f0bb6f899b8332667591033b9c
|
|
|
|
jobs:
|
|
build:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 45
|
|
permissions:
|
|
contents: write
|
|
id-token: write
|
|
attestations: write
|
|
steps:
|
|
# Fail in seconds instead of after a 20-minute build. There is no
|
|
# unsigned or debug-signed fallback: anything signed with the debug key
|
|
# can be "updated" by anyone.
|
|
- name: Require the release signing secrets
|
|
env:
|
|
KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
|
KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
|
KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
|
KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
|
run: |
|
|
missing=""
|
|
[ -n "$KEYSTORE_BASE64" ] || missing="$missing ANDROID_KEYSTORE_BASE64"
|
|
[ -n "$KEYSTORE_PASSWORD" ] || missing="$missing ANDROID_KEYSTORE_PASSWORD"
|
|
[ -n "$KEY_ALIAS" ] || missing="$missing ANDROID_KEY_ALIAS"
|
|
[ -n "$KEY_PASSWORD" ] || missing="$missing ANDROID_KEY_PASSWORD"
|
|
if [ -n "$missing" ]; then
|
|
echo "::error title=Release signing is not configured::Missing repository secrets:$missing. Release APKs are never signed with the public debug key; see docs/android-release.md."
|
|
exit 1
|
|
fi
|
|
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 20
|
|
cache: npm
|
|
|
|
- uses: actions/setup-java@v4
|
|
with:
|
|
distribution: temurin
|
|
java-version: 17
|
|
cache: gradle
|
|
|
|
# The action installs the legacy `tools` package by default, which the
|
|
# runner's cmdline-tools 16 no longer carries ("Failed to find package
|
|
# 'tools'"). Gradle fetches the platforms and build-tools it needs.
|
|
- uses: android-actions/setup-android@v3
|
|
with:
|
|
packages: platform-tools
|
|
|
|
- name: Cache Android NDK / CMake
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/.android/build-cache
|
|
/usr/local/lib/android/sdk/ndk
|
|
/usr/local/lib/android/sdk/cmake
|
|
key: android-sdk-${{ runner.os }}-${{ hashFiles('android/build.gradle', 'android/gradle.properties', 'android/app/build.gradle') }}
|
|
restore-keys: android-sdk-${{ runner.os }}-
|
|
|
|
- name: Compute native cache key input
|
|
run: |
|
|
node -e "
|
|
const fs = require('fs');
|
|
const lock = JSON.parse(fs.readFileSync('package-lock.json', 'utf8'));
|
|
if (lock.packages && lock.packages['']) delete lock.packages[''].version;
|
|
delete lock.version;
|
|
fs.writeFileSync('.cxx-cache-input.json', JSON.stringify(lock.packages || {}));
|
|
"
|
|
|
|
- name: Cache native CMake build outputs
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
android/app/.cxx
|
|
android/app/build/intermediates/cxx
|
|
android/app/build/intermediates/merged_native_libs
|
|
android/app/build/intermediates/stripped_native_libs
|
|
key: cxx-${{ runner.os }}-${{ hashFiles('.cxx-cache-input.json', 'android/build.gradle', 'android/app/build.gradle') }}
|
|
restore-keys: cxx-${{ runner.os }}-
|
|
|
|
- name: Install JS dependencies
|
|
run: npm ci
|
|
|
|
# Checks the password and alias before the long build, and refuses a
|
|
# debug key that was uploaded as the release key by mistake.
|
|
- name: Decode and check the release key
|
|
id: key
|
|
env:
|
|
KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
|
KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
|
KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
|
run: |
|
|
set -euo pipefail
|
|
KEYSTORE="$RUNNER_TEMP/release.keystore"
|
|
CERT="$RUNNER_TEMP/release-cert.der"
|
|
echo "$KEYSTORE_BASE64" | base64 -d > "$KEYSTORE"
|
|
if ! keytool -exportcert -keystore "$KEYSTORE" -alias "$KEY_ALIAS" \
|
|
-storepass:env KEYSTORE_PASSWORD -file "$CERT"; then
|
|
echo "::error title=Release key unreadable::Could not read alias ANDROID_KEY_ALIAS from ANDROID_KEYSTORE_BASE64 with ANDROID_KEYSTORE_PASSWORD."
|
|
exit 1
|
|
fi
|
|
CERT_SHA256=$(sha256sum "$CERT" | cut -d' ' -f1)
|
|
OWNER=$(keytool -printcert -file "$CERT")
|
|
if [ "$CERT_SHA256" = "$DEBUG_CERT_SHA256" ] || [[ "$OWNER" == *"CN=Android Debug"* ]]; then
|
|
echo "::error title=Release key is a debug key::ANDROID_KEYSTORE_BASE64 holds an Android debug key. Generate a release key as described in docs/android-release.md."
|
|
exit 1
|
|
fi
|
|
echo "Release certificate SHA-256: $CERT_SHA256"
|
|
echo "store_file=$KEYSTORE" >> "$GITHUB_OUTPUT"
|
|
echo "cert_sha256=$CERT_SHA256" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Build release APK
|
|
working-directory: android
|
|
env:
|
|
ORG_GRADLE_PROJECT_BULWARK_RELEASE_STORE_FILE: ${{ steps.key.outputs.store_file }}
|
|
ORG_GRADLE_PROJECT_BULWARK_RELEASE_STORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
|
ORG_GRADLE_PROJECT_BULWARK_RELEASE_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
|
ORG_GRADLE_PROJECT_BULWARK_RELEASE_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
|
run: |
|
|
chmod +x ./gradlew
|
|
./gradlew assembleGithubRelease --no-daemon
|
|
|
|
- name: Put apksigner on the PATH
|
|
run: |
|
|
BUILD_TOOLS=$(ls -d "$ANDROID_HOME"/build-tools/*/ | sort -V | tail -n 1)
|
|
echo "Using $BUILD_TOOLS"
|
|
echo "$BUILD_TOOLS" >> "$GITHUB_PATH"
|
|
|
|
# Optional v3 key rotation away from the debug key that signed every
|
|
# release up to 0.1.62. Gradle signed the APK with the release key only,
|
|
# which existing installs reject as a different app. With a lineage the
|
|
# APK is re-signed so that Android 9+ sees the release key plus proof
|
|
# that the debug key handed over to it, while Android 7-8.1 (v2 only)
|
|
# still see the debug key. docs/android-release.md has the details.
|
|
- name: Apply the signing key rotation
|
|
id: rotation
|
|
env:
|
|
LINEAGE_BASE64: ${{ secrets.ANDROID_SIGNING_LINEAGE_BASE64 }}
|
|
KEYSTORE: ${{ steps.key.outputs.store_file }}
|
|
KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
|
KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
|
KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
|
RELEASE_CERT_SHA256: ${{ steps.key.outputs.cert_sha256 }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [ -z "$LINEAGE_BASE64" ]; then
|
|
echo "No ANDROID_SIGNING_LINEAGE_BASE64: keeping the release-key-only signature."
|
|
echo "rotated=false" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
LINEAGE="$RUNNER_TEMP/signing.lineage"
|
|
echo "$LINEAGE_BASE64" | base64 -d > "$LINEAGE"
|
|
apksigner lineage --in "$LINEAGE" --print-certs | tee "$RUNNER_TEMP/lineage.txt"
|
|
|
|
DIGESTS=$(sed -n 's/^Signer #[0-9]* in lineage certificate SHA-256 digest: //p' "$RUNNER_TEMP/lineage.txt")
|
|
if [ "$(sed -n '1p' <<< "$DIGESTS")" != "$DEBUG_CERT_SHA256" ] \
|
|
|| [ "$(sed -n '$p' <<< "$DIGESTS")" != "$RELEASE_CERT_SHA256" ]; then
|
|
echo "::error title=Wrong signing lineage::ANDROID_SIGNING_LINEAGE_BASE64 must lead from the debug key to the key in ANDROID_KEYSTORE_BASE64."
|
|
exit 1
|
|
fi
|
|
# Rollback on an old signer lets anything signed with it take the app
|
|
# back after the rotation, which would undo the whole point.
|
|
PAST_ROLLBACK=$(grep 'Has rollback capability' "$RUNNER_TEMP/lineage.txt" | sed -e 's/.*: *//' -e '$d')
|
|
if [[ "$PAST_ROLLBACK" == *true* ]]; then
|
|
echo "::error title=Unsafe signing lineage::An old signer in the lineage has the rollback capability. Recreate it with --set-rollback false."
|
|
exit 1
|
|
fi
|
|
|
|
# Oldest signer first: apksigner uses it for v2, and the rotated key
|
|
# for v3 from API 28. Without --rotation-min-sdk-version it would
|
|
# only rotate on Android 13+ (v3.1) and leave 9-12L on the debug key.
|
|
APK=android/app/build/outputs/apk/github/release/app-github-release.apk
|
|
apksigner sign \
|
|
--v4-signing-enabled false \
|
|
--ks android/app/debug.keystore --ks-key-alias androiddebugkey \
|
|
--ks-pass pass:android --key-pass pass:android \
|
|
--next-signer \
|
|
--ks "$KEYSTORE" --ks-key-alias "$KEY_ALIAS" \
|
|
--ks-pass env:KEYSTORE_PASSWORD --key-pass env:KEY_PASSWORD \
|
|
--lineage "$LINEAGE" \
|
|
--rotation-min-sdk-version 28 \
|
|
--out "$RUNNER_TEMP/app-release-rotated.apk" \
|
|
"$APK"
|
|
mv "$RUNNER_TEMP/app-release-rotated.apk" "$APK"
|
|
echo "rotated=true" >> "$GITHUB_OUTPUT"
|
|
|
|
# The last gate before anything is uploaded.
|
|
- name: Verify the APK signature
|
|
env:
|
|
RELEASE_CERT_SHA256: ${{ steps.key.outputs.cert_sha256 }}
|
|
ROTATED: ${{ steps.rotation.outputs.rotated }}
|
|
run: |
|
|
set -euo pipefail
|
|
APK=android/app/build/outputs/apk/github/release/app-github-release.apk
|
|
apksigner verify --verbose --print-certs "$APK"
|
|
# build-tools 36 prints "Signer #1 certificate SHA-256 digest: …",
|
|
# 37 prints "V3.0 Signer: certificate …" and "V2 Signer: …".
|
|
signers() {
|
|
apksigner verify --print-certs "$@" "$APK" \
|
|
| sed -n -E 's/^(Signer #[0-9]+|V[0-9.]+ Signer:) certificate SHA-256 digest: //p' \
|
|
| sort -u
|
|
}
|
|
MODERN=$(signers --min-sdk-version 28)
|
|
LEGACY=$(signers --max-sdk-version 27)
|
|
echo "Signer on Android 9+: $MODERN"
|
|
echo "Signer on Android 7-8.1: $LEGACY"
|
|
|
|
if [[ "$MODERN" == *"$DEBUG_CERT_SHA256"* ]]; then
|
|
echo "::error title=APK is debug-signed::Android 9+ would see the public debug key as the signer."
|
|
exit 1
|
|
fi
|
|
if [ "$MODERN" != "$RELEASE_CERT_SHA256" ]; then
|
|
echo "::error title=Wrong APK signer::Android 9+ would see '$MODERN', expected only the release key $RELEASE_CERT_SHA256."
|
|
exit 1
|
|
fi
|
|
if [ "$ROTATED" = "true" ]; then
|
|
echo "::warning title=Android 7-8.1 still trust the debug key::The v2 signature is made with the debug key so that installs on Android 7-8.1 can update. Those devices remain exposed; see docs/android-release.md."
|
|
elif [ "$LEGACY" != "$RELEASE_CERT_SHA256" ]; then
|
|
echo "::error title=Wrong APK signer::Android 7-8.1 would see '$LEGACY', expected only the release key $RELEASE_CERT_SHA256."
|
|
exit 1
|
|
fi
|
|
aapt2 dump badging "$APK" | grep '^package:' || true
|
|
|
|
# The R8 mapping goes out with the APK: without the one from this exact
|
|
# build, the obfuscated frames in a release stack trace cannot be
|
|
# decoded (docs/android-release.md, "Stack traces").
|
|
- name: Rename APK and R8 mapping with version
|
|
id: artifact
|
|
run: |
|
|
set -euo pipefail
|
|
VERSION=$(cat VERSION | tr -d '[:space:]')
|
|
SRC=android/app/build/outputs/apk/github/release/app-github-release.apk
|
|
DEST="bulwark-mobile-${VERSION}-${GITHUB_SHA::7}.apk"
|
|
cp "$SRC" "$DEST"
|
|
sha256sum "$DEST" > "$DEST.sha256"
|
|
|
|
MAPPING_SRC=android/app/build/outputs/mapping/githubRelease/mapping.txt
|
|
MAPPING=""
|
|
if [ -f "$MAPPING_SRC" ]; then
|
|
MAPPING="bulwark-mobile-${VERSION}-${GITHUB_SHA::7}-mapping.txt"
|
|
cp "$MAPPING_SRC" "$MAPPING"
|
|
elif grep -Eq '^android\.enableMinifyInReleaseBuilds=true\s*$' android/gradle.properties; then
|
|
echo "::error title=R8 mapping missing::R8 is on but the build wrote no $MAPPING_SRC, so stack traces from this APK could not be decoded."
|
|
exit 1
|
|
fi
|
|
|
|
echo "path=$DEST" >> "$GITHUB_OUTPUT"
|
|
echo "mapping=$MAPPING" >> "$GITHUB_OUTPUT"
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
{
|
|
echo "files<<FILES_EOF"
|
|
echo "$DEST"
|
|
echo "$DEST.sha256"
|
|
if [ -n "$MAPPING" ]; then echo "$MAPPING"; fi
|
|
echo "FILES_EOF"
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
# Ties the APK to this commit and workflow run, which the signing key
|
|
# alone does not: `gh attestation verify <apk> --repo bulwarkmail/native`.
|
|
- name: Attest build provenance
|
|
uses: actions/attest@v4
|
|
with:
|
|
subject-path: ${{ steps.artifact.outputs.path }}
|
|
|
|
- uses: actions/upload-artifact@v4
|
|
with:
|
|
name: ${{ steps.artifact.outputs.path }}
|
|
path: ${{ steps.artifact.outputs.path }}
|
|
if-no-files-found: error
|
|
|
|
- uses: actions/upload-artifact@v4
|
|
if: steps.artifact.outputs.mapping != ''
|
|
with:
|
|
name: ${{ steps.artifact.outputs.mapping }}
|
|
path: ${{ steps.artifact.outputs.mapping }}
|
|
if-no-files-found: error
|
|
|
|
- name: Resolve target release tag
|
|
id: target_tag
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
# A manual run with no tag only builds: it must not replace the APK
|
|
# of the latest release with one from another commit.
|
|
if [ "${{ github.event_name }}" = "release" ]; then
|
|
TAG="${{ github.event.release.tag_name }}"
|
|
else
|
|
TAG="${{ github.event.inputs.attach_to_release_tag }}"
|
|
fi
|
|
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
|
if [ -n "$TAG" ]; then
|
|
echo "Will attach APK to release: $TAG"
|
|
else
|
|
echo "No release tag resolved - APK is in workflow artifacts only."
|
|
fi
|
|
|
|
- name: Attach APK and R8 mapping to release
|
|
if: steps.target_tag.outputs.tag != ''
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
tag_name: ${{ steps.target_tag.outputs.tag }}
|
|
files: ${{ steps.artifact.outputs.files }}
|
|
|
|
# The Google Play bundle (docs/android-release.md, "Google Play"). It is
|
|
# signed with the release key, which Play knows as the upload key; Play
|
|
# re-signs what it delivers with its own app signing key. A separate job so
|
|
# the GitHub APK isn't held up by the three-ABI native build.
|
|
play-bundle:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 75
|
|
permissions:
|
|
contents: read
|
|
env:
|
|
PLAY_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
|
|
steps:
|
|
- name: Require the release signing secrets
|
|
env:
|
|
KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
|
KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
|
KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
|
KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
|
run: |
|
|
missing=""
|
|
[ -n "$KEYSTORE_BASE64" ] || missing="$missing ANDROID_KEYSTORE_BASE64"
|
|
[ -n "$KEYSTORE_PASSWORD" ] || missing="$missing ANDROID_KEYSTORE_PASSWORD"
|
|
[ -n "$KEY_ALIAS" ] || missing="$missing ANDROID_KEY_ALIAS"
|
|
[ -n "$KEY_PASSWORD" ] || missing="$missing ANDROID_KEY_PASSWORD"
|
|
if [ -n "$missing" ]; then
|
|
echo "::error title=Release signing is not configured::Missing repository secrets:$missing. See docs/android-release.md."
|
|
exit 1
|
|
fi
|
|
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 20
|
|
cache: npm
|
|
|
|
- uses: actions/setup-java@v4
|
|
with:
|
|
distribution: temurin
|
|
java-version: 17
|
|
cache: gradle
|
|
|
|
- uses: android-actions/setup-android@v3
|
|
with:
|
|
packages: platform-tools
|
|
|
|
- name: Install JS dependencies
|
|
run: npm ci
|
|
|
|
- name: Decode and check the upload key
|
|
id: key
|
|
env:
|
|
KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
|
KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
|
KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
|
run: |
|
|
set -euo pipefail
|
|
KEYSTORE="$RUNNER_TEMP/release.keystore"
|
|
CERT="$RUNNER_TEMP/release-cert.der"
|
|
echo "$KEYSTORE_BASE64" | base64 -d > "$KEYSTORE"
|
|
keytool -exportcert -keystore "$KEYSTORE" -alias "$KEY_ALIAS" \
|
|
-storepass:env KEYSTORE_PASSWORD -file "$CERT"
|
|
CERT_SHA256=$(sha256sum "$CERT" | cut -d' ' -f1)
|
|
if [ "$CERT_SHA256" = "$DEBUG_CERT_SHA256" ]; then
|
|
echo "::error title=Upload key is a debug key::ANDROID_KEYSTORE_BASE64 holds the Android debug key."
|
|
exit 1
|
|
fi
|
|
echo "store_file=$KEYSTORE" >> "$GITHUB_OUTPUT"
|
|
echo "cert_sha256=$CERT_SHA256" >> "$GITHUB_OUTPUT"
|
|
|
|
# Play splits a bundle per ABI, so the extra architectures cost a phone
|
|
# nothing and let 32-bit ARM phones and x86_64 Chromebooks install it.
|
|
- name: Build the Play bundle
|
|
working-directory: android
|
|
env:
|
|
ORG_GRADLE_PROJECT_BULWARK_RELEASE_STORE_FILE: ${{ steps.key.outputs.store_file }}
|
|
ORG_GRADLE_PROJECT_BULWARK_RELEASE_STORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
|
ORG_GRADLE_PROJECT_BULWARK_RELEASE_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
|
ORG_GRADLE_PROJECT_BULWARK_RELEASE_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
|
run: |
|
|
chmod +x ./gradlew
|
|
./gradlew bundlePlayRelease --no-daemon -PreactNativeArchitectures=armeabi-v7a,arm64-v8a,x86_64
|
|
|
|
# Play rejects a bundle signed with another key than the registered
|
|
# upload key, and its policies reject a self-updater and the media
|
|
# permission. Catch all three here instead of in the Play Console.
|
|
- name: Check the bundle
|
|
env:
|
|
RELEASE_CERT_SHA256: ${{ steps.key.outputs.cert_sha256 }}
|
|
run: |
|
|
set -euo pipefail
|
|
AAB=android/app/build/outputs/bundle/playRelease/app-play-release.aab
|
|
SIGNER=$(keytool -printcert -jarfile "$AAB" | sed -n 's/^[[:space:]]*SHA256: //p' | head -n 1 | tr -d ':' | tr 'A-F' 'a-f')
|
|
echo "Bundle signer SHA-256: $SIGNER"
|
|
if [ "$SIGNER" != "$RELEASE_CERT_SHA256" ]; then
|
|
echo "::error title=Wrong bundle signer::Expected the upload key $RELEASE_CERT_SHA256, got '$SIGNER'."
|
|
exit 1
|
|
fi
|
|
MANIFEST=$(unzip -p "$AAB" base/manifest/AndroidManifest.xml | tr -c '[:print:]' '\n')
|
|
if ! grep -q 'org.bulwarkmail.mobile' <<< "$MANIFEST"; then
|
|
echo "::error title=Wrong package::The bundle is not org.bulwarkmail.mobile."
|
|
exit 1
|
|
fi
|
|
for permission in REQUEST_INSTALL_PACKAGES READ_MEDIA_IMAGES READ_MEDIA_VIDEO RECORD_AUDIO; do
|
|
if grep -q "android.permission.$permission" <<< "$MANIFEST"; then
|
|
echo "::error title=Permission not allowed on Play::The Play bundle declares $permission."
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
- name: Name the bundle and R8 mapping with version
|
|
id: artifact
|
|
run: |
|
|
set -euo pipefail
|
|
VERSION=$(cat VERSION | tr -d '[:space:]')
|
|
AAB="bulwark-mobile-play-${VERSION}-${GITHUB_SHA::7}.aab"
|
|
MAPPING="bulwark-mobile-play-${VERSION}-${GITHUB_SHA::7}-mapping.txt"
|
|
cp android/app/build/outputs/bundle/playRelease/app-play-release.aab "$AAB"
|
|
cp android/app/build/outputs/mapping/playRelease/mapping.txt "$MAPPING"
|
|
echo "aab=$AAB" >> "$GITHUB_OUTPUT"
|
|
echo "mapping=$MAPPING" >> "$GITHUB_OUTPUT"
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
|
|
- uses: actions/upload-artifact@v4
|
|
with:
|
|
name: ${{ steps.artifact.outputs.aab }}
|
|
path: |
|
|
${{ steps.artifact.outputs.aab }}
|
|
${{ steps.artifact.outputs.mapping }}
|
|
if-no-files-found: error
|
|
|
|
# Runs once PLAY_SERVICE_ACCOUNT_JSON is set (Play only accepts API
|
|
# uploads after the first bundle was uploaded by hand in the Console).
|
|
# Every release goes out to the internal and closed (beta) testers right
|
|
# away; closed testing may wait for Google's review first.
|
|
- name: Upload to Google Play (internal and closed testing)
|
|
if: env.PLAY_SERVICE_ACCOUNT_JSON != ''
|
|
uses: r0adkll/upload-google-play@v1
|
|
with:
|
|
serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
|
|
packageName: org.bulwarkmail.mobile
|
|
releaseFiles: ${{ steps.artifact.outputs.aab }}
|
|
mappingFile: ${{ steps.artifact.outputs.mapping }}
|
|
releaseName: ${{ steps.artifact.outputs.version }}
|
|
tracks: internal,beta
|
|
status: completed
|