Skip to content

Align reproducible build to F-Droid env (trixie + JDK 21) for v1.1.4 … #34

Align reproducible build to F-Droid env (trixie + JDK 21) for v1.1.4 …

Align reproducible build to F-Droid env (trixie + JDK 21) for v1.1.4 … #34

Workflow file for this run

name: Release
on:
push:
tags:
- "v*"
permissions: {}
# NDK_VERSION / CARGO_NDK_VERSION / RUST_VERSION are not consumed directly by
# the build below (the pinned Dockerfile.reproducible owns the toolchain), but
# scripts/check-toolchain-pins.sh cross-validates them against the Dockerfile
# and build.gradle.kts, so they must stay in sync.
env:
ANDROID_HOME: /usr/local/lib/android/sdk
NDK_VERSION: "29.0.14206865"
CARGO_NDK_VERSION: "4.1.2"
RUST_VERSION: "1.89.0"
BUILD_TOOLS_VERSION: "37.0.0"
jobs:
build:
runs-on: ubuntu-24.04
timeout-minutes: 90
permissions:
contents: read
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Decode keystore
env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
run: |
if [ -z "$KEYSTORE_BASE64" ]; then
echo "error: KEYSTORE_BASE64 secret is empty." >&2
exit 1
fi
KEYSTORE_OUT="${{ runner.temp }}/release.keystore"
printf '%s' "$KEYSTORE_BASE64" | base64 -d > "$KEYSTORE_OUT"
if [ ! -s "$KEYSTORE_OUT" ]; then
echo "error: decoded keystore is empty; check KEYSTORE_BASE64 encoding." >&2
exit 1
fi
if ! keytool -list -keystore "$KEYSTORE_OUT" -storepass:env KEYSTORE_PASSWORD >/dev/null; then
echo "error: keystore failed to load (corrupt base64 or wrong password)." >&2
exit 1
fi
# Build the release APK inside the pinned, reproducible container
# (Dockerfile.reproducible) instead of on the bare runner. A clean, pinned
# environment is required for the native aws-lc artifacts to be
# bit-reproducible by F-Droid: the bare GitHub runner produces a
# non-standard aws-lc build that no clean environment (F-Droid's
# buildserver, a local checkout, this container) reproduces.
#
# The release keystore + passwords are passed as BuildKit secrets so
# Gradle signs the APK *during* the build. Signing during the build (not
# re-signing the finished APK with apksigner) keeps the payload identical
# to F-Droid's gradle-built rebuild — apksigner re-signing repacks the APK
# and would break the payload comparison. Secrets never land in an image
# layer or docker history.
- name: Build release-signed APK in pinned container
env:
REF_NAME: ${{ github.ref_name }}
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: |
DOCKER_BUILDKIT=1 docker build \
--build-arg KEEP_SHA="$(tr -d '[:space:]' < keep.version)" \
--secret id=keystore,src="${{ runner.temp }}/release.keystore" \
--secret id=keystore_pass,env=KEYSTORE_PASSWORD \
--secret id=key_alias,env=KEY_ALIAS \
--secret id=key_pass,env=KEY_PASSWORD \
--output type=local,dest=docker-out \
-f Dockerfile.reproducible .
test -f docker-out/app-release.apk
mv docker-out/app-release.apk "keep-android-${REF_NAME}.apk"
- name: Setup Android SDK
uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1
- name: Install build-tools (for apksigner)
run: sdkmanager --install "build-tools;${{ env.BUILD_TOOLS_VERSION }}"
- name: Verify APK signing certificate
env:
REF_NAME: ${{ github.ref_name }}
# SHA-256 of the release signing certificate (DER encoding). Must
# equal the AllowedAPKSigningKeys value in
# metadata/io.privkey.keep.yml; both are the digest apksigner reports
# as "certificate SHA-256 digest".
EXPECTED_CERT_SHA256: 3eb0e6f6f4e0962ebd717f84eea47428b73087b686c956633d6a9fcc4c854f50
run: |
APKSIGNER="$ANDROID_HOME/build-tools/${{ env.BUILD_TOOLS_VERSION }}/apksigner"
CERTS="$("$APKSIGNER" verify --print-certs "keep-android-${REF_NAME}.apk")"
echo "$CERTS"
ACTUAL="$(printf '%s\n' "$CERTS" \
| sed -nE 's/.*certificate SHA-256 digest:[[:space:]]*([0-9a-fA-F]+).*/\1/p' \
| head -1 | tr 'A-F' 'a-f')"
if [ -z "$ACTUAL" ]; then
echo "::error::could not parse signing certificate SHA-256 digest" >&2
exit 1
fi
if [ "$ACTUAL" != "$EXPECTED_CERT_SHA256" ]; then
echo "::error::signing cert SHA-256 $ACTUAL does not match expected release key $EXPECTED_CERT_SHA256" >&2
exit 1
fi
echo "Signing certificate matches the expected release key."
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: apk
path: keep-android-${{ github.ref_name }}.apk
release:
needs: [build]
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: write
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: artifacts
merge-multiple: true
- name: Generate checksums
run: |
cd artifacts
sha256sum * > SHA256SUMS
- name: Create release
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v2
with:
files: artifacts/*
generate_release_notes: true