{"id":46886793,"url":"https://github.com/niels-emmer/pwa-maker-android","last_synced_at":"2026-03-10T22:17:53.041Z","repository":{"id":340787615,"uuid":"1167590531","full_name":"niels-emmer/pwa-maker-android","owner":"niels-emmer","description":"Self-hosted web app that wraps any PWA in a Trusted Web Activity (TWA) and generates a signed Android APK for sideloading — no Android Studio required.","archived":false,"fork":false,"pushed_at":"2026-02-26T18:31:30.000Z","size":345,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-26T19:58:31.432Z","etag":null,"topics":["claude-code","sonnet-4-6","vibecoding"],"latest_commit_sha":null,"homepage":"https://github.com/niels-emmer/pwa-maker-android","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/niels-emmer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-26T13:15:41.000Z","updated_at":"2026-02-26T18:31:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/niels-emmer/pwa-maker-android","commit_stats":null,"previous_names":["niels-emmer/pwa-maker-android"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/niels-emmer/pwa-maker-android","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niels-emmer%2Fpwa-maker-android","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niels-emmer%2Fpwa-maker-android/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niels-emmer%2Fpwa-maker-android/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niels-emmer%2Fpwa-maker-android/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/niels-emmer","download_url":"https://codeload.github.com/niels-emmer/pwa-maker-android/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niels-emmer%2Fpwa-maker-android/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30357688,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-10T21:41:54.280Z","status":"ssl_error","status_checked_at":"2026-03-10T21:40:59.357Z","response_time":106,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["claude-code","sonnet-4-6","vibecoding"],"created_at":"2026-03-10T22:17:52.225Z","updated_at":"2026-03-10T22:17:53.028Z","avatar_url":"https://github.com/niels-emmer.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003e [!CAUTION]\n\u003e **This entire project is the result of agentic coding.** It was built through prompts and iterative debugging sessions, with no human review of the code. It is to be taken as an experiment. Reasonable effort has been made to apply proper security architecture — SSRF protection, input validation, non-root containers, capability dropping, rate limiting — but the code has **not been audited by a human developer**. Run it on infrastructure you control, behind auth, at your own risk.\n\n# PWA Maker — Android APK Generator\n\n\u003e Turn any Progressive Web App into a signed Android APK you can sideload directly onto any Android device. No Android Studio, no Play Store, no fuss.\n\n**Live demo: [pwa.macjuu.com](https://pwa.macjuu.com)**\n\n## What it is\n\nA self-hosted web application that wraps any HTTPS PWA in a [Trusted Web Activity (TWA)](https://developer.chrome.com/docs/android/trusted-web-activity/) shell and produces a signed `.apk` file — ready to install directly on any Android phone.\n\nBuilt with the same stack as a typical vibecoded PWA (React + Vite frontend, Express backend), so it runs consistently on both your MacBook and VPS.\n\n---\n\n## Screenshots\n\n### Desktop\n\n![Desktop — full form](docs/screenshots/desktop-form.png)\n\n### Mobile\n\n![Mobile view](docs/screenshots/mobile-form.png)\n\n---\n\n## Features\n\n- Paste any HTTPS PWA URL → manifest fields auto-filled\n- Configurable: app name, short name, package ID, theme/background colour, display mode, orientation, icon\n- SVG icons auto-converted to 512×512 PNG (via [resvg-js](https://github.com/yisibl/resvg-js)) — no manual icon prep required\n- Server-side APK build via [Bubblewrap](https://github.com/GoogleChromeLabs/bubblewrap) + Android SDK 36\n- Live build log streamed via SSE while you wait (with keep-alive heartbeat)\n- Download a signed APK directly in browser\n- Dark theme, mobile-first UI\n- Bot prevention: HMAC-signed build tokens (server-enforced) + honeypot field (client-side)\n- Rate limiting, input validation, non-root container, no shell injection\n- Docker Compose — one command deploy\n- Production-hardened: nginx dynamic DNS, OOM-safe Gradle JVM cap, request logging\n\n---\n\n## Prerequisites\n\n- Docker + Docker Compose on your VPS\n- An SSL-terminating reverse proxy in front of the stack (Nginx Proxy Manager, Caddy, Traefik, etc.)\n- That's it\n\n---\n\n## Quick start\n\n### 1. Clone\n\n```bash\ngit clone https://github.com/niels-emmer/pwa-maker-android.git\ncd pwa-maker-android\n```\n\n### 2. Configure\n\n```bash\ncp .env.example .env\n# Edit .env if you want to change rate limits or TTL\n# All defaults are sane for a personal VPS\n```\n\n### 3. Build and start\n\n```bash\ndocker compose up -d --build\n```\n\n\u003e **First build takes 15–30 minutes** — the backend image installs JDK 17 + Android SDK (~1.5 GB). Subsequent builds use Docker layer cache and are fast.\n\n\u003e **Low-memory host?** The default Gradle JVM heap is capped at 512 MB, which is sufficient for a TWA build and leaves headroom on 4 GB machines. Override with `GRADLE_OPTS=-Xmx768m` in `.env` if you have more RAM to spare.\n\n### 4. Point your reverse proxy at the app port\n\nThe frontend container binds to `HOST_PORT` on the host (default **8088**).\nIf a different port is free, set `HOST_PORT=\u003cport\u003e` in `.env` before starting.\n\n```\n# .env\nHOST_PORT=8088        # change to any free port on your host\n```\n\nExample Nginx Proxy Manager / Caddy upstream target: `http://\u003cvps-ip\u003e:8088`\n\n### 5. Open the app\n\nNavigate to your domain. Paste a PWA URL, configure options, click **Generate APK**.\n\n---\n\n## Usage\n\n1. **Enter your PWA URL** — e.g. `https://my-app.example.com`\n   - The manifest is fetched automatically and all fields are pre-filled\n2. **Adjust options** — name, package ID, colours, orientation\n3. **Click Generate APK** — watch the build log stream in real time (first build ~2–5 min while Gradle downloads dependencies; cached builds ~30s)\n4. **Download** — click the Download APK button when the build completes\n5. **Install** — transfer the `.apk` to your Android device and open it (enable \"Install from unknown sources\" in Settings → Security)\n\n---\n\n## Configuration\n\nAll configuration is via environment variables in `.env`:\n\n| Variable | Default | Description |\n|---|---|---|\n| `HOST_PORT` | `8088` | Host port the frontend is exposed on |\n| `NODE_ENV` | `production` | Node environment |\n| `PORT` | `3001` | Backend listen port (internal) |\n| `ANDROID_HOME` | `/opt/android-sdk` | Android SDK path (set in Docker) |\n| `JAVA_HOME` | `/usr/lib/jvm/java-17-openjdk-amd64` | JDK path (set in Docker) |\n| `GRADLE_USER_HOME` | `/home/appuser/.gradle` | Gradle cache (mounted as volume) |\n| `GRADLE_OPTS` | `-Xmx512m -Xms128m` | Gradle JVM heap limits (safe for 4 GB hosts) |\n| `MAX_CONCURRENT_BUILDS` | `3` | Max simultaneous APK builds |\n| `BUILD_RATE_LIMIT_PER_HOUR` | `10` | Max builds per IP per hour |\n| `BUILD_TTL_HOURS` | `1` | Hours to keep built APK available |\n| `CORS_ORIGIN` | `*` | Allowed CORS origin |\n| `BUILD_TOKEN_SECRET` | *(random)* | HMAC secret for build tokens — set with `openssl rand -hex 32`; if unset a random secret is generated per restart |\n\n---\n\n## Docker internals\n\n```\npwa-maker-android/\n├── frontend/          React + Vite SPA → served by Nginx\n│   └── nginx.conf     Nginx config: serves SPA + proxies /api/* to backend\n├── backend/           Express + TypeScript + Android build toolchain\n│   └── Dockerfile     Node 20 + JDK 17 + Android SDK 36 (~1.5 GB image)\n└── docker-compose.yml\n```\n\nThe `gradle_cache` named volume persists between container restarts so Gradle dependencies (~200 MB) are only downloaded once.\n\nTo clear the Gradle cache:\n```bash\ndocker compose down -v\n```\n\n---\n\n## Development\n\n### Backend\n\n```bash\ncd backend\nnpm install\nnpm run dev        # tsx watch — hot reload\nnpm test           # vitest — 100 tests\n```\n\n### Frontend\n\n```bash\ncd frontend\nnpm install\nnpm run dev        # Vite dev server on :5200\nnpm test           # vitest + React Testing Library — 43 tests\n```\n\nThe frontend dev server proxies `/api/*` to `localhost:3001`.\n\n---\n\n## Authentication\n\nThis app has **no built-in auth**. Protect it at the reverse proxy level. See [SECURITY.md](SECURITY.md) for options and recommendations.\n\n---\n\n## Sideloading on Android\n\n1. Enable **Install unknown apps** for your file manager / browser:\n   - Settings → Apps → Special app access → Install unknown apps\n2. Transfer the `.apk` to your device (USB, email, cloud storage, local network)\n3. Tap the file to install\n\nThe installed app will appear on your home screen / app drawer like any other app.\n\n\u003e Each build generates a fresh signing key. If you reinstall a newer build of the same app, you must first uninstall the old version (Android enforces consistent signing for upgrades). This is acceptable for personal sideloaded use.\n\n---\n\n## Security\n\nSee [SECURITY.md](SECURITY.md) for the full security design, reporting policy, and hardening notes.\n\n---\n\n## Tech stack\n\n| Layer | Technology |\n|---|---|\n| Frontend | React 18, Vite, TypeScript, Tailwind CSS |\n| Backend | Node 20, Express, TypeScript |\n| APK generation | [@bubblewrap/core](https://github.com/GoogleChromeLabs/bubblewrap), Android SDK 36, JDK 17 |\n| Build tooling | Gradle (Android) |\n| Signing | `apksigner` (Android build tools) |\n| Progress delivery | Server-Sent Events (SSE) |\n| Tests | Vitest, React Testing Library |\n| Container | Docker, Nginx |\n\n---\n\n## Credits\n\n### [Bubblewrap](https://github.com/GoogleChromeLabs/bubblewrap) by Google Chrome Labs\n\nThe APK generation pipeline depends on `@bubblewrap/core` as an npm library. A single call to `TwaGenerator.createTwaProject()` generates the Android project structure, `build.gradle`, and `gradlew` inside a temp directory. None of the bubblewrap source code is copied into this repository — it is used strictly as a published npm package.\n\nEverything else in this project (Express server, manifest fetching, SSRF protection, rate limiting, SSE progress streaming, signing pipeline, React frontend, Docker setup) is original code.\n\n---\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fniels-emmer%2Fpwa-maker-android","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fniels-emmer%2Fpwa-maker-android","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fniels-emmer%2Fpwa-maker-android/lists"}