A Road Warrior’s Guide to Verification on the Move
Running a DamageBDD Node on an Android Phone — the Arch Way
A verification node should not need a rack, a permanent office or permission from a cloud provider.
An Android phone already contains the essentials: an ARM processor, storage, radios, a battery and a Linux kernel. Add Termux as the launcher, Arch Linux ARM as the userland and Erlang/OTP as the runtime, and the phone becomes a portable verification machine.
Android kernel
└── Termux
└── PRoot-Distro
└── Arch Linux ARM
└── Erlang/OTP
└── DamageBDD road node
PRoot-Distro provides a rootless, chroot-like Linux environment without replacing Android or requiring a custom kernel. It shares the phone’s kernel and network stack rather than running a conventional virtual machine.
This is not a phone pretending to be a data centre.
It is a verifier that can travel.
---
1. Know the mission
A road node is well suited to:
- API and HTTP behaviour verification
- scheduled functional checks
- proof-producing test runs
- testing services over Wi-Fi or mobile data
- verifying infrastructure while travelling
- acting as an independent node outside the cloud perimeter
- collecting evidence at the point where failure is observed
It is not the ideal device for sustained browser farms, CUDA workloads or high-volume load generation. PRoot intercepts system calls in userspace, while Android controls background execution, thermals and battery policy. Treat the phone as an independent edge verifier—not a planetary load generator.
---
2. Install Termux correctly
Install Termux from F-Droid or the official Termux GitHub release channel. Keep Termux and its add-ons, including Termux:Boot, from the same source because their signing keys must match. Disable Android battery optimisation for Termux.
Open Termux and prepare the host:
pkg update
pkg upgrade -y
pkg install -y \
proot-distro \
git \
curl \
openssh \
tmux
termux-setup-storage
termux-wake-lock
uname -m
The preferred result is:
aarch64
A 64-bit ARM phone gives the cleanest path to Arch Linux ARM and Erlang/OTP.
---
3. Install Arch Linux ARM
Modern PRoot-Distro releases install distributions from OCI container images. Pin the image tag rather than relying on "latest", so that another road node can reproduce the same base system. The Arch Linux ARM image below is among the images recommended by the current PRoot-Distro project; "20260712" is a currently published pinned tag.
From Termux:
proot-distro install \
danhunsaker/archlinuxarm:20260712 \
--name damage-arch
Enter the new system with a fully qualified hostname:
proot-distro login \
--hostname damage-phone.local \
damage-arch
The hostname matters because the current Damage application starts its distributed Erlang node using long names derived from the hostname. PRoot-Distro normally reports "localhost", but it supports overriding the guest hostname.
You are now inside Arch.
---
4. Prepare the Arch environment
Update the system and install the build chain:
pacman -Syu --noconfirm
pacman -S --needed --noconfirm \
base-devel \
git \
erlang \
rebar3 \
openssl \
libsodium \
gmp \
curl \
jq \
tmux
Arch Linux ARM currently provides native AArch64 Erlang packages, while Rebar3 is architecture-independent.
Create an isolated node user and its writable directories:
useradd -m -s /bin/bash damage 2>/dev/null || true
install -d -o damage -g damage /var/lib/damage
install -d -o damage -g damage /var/log/damage
install -d -o damage -g damage /home/damage/.local/share/damage
install -d -o damage -g damage /home/damage/.local/state/damage
Switch users:
su - damage
---
5. Fetch DamageBDD
Clone the source:
git clone --depth 1 \
--branch develop \

GitHub
GitHub - DamageBDD/DamageBDD: Behaviour Verification at Planetary Scale
Behaviour Verification at Planetary Scale. Contribute to DamageBDD/DamageBDD development by creating an account on GitHub.
cd DamageBDD
Check the toolchain:
erl -noshell \
-eval 'io:format("OTP ~s~n", [erlang:system_info(otp_release)]), halt().'
rebar3 version
The public DamageBDD instructions currently describe the source path as cloning the repository and running "rebar3 shell".
---
6. The important mobile reality
The public server build is not yet a clean Android one-liner.
The current repository configuration includes:
- a CPU ECAI NIF using GMP and OpenSSL
- a CUDA ECAI GPU NIF built with "nvcc"
- the graphical ERM application
- ECAI, Nosternity and BoP applications in the default shell
- optional ChromeDriver and IPFS sidecars
- server-oriented paths under "/var/lib/damage" and "/var/log/damage"
- comparatively large worker pools
These assumptions are reasonable for a workstation or permanent node but unnecessarily heavy for an Android road node.
Before presenting Android as a supported target, DamageBDD should include a checked-in "mobile" profile with the following contract:
Architecture: AArch64
Runtime: CPU only
Primary app: damage
Listen address: 127.0.0.1
HTTP port: 4888
GPU NIF: disabled
ERM GUI: disabled
ChromeDriver: disabled
IPFS sidecar: disabled by default
SSH listeners: disabled by default
Worker pools: reduced
Data directory: ~/.local/share/damage
Log directory: ~/.local/state/damage
Secrets: private guest storage only
The Damage OTP application itself only declares its core server dependencies and Vanillae; the additional applications are being started by the top-level development shell configuration rather than being mandatory Damage application dependencies.
---
7. Create the road configuration
Begin from the supplied sample:
cp config/sys.config.sample config/mobile.config
mkdir -p \
"$HOME/.local/share/damage" \
"$HOME/.local/state/damage"
Edit "config/mobile.config" and apply these changes.
Node paths
{feature_dirs, ["/home/damage/DamageBDD/features/"]},
{run_user, "damage"},
{data_dir, "/home/damage/.local/share/damage/"},
Change logger paths from:
/var/log/damage/
to:
/home/damage/.local/state/damage/
Network binding
Keep the node private by default:
{ip, {127, 0, 0, 1}},
{port, 4888},
Disable sidecars
Replace the configured "abduco_workers" list with:
{abduco_workers, []},
Disable embedded SSH listeners
{ssh_git, [
{enabled, false},
{listen_addr, {127, 0, 0, 1}},
{port, 2222},
{system_dir, "/home/damage/.local/share/damage/ssh/git/system"},
{user_dir, "/home/damage/.local/share/damage/ssh/git/user"},
{repos_root, "/home/damage/.local/share/damage/git"}
]},
{ssh_tunnel, [
{enabled, false},
{ip, {127, 0, 0, 1}},
{port, 2223},
{system_dir, "/home/damage/.local/share/damage/ssh/tunnel/system"},
{user_dir, "/home/damage/.local/share/damage/ssh/tunnel/user"}
]},
Reduce mobile worker pools
Start conservatively:
{pools, [
{damage, [{size, 2}, {max_overflow, 4}], []},
{damage_ai, [{size, 1}, {max_overflow, 1}], []},
{formatter, [{size, 1}, {max_overflow, 4}], []}
]},
Increase these only after observing memory use and temperature on the actual phone.
---
8. Create the CPU-only build profile
The current top-level build attempts to compile the Linux CUDA NIF because its port specification matches Linux generally. For an Android phone without CUDA, copy the build configuration and remove the "apps/ecai/priv/ecai_gpu.so" port specification from the mobile copy.
cp rebar.config rebar.mobile.config
In "rebar.mobile.config":
1. Remove the port specification whose output is:
apps/ecai/priv/ecai_gpu.so
2. Keep the CPU NIF:
apps/ecai/priv/ecai.so
3. Change the shell application list so it does not automatically start the graphical and auxiliary applications:
{shell, [
{config, "config/mobile.config"},
{apps, [damage]}
]}.
Use the alternate configuration through "REBAR_CONFIG", which is an officially supported Rebar3 environment setting.
Compile:
REBAR_CONFIG=rebar.mobile.config rebar3 compile
This is the engineering checkpoint. Any remaining failure here identifies another server-only dependency that should be moved behind the mobile profile rather than worked around independently on every phone.
---
9. Start the node in the foreground
For the first run:
REBAR_CONFIG=rebar.mobile.config \
rebar3 shell \
--apps damage \
--config config/mobile.config
Rebar3 supports explicitly selecting applications with "--apps" and loading a system configuration with "--config". Its interactive shell is intended primarily for development and diagnosis.
From another Termux session, test the node:
curl -fsS
http://127.0.0.1:4888/steps.json | head
curl -fsS
http://127.0.0.1:4888/metrics | head
The current Damage application binds its Cowboy HTTP listener to the configured IP and port; the sample defaults are loopback and port "4888".
At this point, the phone is verifying.
---
10. Run it without an interactive shell
Create "/home/damage/bin/damage-road-node" inside Arch:
mkdir -p "$HOME/bin"
cat > "$HOME/bin/damage-road-node" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
cd "$HOME/DamageBDD"
mkdir -p \
"$HOME/.local/share/damage" \
"$HOME/.local/state/damage"
export REBAR_CONFIG=rebar.mobile.config
exec erl \
-noshell \
-pa _build/default/lib/*/ebin \
-config config/mobile \
-eval '
case application:ensure_all_started(damage) of
{ok, Apps} ->
io:format("Damage road node started: ~p~n", [Apps]),
timer:sleep(infinity);
Error ->
io:format("Damage road node failed: ~p~n", [Error]),
halt(1)
end.
'
EOF
chmod 700 "$HOME/bin/damage-road-node"
Exit Arch:
exit
Create the Termux-side launcher:
mkdir -p "$HOME/bin"
cat > "$HOME/bin/start-damage-road-node" <<'EOF'
#!/data/data/com.termux/files/usr/bin/bash
set -euo pipefail
termux-wake-lock
mkdir -p "$HOME/var/log"
proot-distro login \
--detach \
--hostname damage-phone.local \
--user damage \
damage-arch -- \
bash -lc '
exec "$HOME/bin/damage-road-node" \
>>"$HOME/.local/state/damage/node.log" 2>&1
'
EOF
chmod 700 "$HOME/bin/start-damage-road-node"
Start it:
"$HOME/bin/start-damage-road-node"
Recent PRoot-Distro versions support detached sessions, session inspection and reliable termination of the guest process tree. Detached output is discarded unless the command performs its own redirection, which is why the launcher writes to the node log explicitly.
---
11. Operate the road node
Check the PRoot session
proot-distro ps
Check the HTTP service
curl -fsS
http://127.0.0.1:4888/steps.json >/dev/null \
&& echo "Damage node: ready"
Read logs
proot-distro login \
--hostname damage-phone.local \
--user damage \
damage-arch -- \
tail -f "$HOME/.local/state/damage/node.log"
Stop the node
proot-distro kill damage-arch
termux-wake-unlock
"proot-distro kill damage-arch" terminates the complete tracked guest process tree rather than merely killing the visible parent process.
---
12. Start after Android reboots
Install Termux:Boot from the same source as Termux. Launch the Termux:Boot application once, then create:
mkdir -p "$HOME/.termux/boot"
cat > "$HOME/.termux/boot/10-damage-road-node" <<'EOF'
#!/data/data/com.termux/files/usr/bin/bash
sleep 30
exec "$HOME/bin/start-damage-road-node"
EOF
chmod 700 "$HOME/.termux/boot/10-damage-road-node"
Termux:Boot executes scripts placed in "~/.termux/boot/" in sorted order. Its documentation recommends taking a wake lock for services that must continue while the device sleeps.
---
13. Keep the node private
The safest mobile default is:
{ip, {127, 0, 0, 1}}
This makes the Damage API available to applications on the phone without advertising it to every device on the current Wi-Fi network.
To expose it deliberately over a trusted LAN:
{ip, {0, 0, 0, 0}}
Then determine the phone’s address:
ip addr show wlan0
Because port "4888" is above the privileged range, PRoot does not need port redirection. PRoot-Distro only redirects ports in the privileged "1–1023" range when explicitly requested.
Never store wallet material, Nostr keys, cookies or node secrets under "/sdcard" or shared Android storage. Keep them inside the Arch guest home with restrictive permissions:
chmod 700 "$HOME/.local/share/damage"
find "$HOME/.local/share/damage" -type f -exec chmod 600 {} \;
DamageBDD’s public documentation provides an encrypted secrets store through "secrets:encrypt_store/2"; integrations should be configured there rather than left in scripts or shell history.
---
14. Android is not a guaranteed server scheduler
A wake lock, unrestricted battery setting and Termux:Boot greatly improve survivability, but recent Android releases and some manufacturer firmware can still terminate long-running Termux processes in the background. Reports continue to exist on Android 15 and Android 16 even with normal mitigations enabled.
Therefore:
- treat mobile verification as reconnectable
- make jobs idempotent
- preserve unfinished work on disk
- resume schedules after restart
- expose a clear health endpoint
- never assume one phone has permanent availability
- let several independent road nodes provide redundancy
The phone may disappear.
The proof should not.
---
The road-node doctrine
A cloud node verifies from inside the infrastructure.
A road node verifies from outside it.
It crosses networks. It changes providers. It loses signal. It returns. It observes software from the same unstable edge where real people experience it.
That is not a weakness.
That is an independent verification position.
The road warrior does not carry the system.
The road warrior carries the proof.
#DAMAGE #DamageBDD #Erlang #ArchLinux #Termux #Android #EdgeComputing #SoftwareTesting #Verification #OpenSource #DistributedSystems