diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 7aea630..c149b5c 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -34,8 +34,11 @@ jobs: - 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 ci + run: npm i - name: Install Capacitor dependencies run: npm --prefix build/android install @@ -51,40 +54,125 @@ jobs: - name: Ensure nodejs-mobile gradle variable is defined # @capawesome/capacitor-nodejs expects $nodejsMobileVersion to be set. + # Append on its own line (variables.gradle has no trailing newline, so a + # plain `echo >>` would concatenate onto the closing `}` and break Gradle). run: | cd build/android/android grep -q "nodejsMobileVersion" variables.gradle || \ - echo "nodejsMobileVersion = '18.20.4-capawesome.1'" >> variables.gradle + printf '\next.nodejsMobileVersion = "18.20.4-capawesome.1"\n' >> variables.gradle - name: Apply icon / splash overlay - run: cp -r build/android/res-overlay/. build/android/android/app/src/main/res/ + run: | + # AndroidManifest.xml must go at app/src/main/, NOT inside res/. + # Copy the electerm manifest (with icon, theme, network config) to + # the correct location, overwriting Capacitor's default. + 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 + + # Copy all resource dirs (drawable, mipmap-*, values, xml) into res/. + # Using overlay/. merges directory contents into the destination + # without creating extra nesting levels. + cp -r build/android/res-overlay/. build/android/android/app/src/main/res/ + + # Remove Capacitor's default resources that conflict with / shadow + # the electerm overlay. These are generated by `cap add android` and + # must be deleted AFTER the overlay copy: + # drawable-v24/ic_launcher_foreground.xml — vector foreground that + # takes precedence over our drawable/ic_launcher_foreground.png + # on API 24+, showing the generic Capacitor logo instead of electerm. + # values/ic_launcher_background.xml — same color name as our + # colors-electerm.xml → duplicate-resource build error. + # drawable/splash.png — same resource name as our splash.xml. + # mipmap-*/ic_launcher_round.png + mipmap-anydpi-v26/ic_launcher_round.xml + # — Capacitor's default round icons; remove so only electerm icons remain. + 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" + rm -f "$RES/drawable/splash.png" + rm -f "$RES"/mipmap-*/ic_launcher_round.png + rm -f "$RES/mipmap-anydpi-v26/ic_launcher_round.xml" + + - name: Configure per-ABI APK splits + # Produce one APK per architecture (arm64-v8a, armeabi-v7a, x86_64) + # plus a universal build, instead of a single ~160MB universal blob. + # The second `android { }` block is cumulative with Capacitor's + # template app/build.gradle; output renaming is done post-build in + # shell to avoid AGP-version-specific output-name APIs. + 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 ephemeral keystore) + - name: Build release APK (signed with persistent keystore) env: - KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD || 'android' }} - KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD || 'android' }} + 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 - keytool -genkey -v \ - -keystore release.keystore -alias electerm \ - -keyalg RSA -keysize 2048 -validity 10000 \ - -storepass "$KEYSTORE_PASSWORD" -keypass "$KEY_PASSWORD" \ - -dname "CN=electerm" + 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=release.keystore \ - -Pandroid.injected.signing.store.password="$KEYSTORE_PASSWORD" \ - -Pandroid.injected.signing.key.alias=electerm \ - -Pandroid.injected.signing.key.password="$KEY_PASSWORD" + -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 + # Version comes from package.json. Each per-ABI APK is renamed to + # electerm-android-{arch}-{version}.apk (release) / + # electerm-android-{arch}-{version}-debug.apk (debug). The {arch} is + # parsed from gradle's per-ABI output filename; falls back to + # "universal" for the universal build (or if splits are off). + run: | + VERSION=$(node -p "require('./package.json').version") + 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 uses: actions/upload-artifact@v4 with: name: electerm-android-apks - path: | - build/android/android/app/build/outputs/apk/debug/*.apk - build/android/android/app/build/outputs/apk/release/*.apk + path: build/android/android/app/build/outputs/apk/named/*.apk if-no-files-found: error retention-days: 30 diff --git a/build/android/build.mjs b/build/android/build.mjs index 2c57576..f35fd5d 100644 --- a/build/android/build.mjs +++ b/build/android/build.mjs @@ -121,17 +121,46 @@ function writeLoadingPage () {