bsc

Comprehensive codebase and cou...
Log | Files | Refs | Activity | README | LICENSE

commit 3c26f05b279530e13cad8a590f088571bfc9fd90
parent 3d0d4c5f6fe5db2697484ff3f6eaf62e0d4e7eca
Author: Amit Dutta <mail@amit.is-a.dev>
Date:   Fri, 28 Aug 2026 13:07:32 +0530

Merge pull request #98 from notamitgamer/feat/offline-apk-build

Add separate, opt-in workflow to build an offline Android APK
Diffstat:
A.github/workflows/build-apk.yml | 195+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.github/workflows/update-apk-content.yml | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 279 insertions(+), 0 deletions(-)

diff --git a/.github/workflows/build-apk.yml b/.github/workflows/build-apk.yml @@ -0,0 +1,195 @@ +name: Build Offline APK + +# Fully separate from main.yml on purpose: if anything here misbehaves, +# delete this file and the site build/deploy is completely unaffected. +# +# Fully separate from main.yml on purpose: if anything here misbehaves, +# delete this file and the site build/deploy is completely unaffected. +# +# This builds the APK SHELL only (the installable app itself) — the +# expensive part (Android SDK + Gradle). Manual trigger only: the +# shell rarely if ever needs to change once the app is installed — +# @capgo/capacitor-updater (baked into the shell) handles all content +# updates on its own via update-apk-content.yml, which runs cheaply +# on every push without touching Gradle at all. Run this only when +# something about the native shell itself actually changes (app icon, +# plugin versions, native config, etc.). +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: Build VitePress site + run: npm run docs:build + + # ubuntu-latest ships with Android SDK + a JDK preinstalled, so no + # extra SDK download step is needed here. + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + + - 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 + + 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 + + # Repo secrets required, added once by hand under + # Settings > Secrets and variables > Actions: + # ANDROID_KEYSTORE_BASE64 base64 of the .keystore file + # ANDROID_KEYSTORE_PASSWORD + # ANDROID_KEY_ALIAS + # ANDROID_KEY_PASSWORD + # Same keystore must be reused for every future build — Android + # rejects updates signed with a different key than the installed app. + - 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 update-apk-content.yml, + independently of this shell build. + files: | + ${{ steps.apk.outputs.path }} + prerelease: true diff --git a/.github/workflows/update-apk-content.yml b/.github/workflows/update-apk-content.yml @@ -0,0 +1,84 @@ +name: Update APK Content + +# The cheap counterpart to build-apk.yml. Runs on every push to main — +# no Android SDK, no Gradle, just the normal site build plus a zip and +# a small commit. This is what makes per-push updates affordable: the +# installed app's shell never rebuilds, it just downloads this new +# content bundle next time it's online and notices the version changed. +# +# paths-ignore excludes apk-manifest/manifest.json since this workflow +# writes that file itself — without the exclusion, every run would +# trigger another run in an infinite loop. +on: + push: + branches: + - main + paths-ignore: + - 'CHANGELOG.md' + - 'apk-manifest/manifest.json' + workflow_dispatch: + +permissions: + contents: write + +jobs: + update-content: + runs-on: ubuntu-latest + steps: + - 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: Build VitePress site + run: npm run docs:build + + - name: Package content bundle + id: bundle + run: | + cd docs/.vitepress/dist + zip -qr ../../../bundle.zip . + cd ../../.. + echo "checksum=$(sha256sum bundle.zip | cut -d' ' -f1)" >> "$GITHUB_OUTPUT" + echo "version=${{ github.run_number }}" >> "$GITHUB_OUTPUT" + + # Reuses the same 'apk-content' release across runs — each push + # just replaces the bundle.zip asset in place, it doesn't create + # a new release every time. + - name: Publish content bundle to release + uses: softprops/action-gh-release@v2 + with: + tag_name: apk-content + name: APK content bundle (latest) + body: | + Latest site content bundle for the offline app's auto-updater. + Not a standalone release — this backs apk-manifest/manifest.json. + files: bundle.zip + prerelease: true + + - name: Commit updated manifest to repo + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + mkdir -p apk-manifest + cat > apk-manifest/manifest.json <<EOF + { + "version": "${{ steps.bundle.outputs.version }}", + "url": "https://github.com/${{ github.repository }}/releases/download/apk-content/bundle.zip", + "checksum": "${{ steps.bundle.outputs.checksum }}", + "built_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)" + } + EOF + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add apk-manifest/manifest.json + git diff --cached --quiet && echo "No manifest changes" || git commit -m "chore: update APK content manifest (run ${{ github.run_number }})" + git push origin HEAD:main
© notamitgamer • Site Built: 2026-09-05 01:53:16 UTC • git-mirror commit: c170d72 [view raw info]
Originally created with stagit • modified by notamitgamer
Forked from github.com/notamitgamer/git-mirror