This is what the panel was built on — a OnePlus 6 in a drawer. It runs well here, but the
installer does not: Android has no service manager, apt is Termux's own, and
several dependencies have no Android build at all.
npx bitpanel install will refuse to run here, on purpose.
It is apt- and systemd-shaped. The steps below are the equivalent, and they are not long.
pkg install nodejs git nginx openssh netcat-openbsd garage rclone npm install -g pm2
Garage comes from pkg rather than the upstream release: Termux packages are
compiled for bionic, and the official binary is glibc-linked and will not start.
Replays the pm2 dump after a reboot. Without it nothing comes back — there is no systemd here to do it.
Rewrites #!/usr/bin/env shebangs, which the helper scripts rely on. Termux has no /usr/bin.
Clone at a release tag rather than a branch, so the panel knows which version it is and can update itself later.
git clone --depth 1 --branch v0.1.7 \ https://github.com/yashthakur1/bitroot-panel.git ~/apps/bitroot-panel cd ~/apps/bitroot-panel npm install --include=dev npm run build
--include=dev matters: the build needs TypeScript, and npm drops dev
dependencies whenever NODE_ENV=production is set.
Then the helper scripts, and a minimal .env:
mkdir -p ~/bin for f in server-scripts/*; do head -c 2 "$f" | grep -q '^#!' && install -m 755 "$f" ~/bin/ done printf 'PORT=3210\nSESSION_SECRET=%s\nDASHBOARD_PASSWORD=%s\n' \ "$(openssl rand -hex 32)" "$(openssl rand -base64 18 | tr -dc 'A-Za-z0-9' | cut -c1-20)" \ > ~/apps/bitroot-panel/.env chmod 600 ~/apps/bitroot-panel/.env
Only scripts are copied — the loop checks for a shebang, so data files like
ports.conf stay put. Overwriting that one would wipe the machine's port
registry.
PORT=3210 pm2 start npm --name bitroot-panel --cwd ~/apps/bitroot-panel -- start pm2 start garage --name garage -- server pm2 save
PORT has to be in the environment, not just .env.
next start reads the port from the process environment; a
PORT line in a dotenv file alone leaves the panel on 3000 while everything
else expects 3210.
pm2 save matters more here than on Linux. With no systemd,
Termux:Boot replays that saved dump and nothing else will.
Open http://localhost:3210 on the phone, or reach it from another device over
Tailscale. The setup wizard takes it from there.
| Thing | What actually happens |
|---|---|
| /dev/tcp | Fails silently under sh, which is dash. Anything probing a port must use nc -z. |
| ss, netstat | Return nothing — Android denies netlink to apps. Listening sockets cannot be enumerated; connect to them instead. |
| node-gyp | Native modules need GYP_DEFINES="android_ndk_path=" or it will not configure. |
| docker | The package exists but ships no daemon. |
| ports < 1024 | Unavailable to a non-root process. |
| glibc binaries | Fail with cannot execute: required file not found — the ELF interpreter is missing, not the file. Bun, turbo and Claude Code past 2.1.112 are all in this category. |
| tailscale | Runs as the Android app, so there is no CLI in Termux. The panel detects the tailnet from the interface address instead, and tailscale serve cannot run here. |
Android is aggressive about background processes. Acquire a wakelock and exclude Termux from battery optimisation, or the device drops off the network while it sleeps — the services stay running, but nothing can reach them.
termux-wake-lock
A phone is a real server with real limits. Its upload link is the ceiling on everything public, which is why storage leans on edge caching and compresses before it sends. None of that is hidden in the UI: a service bound to loopback says so rather than offering a link that will not open.
Because the clone is at a tag, Config → Setup knows which release is running and offers the newer one when there is one — it fetches, builds and restarts by itself. A build takes several minutes on a phone, so it runs detached and streams its log.