commit cf7ecdd6d6bca17ddcc7601a3ba9f49b53510d5f
parent 6486de644580e657082862b6ce9f627c63141626
Author: Amit Dutta <mail@amit.is-a.dev>
Date: Fri, 28 Aug 2026 20:42:46 +0530
Add workflow to build offline APK for BSc Code Index [skip ci]
This workflow builds an offline APK for the BSc Code Index site, including setup for Node, Python, and Java, as well as signing configurations.
Diffstat:
1 file changed, 196 insertions(+), 0 deletions(-)
diff --git a/.github/workflows/build-apk.yml b/.github/workflows/build-apk.yml
@@ -0,0 +1,196 @@
+name: Build Offline APK
+
+on:
+ workflow_dispatch:
+ inputs:
+ release_tag:
+ description: 'Release tag to attach the APK to (created if it does not exist)'
+ required: false
+ default: 'apk-latest'
+
+permissions:
+ contents: write
+
+jobs:
+ build-apk:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Resolve release tag
+ id: tag
+ run: |
+ echo "value=${{ inputs.release_tag || 'apk-latest' }}" >> "$GITHUB_OUTPUT"
+
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: 'npm'
+
+ - name: Install site deps
+ run: npm ci
+
+ - name: Setup Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.13'
+
+ - name: Generate markdown files
+ run: |
+ python list.py
+ python main.py
+
+ - name: Build VitePress site
+ run: npm run docs:build
+
+ - name: Set up JDK 21
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: '21'
+
+ - name: Scaffold Capacitor Android project
+ run: |
+ mkdir -p apk-build
+ cd apk-build
+ npm init -y
+ npm install @capacitor/core @capacitor/cli @capacitor/android @capgo/capacitor-updater @capacitor/assets
+
+ mkdir -p www
+ cp -r ../docs/.vitepress/dist/. www/
+
+ cat > capacitor.config.json <<'EOF'
+ {
+ "appId": "dev.amit.bsc",
+ "appName": "BSc Code Index",
+ "webDir": "www",
+ "bundledWebRuntime": false,
+ "plugins": {
+ "CapacitorUpdater": {
+ "autoUpdate": true,
+ "updateUrl": "https://raw.githubusercontent.com/notamitgamer/bsc/main/apk-manifest/manifest.json"
+ }
+ }
+ }
+ EOF
+
+ npx cap add android
+ npx cap sync android
+
+ - name: Generate Capacitor Assets (App Icon & Splash)
+ run: |
+ mkdir -p apk-build/assets
+ # Copy the 512x512 logo to the assets folder as icon and splash targets
+ cp docs/public/logo_512.png apk-build/assets/icon.png
+ cp docs/public/logo_512.png apk-build/assets/splash.png
+ cd apk-build
+ npx @capacitor/assets generate --android
+
+ - name: Bump Android compile SDK for okhttp-android
+ working-directory: apk-build/android
+ run: |
+ sed -Ei 's/(compileSdkVersion\s*=\s*)[0-9]+/\136/' variables.gradle
+ echo "android.suppressUnsupportedCompileSdk=36" >> gradle.properties
+
+ - name: Decode signing keystore
+ working-directory: apk-build/android/app
+ env:
+ ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
+ run: echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > release.keystore
+
+ - name: Add signing config to Gradle
+ working-directory: apk-build/android/app
+ env:
+ ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
+ ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
+ ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
+ run: |
+ cat > keystore.properties <<EOF
+ storeFile=release.keystore
+ storePassword=${ANDROID_KEYSTORE_PASSWORD}
+ keyAlias=${ANDROID_KEY_ALIAS}
+ keyPassword=${ANDROID_KEY_PASSWORD}
+ EOF
+
+ python3 - <<'PYEOF'
+ import re
+ path = "build.gradle"
+ with open(path) as f:
+ content = f.read()
+
+ props_load = (
+ "def keystoreProperties = new Properties()\n"
+ "def keystorePropertiesFile = rootProject.file('app/keystore.properties')\n"
+ "if (keystorePropertiesFile.exists()) {\n"
+ " keystoreProperties.load(new FileInputStream(keystorePropertiesFile))\n"
+ "}\n\n"
+ )
+ if "keystoreProperties" not in content:
+ content = props_load + content
+
+ signing_block = (
+ " signingConfigs {\n"
+ " release {\n"
+ " storeFile file(keystoreProperties['storeFile'])\n"
+ " storePassword keystoreProperties['storePassword']\n"
+ " keyAlias keystoreProperties['keyAlias']\n"
+ " keyPassword keystoreProperties['keyPassword']\n"
+ " }\n"
+ " }\n"
+ )
+ content = re.sub(
+ r"(android\s*\{)",
+ r"\1\n" + signing_block,
+ content,
+ count=1,
+ )
+
+ content = re.sub(
+ r"(buildTypes\s*\{\s*release\s*\{)",
+ r"\1\n signingConfig signingConfigs.release",
+ content,
+ count=1,
+ )
+
+ with open(path, "w") as f:
+ f.write(content)
+ PYEOF
+
+ - name: Build signed release APK
+ working-directory: apk-build/android
+ run: |
+ chmod +x ./gradlew
+ ./gradlew assembleRelease --no-daemon
+
+ - name: Locate APK
+ id: apk
+ run: |
+ APK_PATH=$(find apk-build/android -path "*release*" -name "*.apk" | head -n 1)
+ echo "path=$APK_PATH" >> "$GITHUB_OUTPUT"
+
+ - name: Upload APK as workflow artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: bsc-offline-apk
+ path: ${{ steps.apk.outputs.path }}
+
+ - name: Publish to GitHub Release
+ id: release
+ uses: softprops/action-gh-release@v2
+ with:
+ tag_name: ${{ steps.tag.outputs.value }}
+ name: Offline APK build (${{ github.run_number }})
+ body: |
+ Signed offline Android APK of the BSc code index site.
+
+ - Release-signed with the repo's dedicated keystore.
+ - Works fully offline after install.
+ - Auto-pulls newer content when online, via the manifest at
+ apk-manifest/manifest.json (raw.githubusercontent.com) —
+ kept up to date on every push by main.yml's apk-content job,
+ independently of this shell build.
+ files: |
+ ${{ steps.apk.outputs.path }}
+ prerelease: true