diff --git a/.github/workflows/brrrboard-apk-only.yml b/.github/workflows/brrrboard-apk-only.yml new file mode 100644 index 0000000000..8a895ac508 --- /dev/null +++ b/.github/workflows/brrrboard-apk-only.yml @@ -0,0 +1,84 @@ +name: brrrBoard — APK Only (Audit) # independent APK delivery path + +on: + push: + branches: + - 'brrrboard-audit-*' + +permissions: + contents: write + +jobs: + apk: + runs-on: ubuntu-latest + timeout-minutes: 25 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Restore persistent debug signing key + shell: bash + run: | + set -euo pipefail + test -f .github/debug.keystore + mkdir -p "$HOME/.android" + cp .github/debug.keystore "$HOME/.android/debug.keystore" + chmod 600 "$HOME/.android/debug.keystore" + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: temurin + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Build debug APK + shell: bash + run: | + set -euo pipefail + chmod +x gradlew + ./gradlew --no-daemon --stacktrace assembleDebug + + - name: Verify APK and signing identity + shell: bash + run: | + set -euo pipefail + APK="$(find app/build/outputs/apk/debug -maxdepth 1 -type f -name '*-debug.apk' | head -n 1)" + test -n "$APK" + test -s "$APK" + APKSIGNER="$(find "$ANDROID_HOME/build-tools" -type f -name apksigner | sort -V | tail -n 1)" + CERT_OUTPUT="$("$APKSIGNER" verify --print-certs "$APK")" + EXPECTED_CERT="$(keytool -list -v -keystore .github/debug.keystore -storepass android -alias androiddebugkey | sed -n 's/^.*SHA256:[[:space:]]*//p' | head -n 1 | tr -d ':' | tr '[:lower:]' '[:upper:]')" + ACTUAL_CERT="$(printf '%s\n' "$CERT_OUTPUT" | sed -n 's/^.*SHA-256 digest:[[:space:]]*//p' | head -n 1 | tr -d ':' | tr '[:lower:]' '[:upper:]')" + test -n "$EXPECTED_CERT" + test -n "$ACTUAL_CERT" + test "$EXPECTED_CERT" = "$ACTUAL_CERT" + SHA256="$(sha256sum "$APK" | cut -d' ' -f1)" + echo "APK=$APK" >> "$GITHUB_ENV" + echo "SHA256=$SHA256" >> "$GITHUB_ENV" + echo "APK_SIZE=$(stat -c '%s' "$APK")" >> "$GITHUB_ENV" + echo "APK: $APK" + echo "SHA-256: $SHA256" + + - name: Upload APK artifact + uses: actions/upload-artifact@v4 + with: + name: brrrBoard-debug-apk-${{ github.sha }} + path: ${{ env.APK }} + if-no-files-found: error + retention-days: 14 + + - name: Publish audit release + env: + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + set -euo pipefail + SHORT_SHA="${GITHUB_SHA::7}" + STAMP="$(date -u +'%Y%m%d-%H%M%S')" + TAG="brrrboard-audit-${STAMP}-${SHORT_SHA}" + gh release create "$TAG" "$APK" --repo "$GITHUB_REPOSITORY" --target "$GITHUB_SHA" --title "brrrBoard Audit APK — ${SHORT_SHA}" --notes "APK-only audit build. Commit: $GITHUB_SHA. SHA-256: $SHA256. Built with the tracked persistent debug signing key." --prerelease \ No newline at end of file diff --git a/.github/workflows/brrrboard-build-debug.yml b/.github/workflows/brrrboard-build-debug.yml new file mode 100644 index 0000000000..da7e742301 --- /dev/null +++ b/.github/workflows/brrrboard-build-debug.yml @@ -0,0 +1,123 @@ +name: brrrBoard — Build Debug APK + +on: + push: + branches: + - main + - 'brrrboard-audit-*' + workflow_dispatch: + +permissions: + contents: write + +concurrency: + group: brrrBoard-debug-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 45 + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Restore persistent debug signing key + shell: bash + run: | + set -euo pipefail + test -f .github/debug.keystore + mkdir -p "$HOME/.android" + cp .github/debug.keystore "$HOME/.android/debug.keystore" + chmod 600 "$HOME/.android/debug.keystore" + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' # Robolectric SDK 36 requires JDK 21 + distribution: temurin + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Build debug APK + shell: bash + run: | + set -euo pipefail + chmod +x gradlew + ./gradlew --no-daemon --stacktrace assembleDebug + + - name: Verify APK and signing identity + shell: bash + run: | + set -euo pipefail + APK="$(find app/build/outputs/apk/debug -maxdepth 1 -type f -name '*-debug.apk' | head -n 1)" + test -n "$APK" + test -s "$APK" + + APKSIGNER="$(find "$ANDROID_HOME/build-tools" -type f -name apksigner | sort -V | tail -n 1)" + CERT_OUTPUT="$("$APKSIGNER" verify --print-certs "$APK")" + printf '%s\n' "$CERT_OUTPUT" + + EXPECTED_CERT="$(keytool -list -v -keystore .github/debug.keystore -storepass android -alias androiddebugkey | sed -n 's/^.*SHA256:[[:space:]]*//p' | head -n 1 | tr -d ':' | tr '[:lower:]' '[:upper:]')" + ACTUAL_CERT="$(printf '%s\n' "$CERT_OUTPUT" | sed -n 's/^.*SHA-256 digest:[[:space:]]*//p' | head -n 1 | tr -d ':' | tr '[:lower:]' '[:upper:]')" + + test -n "$EXPECTED_CERT" + test -n "$ACTUAL_CERT" + test "$EXPECTED_CERT" = "$ACTUAL_CERT" + + SHA256="$(sha256sum "$APK" | cut -d' ' -f1)" + echo "APK=$APK" >> "$GITHUB_ENV" + echo "SHA256=$SHA256" >> "$GITHUB_ENV" + echo "APK_SIZE=$(stat -c '%s' "$APK")" >> "$GITHUB_ENV" + + echo "APK: $APK" + echo "SHA-256: $SHA256" + ls -lh "$APK" + + - name: Upload debug APK artifact + uses: actions/upload-artifact@v4 + with: + name: brrrBoard-debug-apk-${{ github.sha }} + path: ${{ env.APK }} + if-no-files-found: error + retention-days: 14 + + - name: Run unit tests + id: unit_tests + continue-on-error: true + shell: bash + run: | + set -euo pipefail + ./gradlew --no-daemon --stacktrace testRunTests + + - name: Fail workflow when tests fail + if: steps.unit_tests.outcome == 'failure' + shell: bash + run: | + echo "Unit tests failed; APK was still built, verified, and published for audit/testing." + exit 1 + + - name: Publish GitHub release + if: github.ref_name != 'main' || steps.unit_tests.outcome == 'success' + env: + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + set -euo pipefail + VERSION_NAME="$(sed -n 's/.*versionName = "\([^"]*\)".*/\1/p' app/build.gradle.kts | head -n 1)" + SHORT_SHA="${GITHUB_SHA::7}" + STAMP="$(date -u +'%Y%m%d-%H%M%S')" + + if [ "$GITHUB_REF_NAME" = "main" ]; then + TAG="brrrboard-debug-${STAMP}-${SHORT_SHA}" + TITLE="brrrBoard Debug — ${VERSION_NAME} — ${SHORT_SHA}" + gh release create "$TAG" "$APK" --repo "$GITHUB_REPOSITORY" --target "$GITHUB_SHA" --title "$TITLE" --notes "Verified debug APK. Commit: $GITHUB_SHA. SHA-256: $SHA256. Uses the persistent debug signing key so later debug builds can update the same debug installation." + else + TAG="brrrboard-test-${STAMP}-${SHORT_SHA}" + TITLE="brrrBoard Test Build — ${SHORT_SHA}" + gh release create "$TAG" "$APK" --repo "$GITHUB_REPOSITORY" --target "$GITHUB_SHA" --title "$TITLE" --notes "Audit/test build. Commit: $GITHUB_SHA. SHA-256: $SHA256." --prerelease + fi \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a26e7e0f06..ec5b4b3cc9 100755 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -9,7 +9,7 @@ abstract class GitCommitCountValueSource : ValueSource