fix: proot deps, custom gateway port, node command policy, Ollama (v2026.9.14)
Summary
Fixes setup being completely broken for new installs, closes several long-standing issues, and brings the Android toolchain up to a version that actually compiles on current Flutter.
Every behavioural change was checked against the upstream OpenClaw docs, then verified on a real device (Android 15, arm64), not just compiled.
The headline bug: setup could never work
On a fresh install, setup failed immediately with:
Setup failed: PlatformException(PROOT_ERROR, Command failed (exit code 1):
CANNOT LINK EXECUTABLE ".../lib/arm64/libproot.so": library "libandroid-shmem.so" not found
Termux's proot declares:
Package: proot
Version: 5.1.107.92
Depends: libandroid-shmem, libtalloc
scripts/fetch-proot-binaries.sh only fetched libtalloc, so the APK shipped a proot binary the dynamic linker could never load. Nothing downstream of it could run: no rootfs configuration, no Node.js, no OpenClaw. This is a plausible root cause for the recent wave of "setup failed" and "app is stuck" reports.
Fixed by fetching libandroid-shmem too, and by making the CI verification step fail (it previously only warned) when any of the four required libraries is missing or placeholder-sized, so a broken APK cannot be published again.
Verified on device:
$ libproot.so --version
proot 5.1.107.92
built-in accelerators: process_vm = yes, seccomp_filter = yes
Issues fixed
#124 Custom gateway port ignored
18789 was hard-coded in eight places: the health check, dashboard URL, token-URL regex, node WebSocket, native port probe, watchdog, notification, and the Termux CLI. Setting gateway.port left the app probing the wrong address and eventually declaring a healthy gateway dead.
Upstream precedence is --port > OPENCLAW_GATEWAY_PORT > gateway.port > 18789. A new GatewayConfig service resolves gateway.port from openclaw.json; GatewayService.kt reads it natively and passes --port explicitly for non-default ports. Token URLs are matched on any port now, so a custom port is auto-detected from gateway output.
#81 and #95 Node commands were never authorised
The app wrote gateway.nodes.allowCommands / denyCommands. Those keys do not exist upstream. The canonical keys are:
{ gateway: { nodes: { commands: { allow: [...], deny: [] } } } }
So every classified command (camera.snap, screen.record, and so on) had been silently unauthorised since the feature shipped, which is exactly why "how do I activate the camera" and "openclaw nodes list is empty" kept coming up. Now written under the correct keys, the bogus legacy keys are deleted from existing configs, and gateway.nodes.pairing.autoApproveLocal is set so the in-app node pairs without manual approval.
#133 npm install -g openclaw fails with ENOTEMPTY
An interrupted install leaves /usr/local/lib/node_modules/openclaw plus .openclaw-* staging dirs behind, and npm then fails to stage over them. Those are now cleared before installing, with a retry after a hard cache clean. Node.js also moves 22.14.0 to 22.23.2, satisfying undici's engines.node >= 22.19.0 (the EBADENGINE warning in that report).
#99 npm package lagged the repo
lib/index.js hard-coded VERSION = '1.7.3' while the package was 1.8.7. The version is now read from package.json, a new npm-publish.yml workflow publishes when the version changes, and tests assert package.json, pubspec.yaml and constants.dart agree so they cannot drift again.
#125 "app is stuck"
The splash screen ran the whole repair path inline, a Node.js download plus npm install -g openclaw with a 30 minute timeout, behind a static spinner, with no progress, no notification and no foreground service. The app looked frozen and Android could kill it mid-install, losing all the work.
Repair now runs in the setup wizard via BootstrapService.runRepair(), with step progress, a progress notification, and the wake-lock-holding foreground service. It also skips the ~500 MB rootfs download, since the rootfs is already present.
#107 SSH docs and hardening
Documented the actual setup order and every default with its rationale. sshd binds 0.0.0.0 (needed for LAN access, #61), so it now performs a pre-flight check for a real root password hash in /etc/shadow and refuses to start otherwise, instead of exposing a passwordless root shell. PermitEmptyPasswords no is pinned explicitly.
#117 Ollama support
Added, following the upstream provider docs exactly:
- No
/v1in the base URL. That path selects OpenAI-compatible mode where tool calling is unreliable. A trailing/v1is stripped automatically. api: "ollama"pinned so native tool calling is guaranteed.- No API key required (a placeholder is written for availability checks).
timeoutSeconds: 300for cold local models, and provider-qualified refs (ollama/qwen3:8b).- Editable base URL for LAN hosts or Ollama Cloud.
Gateway token
The gateway had no gateway.auth.token at all on a fresh setup, so the Control UI opened on a manual "Gateway Token" prompt and the node had no credential to present.
The config patch now guarantees a persistent token (32 random bytes, hex) before each start, so the tokenised URL works again:
http://localhost:18789/#token=8da9c1eb44f7e1525f9eb9fb6a0d320fe44f7132235fc9dca465e5f5f2420eab
It never overwrites an existing credential and leaves password, trusted-proxy and none auth setups untouched, because upstream fails startup when both a token and password are set without an explicit mode.
The node uses the same token. It already preferred gateway.auth.token from config, and its cached token is now cleared on gateway restart so a rotated token cannot cause TOKEN_INVALID reconnect loops (#94).
Verified on device: Gateway Running, tokenised URL shown with copy and open buttons, Web Dashboard card showing Token: 8da9c1eb..., and Node "Connected to gateway".
Performance
Setup wall-time, which is the substance of #125 and #111:
- Node.js download now overlaps
apt. It previously waited for apt to finish, making two slow phases fully serial. python3 make g++are no longer installed up front. Several hundred MB and a long dpkg run under proot, needed only when npm has to compile a native addon. Now installed by a fallback only if the install actually fails.apt-get update -o Acquire::Languages=noneskips translation indexes nobody reads;Acquire::Retries=3helps on flaky mobile data.- npm runs with
--omit=dev --no-audit --no-fund. - The CLI path drops a blanket
apt upgrade -yon a freshly downloaded rootfs. getInstallStatus({ skipProotProbe })avoids repeated 30 second proot probes during setup.
Runtime:
- Gateway log pipeline was O(n squared). Every line did
[..._state.logs, log], a full copy of up to 500 entries, and pushed a new state, rebuilding the log UI once per line.--verboseemits thousands of lines. Replaced with a bounded ring buffer (O(1) amortised) and a 100 ms coalesced flush, so rebuilds are bounded by time rather than line count. Token detection stays synchronous so the dashboard URL is never delayed.
I could not measure the setup improvement end to end; that needs a full ~500 MB install run on a device.
Security
- Node private key and tokens were in cloud backup.
FlutterSharedPreferences.xmlholds the node Ed25519 private key, the paired device token and the gateway auth token, and bothbackup_rules.xmlanddata_extraction_rules.xmlincludedsharedprefwholesale. That file is now excluded from cloud backup and device transfer. - Settings snapshots leaked tokens. The export writes to shared storage (
Downloads), where any app with storage access can read it. Device and gateway tokens are now omitted from both snapshot paths. - Pairing code validated against
^[A-Za-z0-9_-]{4,32}$before being interpolated into the auto-approve shell command; the value arrives from the gateway over the network. - sshd refuses to start without a root password (above).
Toolchain
The project could not build on any current Flutter: the Gradle wrapper was 8.3, below Flutter's 8.7 minimum. Raising CI's Flutter version alone would have broken the build, so:
| Before | After | |
|---|---|---|
| Gradle | 8.3 | 8.14.3 |
| AGP | 8.1.0 | 8.11.1 |
| Kotlin | 1.9.0 | 2.2.20 |
| compileSdk | 35 | 36 |
| CI Flutter | 3.24.0 | 3.44.8 |
AGP stays on 8.x deliberately, because AGP 9 removes the legacy DSL this project uses. ndkVersion was added to the plugin subproject defaults, without which the jni plugin fails with Parameter specified as non-null is null: LibraryExtension.setNdkVersion.
CardTheme and DialogTheme migrated to CardThemeData and DialogThemeData, clearing 4 pre-existing analyzer errors.
Tests and project rules
The repo had no Dart tests and flutter analyze ran with continue-on-error: true, so analyzer regressions could not fail the build.
- New
flutter_app/test/, 25 tests covering port resolution, token capture (including the wrapped-TUI-box reconstruction that has broken repeatedly), the generated token's shape, and the provider contract. - npm suite expanded from 11 to 18 tests, including version-consistency guards and a style guard.
- CI now runs
flutter testand no longer swallowsflutter analyzefailures. - New
AGENTS.mddocuments the writing style, the Windows/PowerShell environment, build and verify commands, the proot binary requirement, and the hard rules (do not rename the application ID, never delete outsidefilesDir, match upstream config keys, do not hard-code port 18789, keep versions in sync). - All em dashes and en dashes were replaced with ASCII hyphens across 24 files, with a test that fails if any are reintroduced. Box-drawing characters in the README diagram and the
_boxDrawingregexes are untouched.
Verification
| Check | Result |
|---|---|
flutter analyze | 0 errors (5 pre-existing deprecation infos) |
flutter test | 25/25 pass |
npm test | 18/18 pass |
npx eslint . | clean |
flutter build apk --release | succeeds (43.5 MB) |
adb install -r | Success |
| On device | Gateway Running on 127.0.0.1:18789, Node Connected, tokenised URL present, no FATAL in logcat |
netstat on the device, after setup completed through the full proot to Node to OpenClaw path:
tcp 0 0 127.0.0.1:18789 0.0.0.0:* LISTEN
Docs
- README capability table corrected to 9 capabilities / 21 commands.
battery.statusand the fiveserial.*commands were entirely undocumented. Plus new Gateway Port, SSH and Ollama sections, and a corrected structure tree. - Privacy policy gained a Node Device Capabilities section disclosing that camera, location, screen and sensor data can reach your configured AI provider, and now lists the hardware permissions the node requests.
- All NextGenX references removed from the app, README, privacy policy and release assets.
Notes for the reviewer
- The
com.nxg.openclawprootapplication ID was deliberately left alone. Renaming it would break updates for every existing user. flutter_app/pubspec.lockis now committed; the CI cache key already referenced it.- Generated build files (
gradlew,gradle-wrapper.jar,GeneratedPluginRegistrant.java,.flutter-plugins-dependencies) are gitignored rather than committed. - Lowering
minSdkto 28 for Android 9 (#100) was not attempted, since it cannot be verified without a device on that API level.
Summary by CodeRabbit
-
New Features
- Added Ollama support with configurable local provider URLs.
- Added configurable gateway ports instead of requiring port 18789.
- Added repair mode for incomplete or failed installations.
- Added Battery and Serial node capabilities.
- Added project links for issues, releases, and upstream OpenClaw resources.
-
Security
- Sensitive credentials are excluded from backups and exported settings snapshots.
- SSH startup now requires a root password.
- Pairing-code validation and gateway token handling were strengthened.
-
Bug Fixes
- Improved installation recovery, package setup reliability, and custom-port dashboard connectivity.
- Updated Android and Flutter tooling for improved compatibility.