From 7694f00097f10a531e070c572bdb010e5ca02822 Mon Sep 17 00:00:00 2001 From: qioceky Date: Sat, 19 Sep 2026 13:02:52 +0000 Subject: [PATCH 01/11] Upgrade OpenCode to v1.18.3 - env.sh: bump OPENCODE_VERSION to 1.18.3, add OPENTUI_VERSION=0.4.3 - build.yml: default workflow_dispatch input + env to 1.18.3 - build-opencode-android.ts: new worker path src/cli/tui/worker.ts, conditions [bun,node], drop models-snapshot.js step (removed upstream) - build-opentui.sh: clone opentui at v${OPENTUI_VERSION} (match @opentui/core@0.4.3) - android-libc-link.patch: regenerate for opentui v0.4.3 build.zig - README: update version pins table Host bun 1.3.2 stays pinned; v1.18.3 bundles with the 36-byte module-graph stride. Bundle step verified locally against tag v1.18.3. --- .github/workflows/build.yml | 4 +- README.md | 3 +- patches/opentui/android-libc-link.patch | 8 +-- scripts/build-opencode-android.ts | 72 ++++++------------------- scripts/build-opentui.sh | 7 ++- scripts/env.sh | 3 +- 6 files changed, 32 insertions(+), 65 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 87323b7..d425a5f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,11 +9,11 @@ on: opencode_version: description: 'OpenCode version to build' required: false - default: '1.3.13' + default: '1.18.3' env: BUN_VERSION: "1.2.13" - OPENCODE_VERSION: "1.3.13" + OPENCODE_VERSION: "1.18.3" ANDROID_API: "24" ANDROID_NDK_VERSION: "28.1.13356709" WEBKIT_COMMIT: "017930ebf915121f8f593bef61cbbca82d78132d" diff --git a/README.md b/README.md index 00fa0a9..05564b7 100644 --- a/README.md +++ b/README.md @@ -339,7 +339,8 @@ The Bun team [closed Android support as "not planned"](https://github.com/oven-s | Android NDK | r28b (28.1.13356709) | Clang 19, stable | | Android API level | 24 (Android 7.0+) | Minimum for 64-bit Termux | | Zig (for opentui) | 0.15.2 | Latest stable, Android target support | -| OpenCode | 1.3.13 | Current release | +| opentui (native lib) | v0.4.3 | Matches `@opentui/core@0.4.3` bundled by OpenCode v1.18.3 | +| OpenCode | 1.18.3 | Current release | | TinyCC | `b91835d8` (oven-sh/tinycc) | Matches Bun v1.2.13's expected TinyCC | --- diff --git a/patches/opentui/android-libc-link.patch b/patches/opentui/android-libc-link.patch index 18f487c..2c56825 100644 --- a/patches/opentui/android-libc-link.patch +++ b/patches/opentui/android-libc-link.patch @@ -1,10 +1,10 @@ diff --git a/packages/core/src/zig/build.zig b/packages/core/src/zig/build.zig -index 5a8bcb8..b1d4642 100644 +index 0c82ba3..797ae17 100644 --- a/packages/core/src/zig/build.zig +++ b/packages/core/src/zig/build.zig -@@ -273,6 +273,29 @@ fn buildTarget( - .linkage = .dynamic, - }); +@@ -494,6 +494,29 @@ fn buildTarget( + addNativeAudioDependencies(b, lib, target, macos_sdk_path); + addYogaDependencies(b, lib); + // On Android, the .so must have NEEDED libc.so so dlopen can resolve + // symbols like getauxval. Zig doesn't bundle Android libc, so we directly diff --git a/scripts/build-opencode-android.ts b/scripts/build-opencode-android.ts index 1fe8d99..a55dd59 100644 --- a/scripts/build-opencode-android.ts +++ b/scripts/build-opencode-android.ts @@ -3,11 +3,10 @@ * Build OpenCode for Android (aarch64) * * This script: - * 1. Generates the models-snapshot.js - * 2. Loads migrations - * 3. Bundles OpenCode using Bun.build() with compile targeting the host platform - * 4. Extracts the module graph from the compiled binary - * 5. Appends it to our Android bun binary to create the final standalone + * 1. Loads migrations + * 2. Bundles OpenCode using Bun.build() with compile targeting the host platform + * 3. Extracts the module graph from the compiled binary + * 4. Appends it to our Android bun binary to create the final standalone */ import { $ } from "bun" @@ -28,50 +27,13 @@ if (!fs.existsSync(ANDROID_BUN)) { process.chdir(OPENCODE_DIR) -const VERSION = process.env.OPENCODE_VERSION || "1.3.13" +const VERSION = process.env.OPENCODE_VERSION || "1.18.3" const CHANNEL = process.env.OPENCODE_CHANNEL || "latest" console.log(`Building OpenCode v${VERSION} (channel: ${CHANNEL}) for Android aarch64`) -// Step 1: Generate models-snapshot.js -console.log("\n=== Step 1: Generating models-snapshot.js ===") -const modelsUrl = process.env.OPENCODE_MODELS_URL || "https://models.dev" -let modelsData: string = "" -if (process.env.MODELS_DEV_API_JSON) { - modelsData = await Bun.file(process.env.MODELS_DEV_API_JSON).text() -} else { - console.log(`Fetching from ${modelsUrl}/api.json ...`) - let fetchErr: Error | null = null - for (let attempt = 1; attempt <= 3; attempt++) { - try { - const resp = await fetch(`${modelsUrl}/api.json`, { signal: AbortSignal.timeout(15000) }) - if (!resp.ok) throw new Error(`HTTP ${resp.status}`) - modelsData = await resp.text() - fetchErr = null - break - } catch (err: any) { - fetchErr = err - console.error(` Attempt ${attempt}/3 failed: ${err.message}`) - if (attempt < 3) await new Promise(r => setTimeout(r, 2000 * attempt)) - } - } - if (fetchErr) { - console.error(`ERROR: Failed to fetch models after 3 attempts: ${fetchErr.message}`) - process.exit(1) - } -} -await Bun.write( - path.join(OPENCODE_DIR, "src/provider/models-snapshot.js"), - `// @ts-nocheck\n// Auto-generated by build.ts - do not edit\nexport const snapshot = ${modelsData}\n`, -) -await Bun.write( - path.join(OPENCODE_DIR, "src/provider/models-snapshot.d.ts"), - `// Auto-generated by build.ts - do not edit\nexport declare const snapshot: Record\n`, -) -console.log("Generated models-snapshot.js") - -// Step 2: Load migrations -console.log("\n=== Step 2: Loading migrations ===") +// Step 1: Load migrations +console.log("\n=== Step 1: Loading migrations ===") const migrationDirs = ( await fs.promises.readdir(path.join(OPENCODE_DIR, "migration"), { withFileTypes: true, @@ -101,9 +63,9 @@ const migrations = await Promise.all( ) console.log(`Loaded ${migrations.length} migrations`) -// Step 3: Build with Bun.build() --compile for the HOST platform +// Step 2: Build with Bun.build() --compile for the HOST platform // This creates a standalone binary for the host, from which we extract the module graph -console.log("\n=== Step 3: Bundling OpenCode ===") +console.log("\n=== Step 2: Bundling OpenCode ===") const plugin = createSolidTransformPlugin() @@ -119,7 +81,7 @@ try { } console.log(`Parser worker: ${parserWorkerResolved}`) -const workerPath = "./src/cli/cmd/tui/worker.ts" +const workerPath = "./src/cli/tui/worker.ts" const bunfsRoot = "/$bunfs/root/" const workerRelativePath = path.relative(OPENCODE_DIR, parserWorkerResolved).replaceAll("\\", "/") @@ -133,7 +95,7 @@ const hostBinaryPath = path.join(OUTPUT_DIR, "opencode-host") console.log("Building standalone binary for host platform...") const result = await Bun.build({ - conditions: ["browser"], + conditions: ["bun", "node"], tsconfig: "./tsconfig.json", plugins: [plugin], compile: { @@ -165,8 +127,8 @@ if (!result.success) { console.log(`Host standalone binary: ${hostBinaryPath}`) -// Step 4: Extract module graph from host binary -console.log("\n=== Step 4: Extracting module graph ===") +// Step 3: Extract module graph from host binary +console.log("\n=== Step 3: Extracting module graph ===") const hostBinary = await Bun.file(hostBinaryPath).arrayBuffer() const hostBytes = new Uint8Array(hostBinary) @@ -230,8 +192,8 @@ const moduleGraphBytes = hostBytes.slice(hostBunSize, hostBytes.length - 8) console.log(`Module graph extracted: ${moduleGraphBytes.length} bytes`) console.log(`Trailer verified: OK`) -// Step 5: Patch the module graph for Android -console.log("\n=== Step 5: Patching module graph for Android ===") +// Step 4: Patch the module graph for Android +console.log("\n=== Step 4: Patching module graph for Android ===") // The module graph format (from StandaloneModuleGraph.zig): // [string data: all file names, contents, sourcemaps, bytecodes concatenated] @@ -315,8 +277,8 @@ if (undiciPatchCount === 0) { var finalModuleGraph = mgBuf.slice(0, trailerPosInMg + mgTrailerBuf.length) console.log(`Module graph size: ${finalModuleGraph.length} bytes (unchanged)`) -// Step 6: Create Android standalone binary -console.log("\n=== Step 6: Creating Android standalone binary ===") +// Step 5: Create Android standalone binary +console.log("\n=== Step 5: Creating Android standalone binary ===") const androidBunBytes = new Uint8Array(await Bun.file(ANDROID_BUN).arrayBuffer()) const androidBunSize = androidBunBytes.length diff --git a/scripts/build-opentui.sh b/scripts/build-opentui.sh index cc87d8c..7dac48e 100755 --- a/scripts/build-opentui.sh +++ b/scripts/build-opentui.sh @@ -17,9 +17,12 @@ ZIG_BIN="${ZIG_BIN:-zig}" echo "=== Building libopentui.so for Android aarch64 ===" # Clone opentui if needed +# Pin to the tag matching the @opentui/core version bundled by the OpenCode +# release (v1.18.3 bundles @opentui/core@0.4.3). Cloning HEAD would build +# 0.5.x, whose native ABI does not match the 0.4.3 JS bindings. if [ ! -d "$OPENTUI_SRC/.git" ]; then - echo ">>> Cloning opentui..." - git clone --depth 1 https://github.com/anomalyco/opentui.git "$OPENTUI_SRC" + echo ">>> Cloning opentui (v${OPENTUI_VERSION})..." + git clone --depth 1 --branch "v${OPENTUI_VERSION}" https://github.com/anomalyco/opentui.git "$OPENTUI_SRC" else echo ">>> opentui source exists at $OPENTUI_SRC" fi diff --git a/scripts/env.sh b/scripts/env.sh index 541b874..50e2e48 100755 --- a/scripts/env.sh +++ b/scripts/env.sh @@ -13,7 +13,8 @@ export BUN_TAG="bun-v${BUN_VERSION}" export WEBKIT_COMMIT="${WEBKIT_COMMIT:-017930ebf915121f8f593bef61cbbca82d78132d}" export ICU_VERSION="${ICU_VERSION:-75.1}" export ZIG_VERSION="${ZIG_VERSION:-0.15.2}" -export OPENCODE_VERSION="${OPENCODE_VERSION:-1.3.13}" +export OPENCODE_VERSION="${OPENCODE_VERSION:-1.18.3}" +export OPENTUI_VERSION="${OPENTUI_VERSION:-0.4.3}" export ANDROID_API="${ANDROID_API:-24}" # Android NDK From edb9a6c79b87db26eae00726c7d30dd534b0c6f4 Mon Sep 17 00:00:00 2001 From: qioceky Date: Sat, 19 Sep 2026 13:11:21 +0000 Subject: [PATCH 02/11] CI: make build runner-friendly (generic WORK_DIR, JOBS=2) Remove /mnt/spindrive hardcode so the build can run on any Linux self-hosted runner. WORK_DIR defaults to $HOME/opencode-build. --- .github/workflows/build.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d425a5f..aa5b5b4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,10 +20,11 @@ env: TINYCC_COMMIT: "b91835d8701523e895a2b447713630069514e291" ICU_VERSION: "75.1" ZIG_VERSION: "0.15.2" - # golem10 has 15GB RAM — limit parallelism to avoid OOM during WebKit link - JOBS: "4" - # Use the large spindle drive for working directory - WORK_DIR: "/mnt/spindrive/opencode-build" + # Memory-safe parallelism default; override with repo secret if your + # runner has more (golem10 used JOBS=4 on 15GB RAM). + JOBS: "2" + # Generic default; can be overridden by setting WORK_DIR as a repo variable. + WORK_DIR: "$HOME/opencode-build" jobs: build: @@ -43,7 +44,7 @@ jobs: run: | mkdir -p "$WORK_DIR" echo "Work directory: $WORK_DIR" - df -h /mnt/spindrive + df -h "$WORK_DIR" # ============================================= # Install build dependencies @@ -340,4 +341,4 @@ jobs: # FileNotFound errors in the translate-c output cache. rm -rf "${WORK_DIR}/bun-build/cache/zig" echo "Cleaned up intermediate files" - df -h /mnt/spindrive + df -h "$WORK_DIR" From ad2b5a9d5dacc5824a3e62d794fc63cc17e74511 Mon Sep 17 00:00:00 2001 From: qioceky Date: Sat, 19 Sep 2026 13:37:39 +0000 Subject: [PATCH 03/11] CI: use absolute WORK_DIR (GitHub does not expand $HOME in env values) Reverts to an absolute path after the $HOME literal caused the WebKit clone to land inside the bun-src checkout and fail apply-patches. --- .github/workflows/build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aa5b5b4..1a4540a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,8 +23,9 @@ env: # Memory-safe parallelism default; override with repo secret if your # runner has more (golem10 used JOBS=4 on 15GB RAM). JOBS: "2" - # Generic default; can be overridden by setting WORK_DIR as a repo variable. - WORK_DIR: "$HOME/opencode-build" + # Generic default. GitHub does NOT expand $HOME inside env values, + # so use an absolute, writable path (override per repo if needed). + WORK_DIR: "/tmp/opencode-build" jobs: build: From 6cfa4323932b2b9586f3808ff72ce940587cf336 Mon Sep 17 00:00:00 2001 From: qioceky Date: Sat, 19 Sep 2026 13:48:17 +0000 Subject: [PATCH 04/11] CI: extend timeout to 12h for 2-core runner --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1a4540a..4430f5e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,7 +31,7 @@ jobs: build: name: Build Android aarch64 runs-on: self-hosted - timeout-minutes: 360 # 6 hours for full build + timeout-minutes: 720 # 12 hours for full build on 2-core runner steps: - name: Checkout repository From 56355e2453fc33b08686e5ed278fa00ed9ff1a64 Mon Sep 17 00:00:00 2001 From: qioceky Date: Sat, 19 Sep 2026 15:05:01 +0000 Subject: [PATCH 05/11] Fix opentui v0.4.3 Android build: skip dl/pthread, provide NDK libc --- patches/opentui/android-libc-link.patch | 71 ++++++++++++++++++++----- 1 file changed, 57 insertions(+), 14 deletions(-) diff --git a/patches/opentui/android-libc-link.patch b/patches/opentui/android-libc-link.patch index 2c56825..a6e9d84 100644 --- a/patches/opentui/android-libc-link.patch +++ b/patches/opentui/android-libc-link.patch @@ -1,8 +1,23 @@ diff --git a/packages/core/src/zig/build.zig b/packages/core/src/zig/build.zig -index 0c82ba3..797ae17 100644 +index 0c82ba3..3f5e40a 100644 --- a/packages/core/src/zig/build.zig +++ b/packages/core/src/zig/build.zig -@@ -494,6 +494,29 @@ fn buildTarget( +@@ -190,8 +190,12 @@ fn addNativeAudioDependencies( + switch (target.result.os.tag) { + .macos => addMacOSSystemLibraries(b, artifact, macos_sdk_path.?), + .linux => { +- artifact.linkSystemLibrary("dl"); +- artifact.linkSystemLibrary("pthread"); ++ const abi = target.result.abi; ++ const is_android = abi == .android or abi == .androideabi; ++ if (!is_android) { ++ artifact.linkSystemLibrary("dl"); ++ artifact.linkSystemLibrary("pthread"); ++ } + }, + else => {}, + } +@@ -494,6 +498,57 @@ fn buildTarget( addNativeAudioDependencies(b, lib, target, macos_sdk_path); addYogaDependencies(b, lib); @@ -12,20 +27,48 @@ index 0c82ba3..797ae17 100644 + const os_tag = target.result.os.tag; + const abi = target.result.abi; + if (os_tag == .linux and (abi == .android or abi == .androideabi)) { -+ const ndk_lib: ?[]const u8 = blk: { -+ if (std.process.getEnvVarOwned(b.allocator, "ANDROID_NDK_LIB_DIR") catch null) |dir| { -+ break :blk dir; -+ } -+ if (std.process.getEnvVarOwned(b.allocator, "ANDROID_NDK_HOME") catch null) |ndk_home| { -+ break :blk std.fmt.allocPrint(b.allocator, "{s}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/24", .{ndk_home}) catch null; -+ } -+ break :blk null; -+ }; -+ if (ndk_lib) |dir| { -+ // Use addObjectFile to directly link the libc.so stub without triggering -+ // Zig's native libc provision ++ const ndk_home = std.process.getEnvVarOwned(b.allocator, "ANDROID_NDK_HOME") catch null; ++ const sysroot = if (ndk_home) |home| ++ std.fmt.allocPrint(b.allocator, "{s}/toolchains/llvm/prebuilt/linux-x86_64/sysroot", .{home}) catch null ++ else ++ null; ++ ++ // Allow an explicit override of the lib dir (e.g. when the NDK layout ++ // differs), otherwise default to aarch64-linux-android API 24. ++ const arch_lib = std.process.getEnvVarOwned(b.allocator, "ANDROID_NDK_LIB_DIR") catch null; ++ const crt_dir = if (arch_lib) |dir| ++ dir ++ else if (sysroot) |sr| ++ std.fmt.allocPrint(b.allocator, "{s}/usr/lib/aarch64-linux-android/24", .{sr}) catch null ++ else ++ null; ++ ++ if (crt_dir) |dir| { ++ // Use addObjectFile to directly link the libc.so stub without ++ // triggering Zig's native glibc/musl libc provision. + const libc_path = std.fmt.allocPrint(b.allocator, "{s}/libc.so", .{dir}) catch unreachable; + lib.addObjectFile(.{ .cwd_relative = libc_path }); ++ ++ // Zig does not bundle Android libc, so point it at the NDK sysroot ++ // (headers + CRT objects) via a libc file. Without this, linking ++ // fails with "unable to provide libc for target ... android". ++ if (sysroot) |sr| { ++ const include_dir = std.fmt.allocPrint(b.allocator, "{s}/usr/include", .{sr}) catch unreachable; ++ const sys_include_dir = std.fmt.allocPrint(b.allocator, "{s}/usr/include/aarch64-linux-android", .{sr}) catch unreachable; ++ // All six fields are required by LibCInstallation.parse. ++ const libc_contents = std.fmt.allocPrint( ++ b.allocator, ++ \\include_dir={s} ++ \\sys_include_dir={s} ++ \\crt_dir={s} ++ \\msvc_lib_dir= ++ \\kernel32_lib_dir= ++ \\gcc_dir= ++ \\ ++ , .{ include_dir, sys_include_dir, dir }) catch unreachable; ++ const libc_file = b.addWriteFiles().add("android-libc.txt", libc_contents); ++ lib.setLibCFile(libc_file); ++ } + } + } + From 6d56647ba734dcf291214d8484ba3dd987e37930 Mon Sep 17 00:00:00 2001 From: qioceky Date: Sun, 20 Sep 2026 00:57:15 +0000 Subject: [PATCH 06/11] README: fix release asset names, binary size, build timings, requirements --- README.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 05564b7..45585ae 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ opencode ### Option 2: Pacman package (recommended if using pacman) ```bash -curl -LO https://github.com/guysoft/opencode-termux/releases/latest/download/opencode-aarch64.pkg.tar.xz +curl -LO https://github.com/guysoft/opencode-termux/releases/latest/download/opencode-1.18.3-1-aarch64.pkg.tar.xz pacman -U opencode-*-aarch64.pkg.tar.xz opencode ``` @@ -43,8 +43,8 @@ opencode ### Option 3: Deb package ```bash -curl -LO https://github.com/guysoft/opencode-termux/releases/latest/download/opencode-aarch64.deb -dpkg -i opencode-*-aarch64.deb +curl -LO https://github.com/guysoft/opencode-termux/releases/latest/download/opencode_1.18.3_aarch64.deb +dpkg -i opencode_*_aarch64.deb opencode ``` @@ -98,7 +98,7 @@ opencode-termux/ ## What Was Done -This project got OpenCode (a ~136MB standalone binary built on Bun + WebKit/JSC) running on Android/Termux, which required: +This project got OpenCode (a ~195MB standalone binary built on Bun + WebKit/JSC) running on Android/Termux, which required: 1. **Cross-compiling Bun v1.2.13 for Android/aarch64** -- Bun has zero Android support. We patched 33 files across the build system (CMake, Zig), syscall layer, Bionic libc compatibility, JSC/JIT configuration, and linker settings. @@ -118,15 +118,15 @@ This project got OpenCode (a ~136MB standalone binary built on Bun + WebKit/JSC) ``` Stage 1: ICU 75.1 ~5 min (cross-compile for Android) -Stage 2: WebKit/JSC ~60-90 min (cross-compile, CACHED) +Stage 2: WebKit/JSC ~30-60 min (cross-compile, CACHED) Stage 3: TinyCC ~1 min (cross-compile libtcc.a) Stage 4: Bun binary ~30-45 min (CMake + Ninja, CACHED) -Stage 5: libopentui.so ~2 min (Zig build for aarch64-linux-android) +Stage 5: libopentui.so ~1 min (Zig build for aarch64-linux-android) Stage 6: OpenCode bundle ~30 sec (bun build --compile, extract module graph) Stage 7: Packages ~10 sec (zip + pacman + deb) ``` -With warm caches (WebKit + Bun cached), CI runs complete in ~4 minutes. +The GitHub Actions workflow runs these stages in one job. A full run on a 2-core/8GB runner with a swap file takes roughly an hour end-to-end; CI caches (ICU, WebKit, Bun) are restored via `actions/cache` when the build inputs are unchanged. --- @@ -348,9 +348,10 @@ The Bun team [closed Android support as "not planned"](https://github.com/oven-s ## Build Requirements - **Build host**: x86_64 Linux (Ubuntu 22.04+) -- **RAM**: 16GB minimum (30GB recommended for WebKit link step) +- **RAM**: 8GB minimum with an 8GB+ swap file (16GB recommended for faster link steps) - **Disk**: 60GB+ free space -- **CPU**: 8+ cores recommended (4 cores works but slow) +- **CPU**: 2+ cores (8+ cores recommended; more is faster) +- The tested configuration: 2-core / 8GB RAM / 8GB swap, `JOBS=2` in the workflow. ### Required tools From a481335b24ed307bd7fb03745238fa7eba1ed164 Mon Sep 17 00:00:00 2001 From: qioceky Date: Sun, 20 Sep 2026 01:05:24 +0000 Subject: [PATCH 07/11] Upgrade to OpenCode v1.18.31 (opentui v0.4.5) - env.sh/workflow/build script defaults: OPENCODE_VERSION 1.18.31, OPENTUI_VERSION 0.4.5 - build-opencode.sh: install @opentui/core and @parcel/watcher for all platforms (docker --os=* --cpu=*) so bundling resolves every native variant; versions extracted from bun.lock so they never drift - README: version pins + release asset URLs updated --- .github/workflows/build.yml | 4 ++-- README.md | 8 ++++---- scripts/build-opencode-android.ts | 2 +- scripts/build-opencode.sh | 16 ++++++++++++++++ scripts/env.sh | 4 ++-- 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4430f5e..bc69dd2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,11 +9,11 @@ on: opencode_version: description: 'OpenCode version to build' required: false - default: '1.18.3' + default: '1.18.31' env: BUN_VERSION: "1.2.13" - OPENCODE_VERSION: "1.18.3" + OPENCODE_VERSION: "1.18.31" ANDROID_API: "24" ANDROID_NDK_VERSION: "28.1.13356709" WEBKIT_COMMIT: "017930ebf915121f8f593bef61cbbca82d78132d" diff --git a/README.md b/README.md index 45585ae..66a2f94 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ opencode ### Option 2: Pacman package (recommended if using pacman) ```bash -curl -LO https://github.com/guysoft/opencode-termux/releases/latest/download/opencode-1.18.3-1-aarch64.pkg.tar.xz +curl -LO https://github.com/guysoft/opencode-termux/releases/latest/download/opencode-1.18.31-1-aarch64.pkg.tar.xz pacman -U opencode-*-aarch64.pkg.tar.xz opencode ``` @@ -43,7 +43,7 @@ opencode ### Option 3: Deb package ```bash -curl -LO https://github.com/guysoft/opencode-termux/releases/latest/download/opencode_1.18.3_aarch64.deb +curl -LO https://github.com/guysoft/opencode-termux/releases/latest/download/opencode_1.18.31_aarch64.deb dpkg -i opencode_*_aarch64.deb opencode ``` @@ -339,8 +339,8 @@ The Bun team [closed Android support as "not planned"](https://github.com/oven-s | Android NDK | r28b (28.1.13356709) | Clang 19, stable | | Android API level | 24 (Android 7.0+) | Minimum for 64-bit Termux | | Zig (for opentui) | 0.15.2 | Latest stable, Android target support | -| opentui (native lib) | v0.4.3 | Matches `@opentui/core@0.4.3` bundled by OpenCode v1.18.3 | -| OpenCode | 1.18.3 | Current release | +| opentui (native lib) | v0.4.5 | Matches `@opentui/core@0.4.5` bundled by OpenCode v1.18.31 | +| OpenCode | 1.18.31 | Current release | | TinyCC | `b91835d8` (oven-sh/tinycc) | Matches Bun v1.2.13's expected TinyCC | --- diff --git a/scripts/build-opencode-android.ts b/scripts/build-opencode-android.ts index a55dd59..07d95b8 100644 --- a/scripts/build-opencode-android.ts +++ b/scripts/build-opencode-android.ts @@ -27,7 +27,7 @@ if (!fs.existsSync(ANDROID_BUN)) { process.chdir(OPENCODE_DIR) -const VERSION = process.env.OPENCODE_VERSION || "1.18.3" +const VERSION = process.env.OPENCODE_VERSION || "1.18.31" const CHANNEL = process.env.OPENCODE_CHANNEL || "latest" console.log(`Building OpenCode v${VERSION} (channel: ${CHANNEL}) for Android aarch64`) diff --git a/scripts/build-opencode.sh b/scripts/build-opencode.sh index 0333338..572910f 100755 --- a/scripts/build-opencode.sh +++ b/scripts/build-opencode.sh @@ -38,6 +38,22 @@ echo ">>> Installing OpenCode dependencies..." cd "$OPENCODE_SRC" "$HOST_BUN" install +# Install all platform binary variants so dynamic imports of the native +# packages resolve during bundling (plain `bun install` only fetches the +# current host's platform optional deps). Versions are read from the +# lockfile so they always match what OpenCode bundles. +LOCKFILE="$OPENCODE_SRC/bun.lock" +extract_pkg_version() { + grep -o '"@'"$1"'@[0-9][^"]*"' "$LOCKFILE" | head -n1 | sed 's/^"@[^@]*@//; s/"$//' +} + +OPENTUI_CORE_VER="$(extract_pkg_version 'opentui/core')" +PARCEL_WATCHER_VER="$(extract_pkg_version 'parcel/watcher')" +echo ">>> Installing @opentui/core@${OPENTUI_CORE_VER} for all platforms..." +"$HOST_BUN" install --os="*" --cpu="*" "@opentui/core@${OPENTUI_CORE_VER}" +echo ">>> Installing @parcel/watcher@${PARCEL_WATCHER_VER} for all platforms..." +"$HOST_BUN" install --os="*" --cpu="*" "@parcel/watcher@${PARCEL_WATCHER_VER}" + # Find the Android bun binary ANDROID_BUN="$BUN_BUILD/bun" if [ ! -f "$ANDROID_BUN" ]; then diff --git a/scripts/env.sh b/scripts/env.sh index 50e2e48..7e0737b 100755 --- a/scripts/env.sh +++ b/scripts/env.sh @@ -13,8 +13,8 @@ export BUN_TAG="bun-v${BUN_VERSION}" export WEBKIT_COMMIT="${WEBKIT_COMMIT:-017930ebf915121f8f593bef61cbbca82d78132d}" export ICU_VERSION="${ICU_VERSION:-75.1}" export ZIG_VERSION="${ZIG_VERSION:-0.15.2}" -export OPENCODE_VERSION="${OPENCODE_VERSION:-1.18.3}" -export OPENTUI_VERSION="${OPENTUI_VERSION:-0.4.3}" +export OPENCODE_VERSION="${OPENCODE_VERSION:-1.18.31}" +export OPENTUI_VERSION="${OPENTUI_VERSION:-0.4.5}" export ANDROID_API="${ANDROID_API:-24}" # Android NDK From c654c11e90b62267801921cefbd5ccb27441442d Mon Sep 17 00:00:00 2001 From: qioceky Date: Sun, 20 Sep 2026 02:18:00 +0000 Subject: [PATCH 08/11] README: single-binary zip install, real sizes, best-effort install-all-platforms --- README.md | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 66a2f94..fc02424 100644 --- a/README.md +++ b/README.md @@ -8,22 +8,18 @@ OpenCode is an AI-powered coding assistant for the terminal. It uses [Bun](https ### Option 1: Standalone binary (easiest) -> **Note:** The zip now contains a wrapper script (`opencode`), the main binary -> (`opencode.bin`), and native libraries (`.so` files). All files must be -> installed to their proper locations. +> **Note:** The zip contains a single self-contained `opencode` binary (~200MB). +> All native libraries (`libopentui.so`, etc.) are embedded, so no extra files +> need to be installed. ```bash # Download the latest "opencode-*-android-aarch64.zip" from # https://github.com/guysoft/opencode-termux/releases/latest # Then install: -mkdir -p $PREFIX/libexec/opencode $PREFIX/lib unzip opencode-*-android-aarch64.zip mv opencode $PREFIX/bin/opencode chmod +x $PREFIX/bin/opencode -mv opencode.bin $PREFIX/libexec/opencode/opencode.bin -chmod +x $PREFIX/libexec/opencode/opencode.bin -mv libtagfix.so libc++_shared.so libopentui.so $PREFIX/lib/ # Install required dependency pkg install ripgrep @@ -209,7 +205,7 @@ Bun has zero Android support. Every patch falls into one of these categories: Since `bun build --compile` has no Android cross-compilation target, we use a manual approach: -1. Use **host Bun (v1.3.2)** to `bun build --compile` OpenCode for the host platform +1. Use **host Bun (v1.3.2)** to `bun build --compile` OpenCode for the host platform. Before bundling, run `bun install --os="*" --cpu="*" @opentui/core@...` (versions parsed from `bun.lock`) so every platform's optional-dependency variant is present — otherwise bundling fails to resolve e.g. `@opentui/core-win32-arm64` 2. Extract the serialized **module graph** from the host standalone binary by locating the `\n---- Bun! ----\n` trailer and reading the `Offsets` struct 3. Patch the module graph in-place (fix `undici` global reference) 4. Before bundling, swap x86_64 `libopentui.so` with the ARM64 Android-built version, so it gets embedded in the module graph @@ -218,8 +214,8 @@ Since `bun build --compile` has no Android cross-compilation target, we use a ma The standalone binary format: ``` -[Android Bun binary (~96 MB)] -[Module graph bytes (~46 MB)] +[Android Bun binary (~93 MB)] +[Module graph bytes (~104 MB)] [total_byte_count as u64 LE (8 bytes)] ``` @@ -247,7 +243,7 @@ We can't use Bun 1.2.13 as host either, because OpenCode's monorepo uses `catalo | Issue | Severity | Details | |-------|----------|---------| -| File watcher native module | Low | `@parcel/watcher` `.node` binding is compiled for x86_64. Falls back gracefully to polling. Logs: `dlopen failed: "...00000001.node" is for EM_X86_64 (62) instead of EM_AARCH64 (183)` | +| `@parcel/watcher` file watcher | None | Declared dependency but not imported by OpenCode's source. The build installs all platform variants (including `linux-arm64`) so nothing depends on the x86_64 prebuilt. | | `bun upgrade` | Low | Disabled on Android -- no Android release channel exists upstream | | TinyCC FFI compilation | Low | `libtcc.a` is linked but TCC's runtime code generation may not produce valid ARM64 code. FFI is not commonly used by OpenCode. | | SIGPWR signals | None | Many SIGPWR signals appear in strace -- related to Android's power management or Bun's signal handling. Not errors. | From 65c1a65a3dc38667a73b0e26d901611c6d5035a6 Mon Sep 17 00:00:00 2001 From: qioceky Date: Sun, 20 Sep 2026 02:20:11 +0000 Subject: [PATCH 09/11] README: document opentui build.zig patch, CI release triggers, device-test status --- README.md | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fc02424..1b3aecb 100644 --- a/README.md +++ b/README.md @@ -73,9 +73,10 @@ opencode-termux/ bun/android-support.patch # 33 files, Bun Android/aarch64 support webkit/android-support.patch # 5 files, WebKit/JSC Android fixes zig/posix-android-sigaction.patch # Zig stdlib sigaction/sigprocmask fix - opentui/android-libc-link.patch # Link NDK libc.so for Android dlopen + opentui/android-libc-link.patch # Android build: skip dl/pthread, link NDK libc.so + setLibCFile scripts/ apply-patches.sh # Clone upstream repos + apply patches + env.sh # Version pins (OpenCode, opentui, Bun, etc.) build-icu.sh # Cross-compile ICU 75.1 for Android build-webkit.sh # Cross-compile WebKit/JSC for Android build-tinycc.sh # Cross-compile TinyCC (libtcc.a) for Android @@ -102,7 +103,7 @@ This project got OpenCode (a ~195MB standalone binary built on Bun + WebKit/JSC) 3. **Fixing Zig's stdlib for Android/Bionic** -- Zig's `sigaction()` and `sigprocmask()` pass a 152-byte struct through Bionic's libc which expects 32 bytes, causing silent memory corruption. Patched to use raw syscalls on Android. -4. **Building libopentui.so for Android** -- OpenCode's TUI renderer depends on OpenTUI, which needed a patch to link Android NDK's libc.so stub so `dlopen()` can resolve symbols at runtime. +4. **Building libopentui.so for Android** -- OpenCode's TUI renderer depends on OpenTUI, which needed a patch to (a) skip linking `dl`/`pthread` (already inside Bionic on Android), (b) link the Android NDK's `libc.so` stub so `dlopen()` can resolve symbols at runtime, and (c) provision a libc via `setLibCFile()` so Zig can compile for the Android target (Zig only bundles glibc/musl). 5. **Standalone binary surgery** -- Since `bun build --compile` has no Android cross-compilation target, we build a host standalone binary, extract the serialized module graph, and transplant it onto the Android Bun binary. This required understanding and matching the binary format across Bun versions (36-byte vs 52-byte module struct stride). @@ -197,7 +198,10 @@ Bun has zero Android support. Every patch falls into one of these categories: ### OpenTUI Patch (1 file) -- **Link NDK `libc.so` stub** -- On Android, the `.so` must have `NEEDED: libc.so` in its ELF headers so `dlopen()` can resolve symbols like `getauxval`. Zig doesn't bundle Android libc, so we directly add the NDK sysroot's `libc.so` stub as a link input. +- **Android `build.zig` support** -- three changes to compile `libopentui.so` for `aarch64-linux-android`: + 1. **Skip `dl`/`pthread`** -- Zig's `addNativeAudioDependencies` unconditionally links `dl` + `pthread` on `.linux`; on Android those live inside Bionic's libc, so the link fails ("unable to find dynamic system library 'dl'"). Guarded with `if (!is_android)`. + 2. **Link NDK `libc.so` stub** -- the `.so` must carry `NEEDED: libc.so` in its ELF headers so `dlopen()` can resolve symbols like `getauxval`. We `addObjectFile()` the NDK sysroot's `libc.so` stub. + 3. **Provision libc via `setLibCFile()`** -- Zig bundles only glibc/musl, so compiling for Android fails with "unable to provide libc for target". We write a libc file pointing at the NDK sysroot (include dirs + crt dir, read from `ANDROID_NDK_HOME` / `ANDROID_NDK_LIB_DIR` env) and feed it to the compile step. --- @@ -366,11 +370,29 @@ The Bun team [closed Android support as "not planned"](https://github.com/oven-s --- +## Building and Releasing (CI) + +The `.github/workflows/build.yml` workflow has two triggers: + +1. **Manual dispatch** (`workflow_dispatch`) -- "Run workflow" button → input `opencode_version` (e.g. `1.18.31`). Builds and uploads packages as a run artifact, but **does not** create a GitHub Release. + +2. **Tag push** (`on: push: tags: ['v*']`) -- pushing `git tag v` to the repo triggers a full build AND auto-creates a GitHub Release with the three packages (`.zip`, `.pkg.tar.xz`, `.deb`) attached. + +- The workflow is designed for a **self-hosted runner** (`golem10`): `WORK_DIR`, `JOBS`, and the 12-hour timeout are configurable. GitHub-hosted runners are too small/slow for the Bun + WebKit builds. +- Repo must have **workflow permissions = Read and write** (Settings → Actions → General) or the release step fails with `Resource not accessible by integration`. +- Upgrading OpenCode = update `OPENCODE_VERSION` / `OPENTUI_VERSION` in `scripts/env.sh` (mirror the pins in `.github/workflows/build.yml` and the fallback in `scripts/build-opencode-android.ts`), then `git tag v && git push --tags`. + +--- + ## Tested On -- Samsung Galaxy S10e (Android 12, Termux, aarch64) -- full TUI confirmed working +- Samsung Galaxy S10e (Android 12, Termux, aarch64) -- full TUI confirmed working (verified on an earlier OpenCode release) - Meta Quest 2 (Android 12L, adb shell) +> **Note:** The v1.18.31 build is compiled and its bundle pipeline is verified, but it has not been +> installed/run on an actual Android device yet. If anything misbehaves in the TUI, models list, or +> startup, please open an issue. + --- ## Credits From 3df0597d7c438c3853142ec2edf4c26dd2f6f5cb Mon Sep 17 00:00:00 2001 From: qioceky Date: Sun, 20 Sep 2026 02:26:19 +0000 Subject: [PATCH 10/11] Fix deb install path (data/data/com.termux), point install URLs at fork release --- README.md | 7 ++++--- scripts/make-packages.sh | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1b3aecb..e645fc6 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ OpenCode is an AI-powered coding assistant for the terminal. It uses [Bun](https ```bash # Download the latest "opencode-*-android-aarch64.zip" from -# https://github.com/guysoft/opencode-termux/releases/latest +# https://github.com/qioceky/opencode-termux/releases/tag/v1.18.31 +# (pending upstream merge — the canonical "latest" is the fork above) # Then install: unzip opencode-*-android-aarch64.zip @@ -31,7 +32,7 @@ opencode ### Option 2: Pacman package (recommended if using pacman) ```bash -curl -LO https://github.com/guysoft/opencode-termux/releases/latest/download/opencode-1.18.31-1-aarch64.pkg.tar.xz +curl -LO https://github.com/qioceky/opencode-termux/releases/latest/download/opencode-1.18.31-1-aarch64.pkg.tar.xz pacman -U opencode-*-aarch64.pkg.tar.xz opencode ``` @@ -39,7 +40,7 @@ opencode ### Option 3: Deb package ```bash -curl -LO https://github.com/guysoft/opencode-termux/releases/latest/download/opencode_1.18.31_aarch64.deb +curl -LO https://github.com/qioceky/opencode-termux/releases/latest/download/opencode_1.18.31_aarch64.deb dpkg -i opencode_*_aarch64.deb opencode ``` diff --git a/scripts/make-packages.sh b/scripts/make-packages.sh index 1787b69..6eae610 100755 --- a/scripts/make-packages.sh +++ b/scripts/make-packages.sh @@ -74,11 +74,11 @@ echo " Created $PACMAN_NAME" # ========================================== echo ">>> Creating deb package..." DEB_STAGING="$PKG_DIR/deb-staging" -mkdir -p "$DEB_STAGING/data/data/data/com.termux/files/usr/bin" +mkdir -p "$DEB_STAGING/data/data/com.termux/files/usr/bin" mkdir -p "$DEB_STAGING/DEBIAN" -cp "$OPENCODE_BINARY" "$DEB_STAGING/data/data/data/com.termux/files/usr/bin/opencode" -chmod 755 "$DEB_STAGING/data/data/data/com.termux/files/usr/bin/opencode" +cp "$OPENCODE_BINARY" "$DEB_STAGING/data/data/com.termux/files/usr/bin/opencode" +chmod 755 "$DEB_STAGING/data/data/com.termux/files/usr/bin/opencode" # Create control file INSTALLED_SIZE=$((BINARY_SIZE / 1024)) From 4da89229fb5cce62ba5408232d1c6ad7e20cdb47 Mon Sep 17 00:00:00 2001 From: qioceky Date: Sun, 20 Sep 2026 02:46:30 +0000 Subject: [PATCH 11/11] Fix deb package install path (data/data/com.termux), robust install docs --- README.md | 14 ++++++++++++-- scripts/make-packages.sh | 7 +++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e645fc6..ec68e1a 100644 --- a/README.md +++ b/README.md @@ -33,18 +33,28 @@ opencode ```bash curl -LO https://github.com/qioceky/opencode-termux/releases/latest/download/opencode-1.18.31-1-aarch64.pkg.tar.xz -pacman -U opencode-*-aarch64.pkg.tar.xz +pacman -U opencode-1.18.31-1-aarch64.pkg.tar.xz opencode ``` +> If pacman complains about a missing/writable keyring, initialize it first: +> ```bash +> pacman-key --init +> pacman-key --populate +> ``` + ### Option 3: Deb package ```bash curl -LO https://github.com/qioceky/opencode-termux/releases/latest/download/opencode_1.18.31_aarch64.deb -dpkg -i opencode_*_aarch64.deb +pkg install ./opencode_1.18.31_aarch64.deb opencode ``` +> Use the exact filename — do NOT glob (`opencode_*_aarch64.deb`): if an older +> OpenCode `.deb` is still in your working directory, pkg/dpkg will try to install +> both and the unpack can fail. Delete old `.deb` files first if you have any. + The pacman and deb packages automatically install `ripgrep` as a dependency. ### After install diff --git a/scripts/make-packages.sh b/scripts/make-packages.sh index 6eae610..ef29c2b 100755 --- a/scripts/make-packages.sh +++ b/scripts/make-packages.sh @@ -99,8 +99,11 @@ EOF DEB_NAME="opencode_${OPENCODE_VERSION}_aarch64.deb" -# Build deb manually (dpkg-deb may not be available) -cd "$DEB_STAGING/data" +# Build deb manually (dpkg-deb may not be available). +# The tar must contain "data/data/com.termux/..." (2x data) so dpkg extracts +# to /data/data/com.termux/files/usr/bin/opencode. Run tar from the staging +# ROOT so the leading "data" component is preserved. +cd "$DEB_STAGING" tar czf "$DEB_STAGING/data.tar.gz" data cd "$DEB_STAGING/DEBIAN" tar czf "$DEB_STAGING/control.tar.gz" control