Skip to content

FIx [r2]

FIx [r2] #4

Workflow file for this run

name: Dev Build Android APK
# Lightweight dev build: builds debug + release APKs, uploads them as
# GitHub Actions artifacts, and optionally mirrors release APKs to Cloudflare
# R2 when the commit message contains [r2].
#
# Unlike build-android.yml (triggered on `build` branch), this workflow:
# - triggers on `main` branch pushes + manual dispatch
# - NEVER publishes a GitHub Release
# - ALWAYS uploads to artifacts (not gated on [debug])
# - optionally uploads to R2 when [r2] is in the commit message
#
# The R2 upload is a separate job so it can use the `build` environment
# (where R2 secrets live) without gating the build step behind environment
# approvals.
on:
push:
branches: [ dev ]
workflow_dispatch:
# GitHub Actions already skips workflows when the commit message contains
# [skip ci], [ci skip], [no ci], [skip actions], or [actions skip].
# The job-level `if` below is an explicit, belt-and-suspenders guard.
jobs:
build:
name: Build electerm Android APK (dev)
runs-on: ubuntu-latest
if: ${{ !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[no ci]') && !contains(github.event.head_commit.message, '[skip actions]') && !contains(github.event.head_commit.message, '[actions skip]') }}
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
- name: Set up Java (JDK 21)
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
- name: Set up Android SDK
uses: android-actions/setup-android@v3
- name: Accept Android SDK licenses
run: yes | sdkmanager --licenses || true
- name: Install required Android SDK packages
run: sdkmanager "platforms;android-36" "build-tools;36.0.0"
- name: ci init
run: npm config set legacy-peer-deps true
- name: Install root dependencies (electerm + build tooling)
run: npm i
- name: Install Capacitor dependencies
run: npm --prefix build/android install
- name: Build web frontend + Node.js backend bundle
env:
SERVER_SECRET: ${{ secrets.SERVER_SECRET }}
run: npm run build:android
- name: Add Android platform (first run only)
run: cd build/android && [ -d android ] || npx cap add android
- name: Sync Capacitor (copies web assets + native plugins)
run: cd build/android && npx cap sync android
- name: Ensure nodejs-mobile gradle variable is defined
run: |
cd build/android/android
grep -q "nodejsMobileVersion" variables.gradle || \
printf '\next.nodejsMobileVersion = "18.20.4-capawesome.1"\n' >> variables.gradle
- name: Apply icon / splash / MainActivity overlay
run: |
# AndroidManifest.xml must go at app/src/main/, NOT inside res/.
MANIFEST_SRC=build/android/res-overlay/AndroidManifest.xml
MANIFEST_DST=build/android/android/app/src/main/AndroidManifest.xml
if [ -f "$MANIFEST_SRC" ]; then
cp "$MANIFEST_SRC" "$MANIFEST_DST"
echo "Overlayed AndroidManifest.xml -> $MANIFEST_DST"
fi
# Custom MainActivity.java (edge-to-edge fix for Android 15+).
JAVA_SRC=build/android/res-overlay/java
JAVA_DST=build/android/android/app/src/main/java
if [ -d "$JAVA_SRC" ]; then
mkdir -p "$JAVA_DST"
cp -r "$JAVA_SRC/." "$JAVA_DST/"
echo "Overlayed MainActivity.java -> $JAVA_DST"
fi
# Copy all resource dirs (drawable, mipmap-*, values, xml) into res/.
cp -r build/android/res-overlay/. build/android/android/app/src/main/res/
# Remove the stray res/java/ created by the bulk cp above.
rm -rf build/android/android/app/src/main/res/java
# Remove Capacitor's default resources that conflict with the overlay.
RES=build/android/android/app/src/main/res
rm -f "$RES/drawable-v24/ic_launcher_foreground.xml"
rm -f "$RES/values/ic_launcher_background.xml"
find "$RES" -type f -name 'splash.png' -path '*/drawable*' -delete
- name: Configure per-ABI APK splits
run: |
GRADLE=build/android/android/app/build.gradle
grep -q 'splits' "$GRADLE" || printf '%s\n' \
'' \
'// electerm: split per ABI (injected by CI)' \
'android {' \
' splits {' \
' abi {' \
' enable true' \
' reset()' \
" include 'arm64-v8a', 'armeabi-v7a', 'x86_64'" \
' universalApk true' \
' }' \
' }' \
'}' >> "$GRADLE"
tail -n 15 "$GRADLE"
- name: Build debug APK
run: cd build/android/android && chmod +x gradlew && ./gradlew assembleDebug
- name: Build release APK (signed with persistent keystore)
env:
ANDROID_RELEASE_KEYSTORE_BASE64: ${{ secrets.ANDROID_RELEASE_KEYSTORE_BASE64 }}
ANDROID_RELEASE_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_RELEASE_KEYSTORE_PASSWORD }}
ANDROID_RELEASE_KEY_PASSWORD: ${{ secrets.ANDROID_RELEASE_KEY_PASSWORD }}
ANDROID_RELEASE_KEY_ALIAS: ${{ secrets.ANDROID_RELEASE_KEY_ALIAS }}
run: |
cd build/android/android
KEYSTORE="$PWD/app/release.keystore"
printf '%s' "$ANDROID_RELEASE_KEYSTORE_BASE64" | base64 --decode > "$KEYSTORE"
keytool -list -keystore "$KEYSTORE" -storepass "$ANDROID_RELEASE_KEYSTORE_PASSWORD"
./gradlew assembleRelease \
-Pandroid.injected.signing.store.file="$KEYSTORE" \
-Pandroid.injected.signing.store.password="$ANDROID_RELEASE_KEYSTORE_PASSWORD" \
-Pandroid.injected.signing.key.alias="$ANDROID_RELEASE_KEY_ALIAS" \
-Pandroid.injected.signing.key.password="$ANDROID_RELEASE_KEY_PASSWORD"
- name: Rename APKs to electerm-android-{arch}-{version}.apk
id: rename
run: |
VERSION=$(node -p "require('./package.json').version")
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
APKROOT=build/android/android/app/build/outputs/apk
mkdir -p "$APKROOT/named"
for variant in debug release; do
dir="$APKROOT/$variant"
[ -d "$dir" ] || continue
for apk in "$dir"/*.apk; do
[ -f "$apk" ] || continue
name=$(basename "$apk")
arch=$(printf '%s' "$name" | grep -oE 'arm64-v8a|armeabi-v7a|x86_64|universal' | head -1)
[ -z "$arch" ] && arch=universal
if [ "$variant" = release ]; then
out="electerm-android-$arch-$VERSION.apk"
else
out="electerm-android-$arch-$VERSION-debug.apk"
fi
cp "$apk" "$APKROOT/named/$out"
echo " $variant/$name -> named/$out"
done
done
echo "=== named APKs ==="
ls -la "$APKROOT/named/"
- name: Upload APKs (artifact)
uses: actions/upload-artifact@v4
with:
name: electerm-android-dev-apks
path: build/android/android/app/build/outputs/apk/named/*.apk
if-no-files-found: error
retention-days: 30
# -----------------------------------------------------------------------
# R2 upload — only runs when [r2] is in the commit message.
# Uses the `build` environment so R2 secrets (CF_R2_*) are available.
# Downloads the APKs from the build job's artifact, then uploads release
# APKs (skips debug) to the R2 bucket's temp/ prefix.
# -----------------------------------------------------------------------
upload-to-r2:
name: Upload release APKs to R2
runs-on: ubuntu-latest
needs: build
if: ${{ contains(github.event.head_commit.message, '[r2]') }}
environment: build
permissions:
contents: read
steps:
- name: Download APK artifact
uses: actions/download-artifact@v4
with:
name: electerm-android-dev-apks
path: ./apks
- name: List downloaded APKs
run: ls -la ./apks
- name: Configure AWS credentials for R2
run: |
mkdir -p ~/.aws
cat > ~/.aws/credentials << 'EOF'
[default]
aws_access_key_id = ${{ secrets.CF_R2_ACCESS_KEY_ID }}
aws_secret_access_key = ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
EOF
cat > ~/.aws/config << 'EOF'
[default]
region = auto
endpoint_url = ${{ secrets.CF_R2_ENDPOINT }}
EOF
- name: Upload release APKs to R2 (temp folder)
env:
CF_R2_BUCKET: ${{ secrets.CF_R2_BUCKET }}
run: |
for file in ./apks/*.apk; do
[ -f "$file" ] || continue
rel_path=$(basename "$file")
case "$rel_path" in
*-debug.apk)
echo "Skipping debug APK for R2 upload: $rel_path"
continue
;;
esac
aws s3 cp "$file" "s3://${CF_R2_BUCKET}/temp/${rel_path}" \
--acl public-read \
--cache-control "max-age=31536000"
echo "Uploaded: temp/${rel_path}"
done
- name: Verify uploads
env:
CF_R2_BUCKET: ${{ secrets.CF_R2_BUCKET }}
run: |
aws s3 ls "s3://${CF_R2_BUCKET}/temp/" --recursive