Generate Assets Zips and Upload to Google Drive #92
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: Generate Assets Zips | |
| permissions: | |
| id-token: write | |
| contents: write | |
| actions: write | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| ref: | |
| description: Branch or tag to build the asset zips from | |
| required: false | |
| default: stage | |
| asset_source: | |
| description: Where the debug asset ingredients come from | |
| required: false | |
| default: site | |
| type: choice | |
| options: | |
| - site | |
| - release | |
| asset_release_tag: | |
| description: 'Release tag holding the debug ingredients (asset_source: release)' | |
| required: false | |
| default: adfa-2602-rc | |
| env: | |
| SCP_HOST: ${{ vars.GREENGEEKS_SSH_HOST }} | |
| jobs: | |
| prepare: | |
| name: Stage debug assets | |
| runs-on: self-hosted | |
| timeout-minutes: 90 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ inputs.ref }} | |
| - name: Remove stale staged assets | |
| run: | | |
| rm -f assets/*.zip assets/documentation.db assets/core.cgt | |
| ls -la assets/ 2>/dev/null || true | |
| - name: Check if Nix is installed | |
| id: check_nix | |
| run: | | |
| if command -v nix >/dev/null 2>&1; then | |
| echo "nix is installed" | |
| echo "nix_installed=true" >> $GITHUB_ENV | |
| else | |
| echo "nix is not installed" | |
| echo "nix_installed=false" >> $GITHUB_ENV | |
| fi | |
| - name: Install Flox | |
| if: env.nix_installed == 'false' | |
| uses: flox/install-flox-action@v2 | |
| - name: Create google-services.json | |
| env: | |
| GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} | |
| run: | | |
| echo "$GOOGLE_SERVICES_JSON" > app/google-services.json | |
| echo "google-services.json created successfully" | |
| - name: Authenticate to Google Cloud for Drive access | |
| id: auth_drive | |
| uses: google-github-actions/auth@v2 | |
| with: | |
| workload_identity_provider: ${{ secrets.WIF_PROVIDER }} | |
| service_account: ${{ secrets.IDENTITY_EMAIL }} | |
| token_format: 'access_token' | |
| access_token_scopes: 'https://www.googleapis.com/auth/drive' | |
| - name: Set up SSH key | |
| if: inputs.asset_source == 'site' | |
| env: | |
| GREENGEEKS_HOST: ${{ vars.GREENGEEKS_SSH_HOST }} | |
| GREENGEEKS_KEY: ${{ secrets.GREENGEEKS_SSH_PRIVATE_KEY }} | |
| GREENGEEKS_USER: ${{ vars.GREENGEEKS_SSH_USER }} | |
| run: | | |
| mkdir -p ~/.ssh | |
| if [ -z "$GREENGEEKS_HOST" ]; then | |
| echo "Error: SSH_HOST variable is not set" | |
| exit 1 | |
| fi | |
| # Write the SSH key, ensuring proper formatting | |
| echo "$GREENGEEKS_KEY" > ~/.ssh/id_rsa | |
| # Remove any trailing newlines and ensure proper key format | |
| sed -i '' -e '$ { /^$/ d; }' ~/.ssh/id_rsa 2>/dev/null || sed -i '$ { /^$/ d; }' ~/.ssh/id_rsa | |
| chmod 600 ~/.ssh/id_rsa | |
| # Verify key format | |
| if ! grep -q "BEGIN.*PRIVATE KEY" ~/.ssh/id_rsa; then | |
| echo "Error: SSH key does not appear to be in correct format" | |
| exit 1 | |
| fi | |
| # Configure SSH to use only the key file and disable other auth methods | |
| cat > ~/.ssh/config <<EOF | |
| Host * | |
| IdentitiesOnly yes | |
| PreferredAuthentications publickey | |
| StrictHostKeyChecking no | |
| UserKnownHostsFile ~/.ssh/known_hosts | |
| PubkeyAuthentication yes | |
| PasswordAuthentication no | |
| ChallengeResponseAuthentication no | |
| GSSAPIAuthentication no | |
| GSSAPIKeyExchange no | |
| GSSAPIDelegateCredentials no | |
| Host $GREENGEEKS_HOST | |
| User $GREENGEEKS_USER | |
| IdentityFile ~/.ssh/id_rsa | |
| IdentitiesOnly yes | |
| PreferredAuthentications publickey | |
| PubkeyAuthentication yes | |
| PasswordAuthentication no | |
| ChallengeResponseAuthentication no | |
| GSSAPIAuthentication no | |
| GSSAPIKeyExchange no | |
| GSSAPIDelegateCredentials no | |
| NumberOfPasswordPrompts 0 | |
| EOF | |
| chmod 600 ~/.ssh/config | |
| # Disable SSH agent completely | |
| unset SSH_AUTH_SOCK | |
| unset SSH_AGENT_PID | |
| # Remove any default SSH keys that might interfere | |
| rm -f ~/.ssh/id_ed25519 ~/.ssh/id_ecdsa ~/.ssh/id_dsa ~/.ssh/id_rsa.pub 2>/dev/null | |
| ssh-keyscan -H "$GREENGEEKS_HOST" >> ~/.ssh/known_hosts 2>/dev/null | |
| - name: Download debug assets from the site | |
| if: inputs.asset_source == 'site' | |
| run: | | |
| flox activate -d flox/base -- ./gradlew :app:assetsDownloadDebug --no-daemon \ | |
| -Dorg.gradle.jvmargs="-Xmx10g -XX:MaxMetaspaceSize=2g -XX:+HeapDumpOnOutOfMemoryError --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED" \ | |
| -Dandroid.aapt2.daemonHeapSize=4096M \ | |
| -Dorg.gradle.workers.max=1 \ | |
| -Dorg.gradle.parallel=false | |
| - name: Download debug assets from a release | |
| if: inputs.asset_source == 'release' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| ASSET_TAG: ${{ inputs.asset_release_tag }} | |
| run: | | |
| mkdir -p assets | |
| gh release download "$ASSET_TAG" --dir assets --clobber \ | |
| --pattern 'android-sdk-*.zip' \ | |
| --pattern 'bootstrap-*.zip' \ | |
| --pattern 'gradle-*-bin.zip' \ | |
| --pattern 'gradle-api-*.jar.zip' \ | |
| --pattern 'localMvnRepository.zip' \ | |
| --pattern 'core.cgt' | |
| ls -la assets/ | |
| - name: Download latest documentation.db from Google Drive | |
| run: | | |
| DB_FILE_ID="${{ secrets.DOCUMENTATION_DB_FILE_ID }}" | |
| ACCESS_TOKEN="${{ steps.auth_drive.outputs.access_token }}" | |
| if [ -z "$DB_FILE_ID" ]; then | |
| echo "ERROR: DOCUMENTATION_DB_FILE_ID secret not set" | |
| echo "Please set the DOCUMENTATION_DB_FILE_ID secret in repository settings" | |
| exit 1 | |
| fi | |
| echo "Downloading documentation.db from Google Drive..." | |
| mkdir -p assets | |
| curl -sL -H "Authorization: Bearer $ACCESS_TOKEN" \ | |
| "https://www.googleapis.com/drive/v3/files/${DB_FILE_ID}?alt=media&supportsAllDrives=true&acknowledgeAbuse=true" \ | |
| -o assets/documentation.db | |
| if [ ! -f assets/documentation.db ]; then | |
| echo "ERROR: Failed to download documentation.db" | |
| exit 1 | |
| fi | |
| FILE_SIZE_BYTES=$(stat -c%s assets/documentation.db 2>/dev/null || stat -f%z assets/documentation.db 2>/dev/null) | |
| FILE_SIZE_HUMAN=$(du -h assets/documentation.db | cut -f1) | |
| if [ "$FILE_SIZE_BYTES" -lt 1000000 ]; then | |
| echo "ERROR: Downloaded file is too small ($FILE_SIZE_HUMAN)" | |
| echo "This usually means the file was not found or service account lacks access" | |
| exit 1 | |
| fi | |
| echo "Successfully downloaded documentation.db ($FILE_SIZE_HUMAN)" | |
| - name: Verify staged ingredients | |
| run: | | |
| missing=0 | |
| for f in localMvnRepository.zip documentation.db core.cgt \ | |
| android-sdk-arm64-v8a.zip android-sdk-armeabi-v7a.zip \ | |
| bootstrap-arm64-v8a.zip bootstrap-armeabi-v7a.zip; do | |
| if [ ! -s "assets/$f" ]; then | |
| echo "ERROR: missing or empty assets/$f" | |
| missing=1 | |
| fi | |
| done | |
| if ! ls assets/gradle-*-bin.zip >/dev/null 2>&1; then | |
| echo "ERROR: no assets/gradle-*-bin.zip staged" | |
| missing=1 | |
| fi | |
| if ! ls assets/gradle-api-*.jar.zip >/dev/null 2>&1; then | |
| echo "ERROR: no assets/gradle-api-*.jar.zip staged" | |
| missing=1 | |
| fi | |
| [ "$missing" -eq 0 ] || exit 1 | |
| command -v sqlite3 >/dev/null || { sudo apt-get update -qq && sudo apt-get install -y -qq sqlite3; } | |
| sqlite3 assets/documentation.db \ | |
| "SELECT 1 FROM pragma_table_info('Content') WHERE name='templateId';" | grep -q 1 \ | |
| || { echo "ERROR: documentation.db predates the templateId column; docs requests will 500"; exit 1; } | |
| echo "All ingredients staged" | |
| - name: Upload staged assets | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: debug-assets | |
| path: assets/ | |
| retention-days: 1 | |
| if-no-files-found: error | |
| - name: Cleanup google-services.json | |
| if: always() | |
| run: | | |
| rm -f app/google-services.json | |
| echo "google-services.json cleaned up successfully" | |
| - name: Cleanup ssh | |
| if: always() && inputs.asset_source == 'site' | |
| run: | | |
| # Remove SSH key | |
| rm -f ~/.ssh/id_rsa | |
| # Clean up SSH known_hosts (remove the entry for this host) | |
| if [ -n "$SCP_HOST" ]; then | |
| ssh-keygen -R "$SCP_HOST" 2>/dev/null || true | |
| fi | |
| # Remove entire .ssh directory if empty | |
| rmdir ~/.ssh 2>/dev/null || true | |
| zip: | |
| name: Zip assets | |
| runs-on: self-hosted | |
| timeout-minutes: 60 | |
| needs: prepare | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - abi: v8 | |
| arch: arm64-v8a | |
| label: 64-bit ARM | |
| - abi: v7 | |
| arch: armeabi-v7a | |
| label: 32-bit ARM | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ inputs.ref }} | |
| - name: Remove stale staged assets | |
| run: | | |
| rm -f assets/*.zip assets/documentation.db assets/core.cgt | |
| - name: Check if Nix is installed | |
| id: check_nix | |
| run: | | |
| if command -v nix >/dev/null 2>&1; then | |
| echo "nix is installed" | |
| echo "nix_installed=true" >> $GITHUB_ENV | |
| else | |
| echo "nix is not installed" | |
| echo "nix_installed=false" >> $GITHUB_ENV | |
| fi | |
| - name: Install Flox | |
| if: env.nix_installed == 'false' | |
| uses: flox/install-flox-action@v2 | |
| - name: Create google-services.json | |
| env: | |
| GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} | |
| run: | | |
| echo "$GOOGLE_SERVICES_JSON" > app/google-services.json | |
| - name: Download staged assets | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: debug-assets | |
| path: assets | |
| - name: Assemble assets zip | |
| run: | | |
| variant_upper=$(echo "${{ matrix.abi }}" | tr '[:lower:]' '[:upper:]') | |
| flox activate -d flox/base -- ./gradlew :app:assemble${variant_upper}Assets --no-daemon \ | |
| -Dorg.gradle.jvmargs="-Xmx6g -XX:MaxMetaspaceSize=2g -XX:+HeapDumpOnOutOfMemoryError --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED" \ | |
| -Dandroid.aapt2.daemonHeapSize=4096M \ | |
| -Dorg.gradle.workers.max=1 \ | |
| -Dorg.gradle.parallel=false | |
| - name: Verify assets zip | |
| id: assets_zip | |
| run: | | |
| zip_path="app/build/outputs/assets/assets-${{ matrix.arch }}.zip" | |
| if [ ! -s "$zip_path" ]; then | |
| echo "ERROR: $zip_path was not produced" | |
| exit 1 | |
| fi | |
| unzip -tq "$zip_path" | |
| for entry in android-sdk.zip bootstrap.zip localMvnRepository.zip documentation.db \ | |
| core.cgt plugin-artifacts.zip plugin-maven-repo.zip; do | |
| unzip -l "$zip_path" | grep -qE "[[:space:]]$entry$" \ | |
| || { echo "ERROR: $zip_path is missing entry $entry"; exit 1; } | |
| done | |
| echo "ASSETS_PATH=$zip_path" >> $GITHUB_OUTPUT | |
| echo "$(du -h "$zip_path" | cut -f1) $zip_path" | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v6 | |
| - name: Upload assets zip to Cloudflare R2 | |
| id: r2 | |
| env: | |
| CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }} | |
| CLOUDFLARE_KEY_ID: ${{ vars.CLOUDFLARE_KEY_ID }} | |
| CLOUDFLARE_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_SECRET_ACCESS_KEY }} | |
| R2_BUCKET: ${{ vars.R2_ASSETS_BUCKET || 'apk-repo' }} | |
| R2_KEY_PREFIX: assets/ | |
| run: | | |
| uv run --with boto3 scripts/cloudflare-r2-upload.py "${{ steps.assets_zip.outputs.ASSETS_PATH }}" | |
| echo "DOWNLOAD_URL=https://download.appdevforall.org/assets/assets-${{ matrix.arch }}.zip" >> $GITHUB_OUTPUT | |
| - name: Send Slack notification | |
| env: | |
| SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} | |
| ASSET_LABEL: ${{ matrix.label }} | |
| DOWNLOAD_URL: ${{ steps.r2.outputs.DOWNLOAD_URL }} | |
| BUILD_REF: ${{ inputs.ref }} | |
| run: | | |
| jq -n \ | |
| --arg label "$ASSET_LABEL" \ | |
| --arg url "$DOWNLOAD_URL" \ | |
| --arg ref "$BUILD_REF" \ | |
| '{ | |
| blocks: [ | |
| { | |
| type: "header", | |
| text: { | |
| type: "plain_text", | |
| text: ":rocket: [Updated] New Assets Zip Available", | |
| emoji: true | |
| } | |
| }, | |
| { | |
| type: "section", | |
| text: { | |
| type: "mrkdwn", | |
| text: "*\($label):* <\($url)|Download assets zip>" | |
| } | |
| }, | |
| { | |
| type: "context", | |
| elements: [ | |
| { | |
| type: "mrkdwn", | |
| text: "Built from `\($ref)`. Push to `/sdcard/Download/` on the device, then `adb shell touch` it." | |
| } | |
| ] | |
| } | |
| ] | |
| }' > payload.json | |
| curl -X POST -H "Content-type: application/json" --data @payload.json "$SLACK_WEBHOOK" | |
| rm -f payload.json | |
| - name: Clean up outputs | |
| if: always() | |
| run: | | |
| rm -f app/google-services.json | |
| rm -rf app/build/outputs/assets/ |