Context
Termux's native Node.js package on the apt-android-5 repo is version 12, which is far too old for OpenClaw (requires Node 18+). Inside the proot Ubuntu environment, the standard approach would be apt install nodejs, but dpkg frequently breaks inside proot due to filesystem syscall emulation issues (fakeroot conflicts, dpkg lock failures, statx syscall not intercepted properly).
The solution is to skip package managers entirely and use the pre-compiled binary tarball from nodejs.org. Node.js provides official linux-armv7l builds that include the V8 engine, npm, and full ICU data. These run on the Moto E2's Snapdragon 410 (Cortex-A53 cores operating in 32-bit ARMv7 mode).
This hack is marked LEGACY. The proot Node.js was replaced by a custom-compiled node22-icu binary running natively on Termux (58 MB, stripped, NDK r26c, API 24 with small-icu). See hack 12 for the native compilation story.
Implementation
Download and extract Node.js 22 inside the proot rootfs:
# From inside proot (run-proot.sh)
cd /root
# Download the official ARM binary tarball
curl -L -o node-v22.12.0-linux-armv7l.tar.xz \
"https://nodejs.org/dist/v22.12.0/node-v22.12.0-linux-armv7l.tar.xz"
# Verify the SHA256 checksum matches nodejs.org/dist/v22.12.0/SHASUMS256.txt
sha256sum node-v22.12.0-linux-armv7l.tar.xzExtract and install to /usr/local:
# Extract the tarball
tar -xf node-v22.12.0-linux-armv7l.tar.xz
# Copy all contents to /usr/local (bin/, lib/, include/, share/)
cp -r node-v22.12.0-linux-armv7l/* /usr/local/
# Clean up the tarball and extracted directory
rm -rf node-v22.12.0-linux-armv7l node-v22.12.0-linux-armv7l.tar.xzVerify the installation:
node --version
# v22.12.0
npm --version
# 10.x.x
# Confirm ICU is included (needed for Unicode property escapes \p{L} \p{N})
node -e "console.log(Intl.DateTimeFormat('fr').format(new Date()))"
# Should print a French-formatted date
# Confirm architecture
node -e "console.log(process.arch, process.platform)"
# arm linuxSet the V8 heap limit to survive on 1 GB RAM:
# In /root/.openclaw/env or the start script
NODE_OPTIONS="--max-old-space-size=128"The binary can also be invoked from Termux through the proot helper:
# From Termux (outside proot)
run-proot.sh -c "node --version"
run-proot.sh -c "node /root/.openclaw/gateway/dist/index.js"Verification
node --versioninside proot returnsv22.12.0.node -e "console.log(process.arch)"returnsarm.node -e "console.log(/\p{L}/u.test('e'))"returnstrue(Unicode property escapes work, confirming ICU is present).file /usr/local/bin/nodeshowsELF 32-bit LSB executable, ARM, EABI5.npm installof a small package succeeds inside proot.- The OpenClaw gateway starts without "unsupported engine" or syntax errors.
node -e "console.log(process.versions)"shows v8, icu, unicode versions.
Gotchas
- The
linux-armv7lbuild is the correct one, notlinux-arm64. Even though the Cortex-A53 supports ARMv8/AArch64, the Moto E2 runs a 32-bit Android kernel and userspace. Using the arm64 binary results inexec format error. - Node.js 22 binaries from nodejs.org include full ICU by default. This is critical because OpenClaw's codebase uses
\p{L}and\p{N}Unicode property escapes in regular expressions. A--without-intlbuild would crash with "Invalid regular expression" errors at runtime. - The V8 heap limit inside proot should be 128 MB maximum. The live heap peaks around 93-105 MB during gateway startup. Setting
--max-old-space-size=96causes OOM. The native migration later proved 112 MB is the true minimum (128 had been used in proot with some margin). - proot adds syscall overhead to every filesystem operation. Node.js startup is noticeably slower inside proot (roughly 2-3x) compared to native execution. Module resolution, which makes many
stat()calls, is particularly affected. - The tarball extracts a
node-v22.12.0-linux-armv7l/directory containingbin/,lib/,include/, andshare/. Usingtar -xfwithout--strip-components=1 -C /usr/localcreates a nested directory. Thecp -rapproach shown above is more explicit. - npm's global prefix inside proot defaults to
/usr/local. This works because Node was extracted there. If extracted elsewhere,npm config set prefix /path/to/nodeis needed. - The full ICU build adds roughly 25 MB to the binary size. The later native build used
--with-intl=small-icuto save space while still supporting\p{L}patterns.
Result
Node.js 22.12.0 runs inside proot on the Moto E2, providing a modern JavaScript runtime capable of running OpenClaw. The binary download approach completely bypasses all package manager issues. Combined with the run-proot.sh helper (hack 7) and V8 heap limits, this gave the phone a working OpenClaw gateway at roughly 170 MB RSS (including proot overhead). The native migration later reduced this to 155 MB by eliminating proot entirely and using a custom-compiled binary with small-icu.