| name | constrained-browser-automation | |||
|---|---|---|---|---|
| description | Run headless browsers on constrained ARM/Android devices without root, Playwright, or X11 | |||
| title | Constrained Browser Automation | |||
| version | 1.1 | |||
| trigger | Running headless browser engines on resource-constrained ARM/Android devices (Termux, old phones, embedded Linux) for web scraping, JS rendering, or agent-driven navigation without root access or desktop dependencies. | |||
| prerequisites |
|
How to run real browser engines on low-resource ARM devices (Android via Termux, Raspberry Pi, old phones) where standard tooling fails: no Playwright wheels, no glibc compatibility, no X11 server, limited RAM/disk.
What looks impossible is possible. The chromium/x11 package in Termux
contains a working headless Chrome binary even without a display server.
Before running pkg install, check if a previous session or system bundle
already placed Chromium on the device:
which chromium-browser
which chromium
pkg list-installed | grep chromium 2>/dev/nullSome environments already ship with chromium/x11 available. If the binary
exists, skip installation and proceed directly to launch flags.
# Termux
pkg install tur-repo # if not already enabled (community repo)
pkg install chromium # pulls chromium/x11
# Raspberry Pi OS / Debian ARM
sudo apt install chromium-browser
# Alpine
apk add chromiumchromium-browser \
--headless \
--headless=new \
--no-sandbox \
--disable-setuid-sandbox \
--disable-dev-shm-usage \
--disable-gpu \
--disable-software-rasterizer \
--disable-features=VizDisplayCompositor \
--disable-crash-reporter \
--disable-breakpad \
--remote-debugging-port=9222 \
--remote-allow-origins='*' \
--user-data-dir=$HOME/.chromium-termux \
--enable-logging \
--v=0 \
about:blankKey flags:
--no-sandbox+--disable-setuid-sandbox— required in Termux/chroot and many containers--remote-allow-origins='*'— critical; without this, WebSocket CDP rejects connections with 403 Forbidden--disable-dev-shm-usage— saves RAM on devices without /dev/shm--disable-features=VizDisplayCompositor— helps in headless mode on some Android builds--user-data-dir— prevents Chrome from fighting over/corrupting the default profile--disable-crash-reporter+--disable-breakpad— reduce background noise, slight RAM savings
See scripts/chromium-start.sh for a production-ready launcher script.
# HTTP CDP metadata
curl -s http://localhost:9222/json/version
# Open a page via REST
curl -s -X PUT 'http://localhost:9222/json/new?https://example.com'
# Returns JSON with id + webSocketDebuggerUrlimport websocket, json
ws_url = 'ws://localhost:9222/devtools/page/<PAGE_ID>'
ws = websocket.create_connection(ws_url, timeout=10)
# Enable domains
ws.send(json.dumps({'id':1,'method':'Page.enable'}))
ws.send(json.dumps({'id':2,'method':'Runtime.enable'}))
# Wait for Page.loadEventFired
while True:
msg = json.loads(ws.recv())
if msg.get('method') == 'Page.loadEventFired':
break
# Evaluate JS and extract
ws.send(json.dumps({
'id':10,
'method':'Runtime.evaluate',
'params':{'expression':'document.body.innerText'}
}))
resp = json.loads(ws.recv())
value = resp['result']['result']['value']
ws.close()See references/termux-chromium-cdp.md for full reproduction recipe.
Lightpanda provides an aarch64-linux binary, but Termux uses Android's Bionic libc, not glibc. Result:
cannot execute: required file not found
Do not try to patch ELF or force-link glibc. Risk of system corruption on no-root devices. Workarounds (proot-distro, chroot) add 200MB+ and are slow. On glibc systems (Raspberry Pi OS, Debian VPS) Lightpanda works fine.
No ARM wheel available for many constrained platforms. pip install playwright fails:
Could not find a version that satisfies the requirement playwright
Use direct CDP instead.
Chromium spawns multiple child processes. killall chromium often leaves orphans.
Use pkill -9 -f chromium and verify with ps. The launcher script handles
cleanup automatically.
Don't let Chrome starve the system. Maintain a resource guardian loop:
Kill Chrome when:
MemAvailable < 1024 MB— system under memory pressure- Battery < 20% and NOT charging — preserve power
- CPU thermal throttling detected
Start/Resume Chrome when:
MemAvailable > 2048 MB— headroom exists- Charger connected
- Agent explicitly requests JS-rendered page
Implementation via Python daemon:
import subprocess, json
def get_available_mb():
with open('/proc/meminfo') as f:
for line in f:
if line.startswith('MemAvailable'):
return int(line.split()[1]) / 1024
return 0
def should_kill_chrome(bat_pct, bat_status, mem_mb):
if mem_mb < 1024:
return True
if bat_pct < 20 and bat_status not in ('CHARGING', 'FULL'):
return True
return False
def chrome_rss_mb():
r = subprocess.run(
["ps", "aux"], capture_output=True, text=True
)
rss = 0
for line in r.stdout.splitlines():
if 'chromium' in line and 'grep' not in line:
parts = line.split()
if len(parts) > 5:
try:
rss += int(parts[5])
except:
pass
return rss / 1024See scripts/chrome-resource-guard.py for full implementation.
| Constraint | Solution |
|---|---|
| No disk space for Chrome (~400MB) | requests + BeautifulSoup4 for static pages |
| No RAM for Chrome (~900MB runtime) | VPS browser farm; send URLs, receive DOM remotely |
| Need JS but Chrome too heavy | Lightpanda on glibc system (Debian VPS, Raspberry Pi OS) |
| Need stealth/rotation | CDP Network.setUserAgentOverride per page |
references/termux-chromium-cdp.md— Full CDP reproduction recipereferences/termux-chromium-headless.md— Verified session log: flags, CORS 403 fix, resource usage, extraction examplereferences/lightpanda-termux-failure.md— Why glibc binaries fail on Bionicreferences/termux-chromium-dbus-notes.md— D-Bus errors are harmless in headless; do not chase themtemplates/dashboard.html— Standalone monitoring UI (no build step)scripts/chromium-start.sh— Production-ready launcher with auto-cleanupscripts/chrome-resource-guard.py— Auto-throttle modulescripts/web-dashboard.py— stdlib-only Python HTTP dashboard
On constrained devices even a lightweight pip/npm install is painful. A monitoring UI can be built with Python stdlib alone:
Stack: http.server + socketserver + sqlite3 (native FTS5 in Termux)
No dependencies. No pip. No wheel hell. No node.
- Real-time cards: battery %, RAM available, daemon PID, Chromium alive/version
- Live log tail from the awareness daemon
- Episodic memory retrieval from sqlite FTS5
- Remote command execution (sandboxed prefix whitelist)
Constrained ARM host
├─ chrome-resource-guard.py (periodic state logging)
├─ episodes.db (sqlite3 + FTS5)
├─ web-dashboard.py (http.server on :8765)
└─ chromium --headless (on :9222, when needed)
import sqlite3
conn = sqlite3.connect(':memory:')
conn.execute('CREATE VIRTUAL TABLE mem USING fts5(body)')
# Works immediately. No pip install.Full implementation: templates/dashboard.html + scripts/web-dashboard.py