name: Release APK run-name: Release APK ${{ inputs.version }} on: workflow_dispatch: inputs: version: description: 'Release tag to create, e.g. v1.0.0' required: true type: string concurrency: release-apk permissions: {} jobs: apk: runs-on: ubuntu-latest timeout-minutes: 30 permissions: contents: write # create the tag + release id-token: write # sign the build attestation attestations: write env: TAG: ${{ inputs.version }} steps: - name: Validate version run: | [[ "$TAG" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]] || { echo "::error::version must look like v1.0.0"; exit 1; } MA=${BASH_REMATCH[1]} MI=${BASH_REMATCH[2]} PA=${BASH_REMATCH[3]} (( MI < 100 && PA < 100 )) || { echo "::error::minor and patch must be < 100"; exit 1; } echo "VERSION=${TAG#v}" >> $GITHUB_ENV echo "CODE=$((MA*10000 + MI*100 + PA))" >> $GITHUB_ENV echo "APK=sune-$TAG.apk" >> $GITHUB_ENV echo "BT=$RUNNER_TEMP/sdk/build-tools/36.1.0" >> $GITHUB_ENV - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Fail if the release already exists if: github.ref == 'refs/heads/master' env: GH_TOKEN: ${{ github.token }} run: | if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then echo "::error::$TAG already exists"; exit 1 fi - uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: distribution: temurin java-version: 17 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 22 - name: Install Android SDK + Bubblewrap run: | SDK=$RUNNER_TEMP/sdk mkdir -p "$SDK/bin" # bubblewrap only checks that this exists SM=$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager yes | "$SM" --sdk_root="$SDK" --licenses >/dev/null || true # yes exits on SIGPIPE "$SM" --sdk_root="$SDK" "build-tools;36.1.0" "platforms;android-36" >/dev/null echo "ANDROID_HOME=$SDK" >> $GITHUB_ENV # the runner's own ANDROID_SDK_ROOT would conflict echo "ANDROID_SDK_ROOT=$SDK" >> $GITHUB_ENV npm install --global --ignore-scripts @bubblewrap/cli@1.25.0 mkdir -p ~/.bubblewrap jq -n --arg jdk "$JAVA_HOME" --arg sdk "$SDK" '{jdkPath:$jdk,androidSdkPath:$sdk}' > ~/.bubblewrap/config.json - name: Build + sign env: ANDROID_KEYSTORE_B64: ${{ secrets.ANDROID_KEYSTORE_B64 }} ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} BUBBLEWRAP_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASS }} BUBBLEWRAP_KEY_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASS }} run: | B=$RUNNER_TEMP/build OUT=$RUNNER_TEMP/out mkdir -p "$B" "$OUT" echo "$ANDROID_KEYSTORE_B64" | base64 -d > "$B/sune.jks" # pin the icon to this exact commit instead of whatever the site serves today ICON="https://raw.githubusercontent.com/$GITHUB_REPOSITORY/$GITHUB_SHA/public/appstore_content/%E2%9C%BA.png" jq --arg v "$VERSION" --argjson c "$CODE" --arg a "$ANDROID_KEY_ALIAS" --arg i "$ICON" \ '.appVersion=$v | .appVersionCode=$c | .signingKey.alias=$a | .iconUrl=$i | .maskableIconUrl=$i' \ android/twa-manifest.json > "$B/twa-manifest.json" cd "$B" bubblewrap update --manifest ./twa-manifest.json --skipVersionUpgrade bubblewrap build --manifest ./twa-manifest.json --skipPwaValidation mv app-release-signed.apk "$OUT/$APK" rm -f sune.jks echo "OUT=$OUT" >> $GITHUB_ENV - name: Verify the APK run: | cd "$OUT" "$BT/apksigner" verify --verbose --print-certs "$APK" | tee verify.txt grep -q "Number of signers: 1" verify.txt GOT=$(sed -n 's/.*certificate SHA-256 digest: //p' verify.txt | sed -n 1p) WANT=$(jq -r '.[].target | select(.package_name=="chat.sune.app") | .sha256_cert_fingerprints[]' "$GITHUB_WORKSPACE/public/.well-known/assetlinks.json" | tr -d ':' | tr A-F a-f) [ "$GOT" = "$WANT" ] || { echo "::error::signing cert $GOT does not match assetlinks.json ($WANT)"; exit 1; } "$BT/aapt2" dump badging "$APK" > full-badging.txt grep -m1 '^package:' full-badging.txt | tee badging.txt grep -q "name='chat.sune.app' versionCode='$CODE' versionName='$VERSION'" badging.txt sha256sum "$APK" | tee "$APK.sha256" echo "SIGNER=$GOT" >> $GITHUB_ENV - name: Attest build provenance if: github.ref == 'refs/heads/master' uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 with: subject-path: ${{ runner.temp }}/out/sune-${{ inputs.version }}.apk - name: Publish release if: github.ref == 'refs/heads/master' env: GH_TOKEN: ${{ github.token }} run: | cd "$OUT" SHA=$(cut -d' ' -f1 "$APK.sha256") cat > notes.md <