Developer kit · GitHub Actions

app-builder

A small, reusable build kit that turns a static web app into two double-clickable, fully offline deliverables — a single-file portable Windows EXE and an Android APK.

It is a kit, not an app. App repos consume it through one workflow file; the kit builds the artefacts and publishes them to a GitHub Release.

Python PyInstaller Kotlin Gradle · AGP 8.x WebView offline by design
What it is

The build kit behind the EXE and APK releases

app-builder is the shared kit that produces the portable Windows EXE and Android APK releases of drsamonline app repos. An app repo drops in one workflow file, points it at the folder holding its index.html, and every push to main yields two fresh artifacts attached to a GitHub Release.

Static in, native out

The app itself stays a single self-contained HTML file. The kit wraps that file in a native shell — it never becomes a new codebase.

No backend, no network

Both deliverables run entirely offline. The EXE serves the app from a loopback port; the APK loads it from bundled assets.

One reusable workflow

Consuming repos check out drsamonline/app-builder in their workflow. Improvements to the kit reach every app at once.

The two artefacts

One HTML file → two offline deliverables

Each job is independent, so a failure in one build does not block the other.

Portable Windows EXE

Built with PyInstaller --onefile from exe/build.py. It bundles a tiny Python launcher that serves the app folder with http.server on a free 127.0.0.1 port, opens the default browser, and stays running until the console window is closed.

build
PyInstaller, single file
serves
bundled app/ on loopback
runtime
no install, fully offline
<APP_NAME>-portable.exe

Android APK

Built from a deliberately minimal, dependency-free WebView Gradle project — the platform framework only, no AndroidX. A single full-screen WebView loads file:///android_asset/www/index.html; the hardware back button navigates history first.

sdk
compileSdk 34 · minSdk 24 · targetSdk 34
plugin
AGP 8.x
signing
debug key by default (see below)
<APP_NAME>.apk
Signing key

Read this before you ship an APK

Sideload-grade — not Play-Store signing

The kit ships keystore/debug.keystore, a debug-grade key (alias androiddebugkey, store & key password android, CN=Android Debug, ~10 000-day validity).

  • Its only job is so successive builds can be installed over each other while sideloading and iterating.
  • It is not a Play-Store signing key. Do not publish to Google Play with it.
  • It is public in the repository, so it provides no security whatsoever.

To sign with your own key, add repository secrets — KEYSTORE_FILE, KEYSTORE_PASSWORD, KEY_ALIAS, KEY_PASSWORD. app/build.gradle.kts reads those environment variables and falls back to the committed debug key when they are unset.

Stable download URLs

Permanent links that track the latest build

The release job always uses the fixed tag app-latest and recreates it on every run, so /releases/latest always resolves to the newest build. Point a landing page at these URLs and the download never goes stale.

asset URLs
https://github.com/drsamonline/<repo>/releases/latest/download/<repo>-portable.exe
https://github.com/drsamonline/<repo>/releases/latest/download/<repo>.apk

The asset name follows the repo's APP_NAME, so <repo> is that same value.

How a repo uses it

One file, two inputs

Copy examples/build-apps.yml into your app repo as .github/workflows/build-apps.yml and set the two values at the top to match your layout.

.github/workflows/build-apps.yml
env:
  APP_NAME: my-app        # -> my-app-portable.exe and my-app.apk
  APP_SRC: docs/play      # folder holding index.html (+ any assets)
  1. exe job — windows-latest.

    Checks out your repo and this kit, copies APP_SRC into exe/app/, runs python exe/build.py, and uploads <APP_NAME>-portable.exe.

  2. apk job — ubuntu-latest.

    Installs the Android SDK packages, copies APP_SRC into apk/app/src/main/assets/www/, and runs gradle assembleRelease to produce a signed <APP_NAME>.apk.

  3. release job.

    Downloads both artifacts and publishes them on a single fixed-tag release (app-latest), deleting any previous one first so /releases/latest resolves to the newest build.