Log encryption failure cause under DEBUG in encryptWithCipher #616
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Reproducibility | |
| on: | |
| workflow_dispatch: | |
| schedule: | |
| - cron: '0 6 * * 1' | |
| push: | |
| tags: | |
| - "v*" | |
| pull_request: | |
| types: [opened, synchronize, reopened, labeled] | |
| permissions: {} | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| # These pins are not consumed by the steps below (the build runs inside | |
| # Dockerfile.reproducible, which owns its own pinned 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: | |
| verify: | |
| if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'reproducibility') | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 90 | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| # Resolve the release tag whose published APK we compare against, then | |
| # build from that exact tag (below) so the build ref and keep.version | |
| # match the published artifact. On schedule/dispatch the runner checked | |
| # out HEAD, which may have advanced past the latest release tag. | |
| - name: Resolve release version to verify | |
| id: ver | |
| if: github.event_name != 'pull_request' | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| REF_NAME: ${{ github.ref_name }} | |
| REPOSITORY: ${{ github.repository }} | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| if [ "$EVENT_NAME" = "push" ]; then | |
| VERSION="$REF_NAME" | |
| else | |
| # Authenticated + retried lookup. An unauthenticated api.github.com | |
| # request shares the runner's egress IP and can hit the 60/hr limit, | |
| # which -eo pipefail turns into an immediate step failure (the weekly | |
| # cron went red this way). The workflow token raises the limit to | |
| # 1000/hr, and the retry rides out any transient API blip. | |
| VERSION="" | |
| for attempt in 1 2 3 4 5; do | |
| if VERSION="$(gh api "repos/${REPOSITORY}/releases/latest" --jq .tag_name)" \ | |
| && [ -n "$VERSION" ] && [ "$VERSION" != "null" ]; then | |
| break | |
| fi | |
| VERSION="" | |
| if [ "$attempt" -lt 5 ]; then | |
| echo "releases/latest lookup failed (attempt $attempt/5); retrying in 15s..." >&2 | |
| sleep 15 | |
| fi | |
| done | |
| fi | |
| if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then | |
| echo "::error::could not determine release version to verify" >&2 | |
| exit 1 | |
| fi | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "Will verify reproduction of $VERSION" | |
| # Build from the published tag (not HEAD) so the source tree and | |
| # keep.version match the APK we downloaded. Skipped on PRs, which build | |
| # the PR head to prove the reproducible path still builds. | |
| - name: Check out published release tag | |
| if: github.event_name != 'pull_request' | |
| env: | |
| VERSION: ${{ steps.ver.outputs.version }} | |
| run: | | |
| git fetch --no-tags --depth 1 origin "+refs/tags/${VERSION}:refs/tags/${VERSION}" | |
| git checkout -f "refs/tags/${VERSION}" | |
| echo "Building from tag ${VERSION} (keep.version $(tr -d '[:space:]' < keep.version))" | |
| # Build the APK exactly as release.yml does: inside the pinned | |
| # Dockerfile.reproducible container. This verifies that the container | |
| # build is deterministic and reproduces the published release payload | |
| # when run here. It is NOT an independent cross-environment rebuild: it | |
| # runs on the same GitHub runner with the same Dockerfile, so it does not | |
| # by itself prove that F-Droid's buildserver will reproduce the bytes. The | |
| # bare GitHub runner produces a non-standard aws-lc build, which is why | |
| # this builds in the pinned container rather than directly on the runner. | |
| - name: Build APK in pinned container | |
| run: | | |
| DOCKER_BUILDKIT=1 docker build \ | |
| --build-arg KEEP_SHA="$(tr -d '[:space:]' < keep.version)" \ | |
| --output type=local,dest=out \ | |
| -f Dockerfile.reproducible . | |
| # ABI splits produce one APK per architecture. | |
| ls out/app-*-release.apk | |
| test "$(ls out/app-*-release.apk | wc -l)" -ge 2 | |
| - name: Setup Android SDK | |
| if: github.event_name != 'pull_request' | |
| uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1 | |
| - name: Install build-tools and put apksigner on PATH | |
| if: github.event_name != 'pull_request' | |
| run: | | |
| sdkmanager --install "build-tools;${{ env.BUILD_TOOLS_VERSION }}" | |
| # apksigcopier invokes `apksigner` from PATH; build-tools is not on | |
| # PATH by default, so add it. | |
| echo "${{ env.ANDROID_HOME }}/build-tools/${{ env.BUILD_TOOLS_VERSION }}" >> "$GITHUB_PATH" | |
| - name: Install apksigcopier | |
| if: github.event_name != 'pull_request' | |
| run: pipx install 'apksigcopier==1.1.1' | |
| # Verify the container build reproduces the published, developer-signed | |
| # release APK. apksigcopier compares the zip payload while ignoring the | |
| # signing block, exactly the comparison F-Droid performs. Skipped on PRs, | |
| # which have no matching published APK (the container build above already | |
| # proves the reproducible path still builds). | |
| - name: Reproduce published release | |
| if: github.event_name != 'pull_request' | |
| env: | |
| VERSION: ${{ steps.ver.outputs.version }} | |
| REPOSITORY: ${{ github.repository }} | |
| run: | | |
| echo "Verifying reproduction of $VERSION" | |
| # One published APK per architecture; compare each to its container build. | |
| for built in out/app-*-release.apk; do | |
| abi="$(basename "$built" | sed -E 's/^app-(.*)-release\.apk$/\1/')" | |
| URL="https://github.com/${REPOSITORY}/releases/download/${VERSION}/keep-android-${VERSION}-${abi}.apk" | |
| # On a tag push this runs concurrently with release.yml, so the asset | |
| # may not be published yet. Wait for it (up to ~20 min). | |
| ok="" | |
| for i in $(seq 1 40); do | |
| if curl -fsSL -o "published-${abi}.apk" "$URL"; then ok=1; break; fi | |
| echo "Published $abi APK not available yet (attempt $i/40); waiting 30s..." | |
| sleep 30 | |
| done | |
| if [ -z "$ok" ] || ! test -s "published-${abi}.apk"; then | |
| echo "::error::published $abi APK never became available at $URL" >&2 | |
| exit 1 | |
| fi | |
| apksigcopier compare "published-${abi}.apk" "$built" | |
| echo "Container build reproduces the published $VERSION $abi APK (payload identical)." | |
| done |