diff --git a/.github/workflows/android-webview.yaml b/.github/workflows/android-webview.yaml new file mode 100644 index 000000000000..ff10cdece271 --- /dev/null +++ b/.github/workflows/android-webview.yaml @@ -0,0 +1,65 @@ +name: Build Android WebView APK + +on: + workflow_dispatch: + pull_request: + paths: + - "ci/android/**" + - ".github/workflows/android-webview.yaml" + +permissions: + contents: read + +jobs: + build-apk: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ci/android + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v6 + with: + node-version-file: .node-version + # TODO: pin these two actions to commit SHAs like the rest of this repo. + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "17" + + # The Android SDK is preinstalled on ubuntu-latest runners. + - name: Set Android SDK env + run: | + echo "ANDROID_SDK_ROOT=$ANDROID_HOME" >> "$GITHUB_ENV" + + - name: Install dependencies + run: npm install + + - name: Add Android platform + run: npx cap add android + + - name: Sync Capacitor + run: npx cap sync android + + # Cleartext traffic is needed because code-server is served over plain + # HTTP on localhost. Capacitor may already set this from + # `server.cleartext`; only patch the manifest if it did not. + - name: Ensure cleartext traffic and INTERNET permission + run: | + manifest=android/app/src/main/AndroidManifest.xml + grep -q 'usesCleartextTraffic' "$manifest" || \ + sed -i 's/# \n#' "$manifest" + grep -n 'usesCleartextTraffic\|INTERNET' "$manifest" + + - name: Build debug APK + run: | + chmod +x android/gradlew + cd android + ./gradlew assembleDebug + + - uses: actions/upload-artifact@v4 # TODO: pin to a commit SHA + with: + name: code-server-android-webview-debug + path: ci/android/android/app/build/outputs/apk/debug/app-debug.apk diff --git a/ci/android/.gitignore b/ci/android/.gitignore new file mode 100644 index 000000000000..f214a4e22d01 --- /dev/null +++ b/ci/android/.gitignore @@ -0,0 +1,3 @@ +node_modules +# Generated by `npx cap add android`. +android diff --git a/ci/android/README.md b/ci/android/README.md new file mode 100644 index 000000000000..9f58ea113fd5 --- /dev/null +++ b/ci/android/README.md @@ -0,0 +1,28 @@ +# Android WebView wrapper + +A minimal [Capacitor](https://capacitorjs.com) app that opens a code-server +instance running on the same device (for example in +[Termux](../../docs/termux.md)) inside a WebView. + +It does **not** bundle Node.js or code-server, so none of the Node.js Mobile +API limitations apply. code-server runs as a normal process in Termux and +terminals, language servers, and sockets behave as they do on any Linux system. + +See [docs/android-webview.md](../../docs/android-webview.md) for usage. + +## Build locally + +Requires Node.js, JDK 17, and the Android SDK. + +```shell +cd ci/android +npm install +npx cap add android +npx cap sync android +cd android +./gradlew assembleDebug +``` + +The APK is written to `android/app/build/outputs/apk/debug/app-debug.apk`. + +The same steps run in the `Build Android WebView APK` GitHub workflow. diff --git a/ci/android/capacitor.config.ts b/ci/android/capacitor.config.ts new file mode 100644 index 000000000000..3fa3b4cded24 --- /dev/null +++ b/ci/android/capacitor.config.ts @@ -0,0 +1,19 @@ +import { CapacitorConfig } from "@capacitor/cli" + +// This app is a thin WebView wrapper. It does not bundle code-server or Node. +// It loads a code-server instance that is already running on the device +// (for example in Termux) at http://127.0.0.1:8080. +const config: CapacitorConfig = { + appId: "com.coder.codeserver", + appName: "code-server", + webDir: "www", + server: { + url: "http://127.0.0.1:8080", + cleartext: true, + }, + android: { + allowMixedContent: true, + }, +} + +export default config diff --git a/ci/android/package.json b/ci/android/package.json new file mode 100644 index 000000000000..f58423ef63cd --- /dev/null +++ b/ci/android/package.json @@ -0,0 +1,19 @@ +{ + "name": "code-server-android-webview", + "private": true, + "version": "0.0.0", + "license": "MIT", + "description": "Android WebView wrapper that connects to a code-server instance running on the device", + "scripts": { + "sync": "cap sync android", + "open": "cap open android" + }, + "dependencies": { + "@capacitor/android": "^6.0.0", + "@capacitor/core": "^6.0.0" + }, + "devDependencies": { + "@capacitor/cli": "^6.0.0", + "typescript": "^5.6.2" + } +} diff --git a/ci/android/www/index.html b/ci/android/www/index.html new file mode 100644 index 000000000000..44a5b9857cd6 --- /dev/null +++ b/ci/android/www/index.html @@ -0,0 +1,16 @@ + + + + + + code-server + + + +

Loading code-server...

+ + diff --git a/docs/android-webview.md b/docs/android-webview.md new file mode 100644 index 000000000000..4f321eea900b --- /dev/null +++ b/docs/android-webview.md @@ -0,0 +1,27 @@ +# Android WebView app + +A small Android app that opens a locally running code-server in a WebView, so +you get a full-screen app instead of a browser tab. + +The app does **not** bundle code-server or Node.js (it does not use Node.js +Mobile). You run code-server on the device yourself, normally in +[Termux](./termux.md), where terminals, language servers, and sockets work as +on any Linux system. The app only displays `http://127.0.0.1:8080`. + +## Usage + +1. Install code-server in Termux by following the [Termux guide](./termux.md). + code-server requires Node.js 24; check with `node -v`. +2. Set a password in `~/.config/code-server/config.yaml` and keep + `auth: password`. Any app on the device can reach `127.0.0.1:8080`, so do not + disable authentication. +3. Start code-server in Termux: `code-server`. +4. Open the app. It loads `http://127.0.0.1:8080`. + +If you change the port in `bind-addr`, rebuild the app with the matching +`server.url` in [`ci/android/capacitor.config.ts`](../ci/android/capacitor.config.ts). + +## Building + +See [`ci/android/README.md`](../ci/android/README.md). The `Build Android +WebView APK` workflow builds a debug APK and uploads it as an artifact.