Running a full scale server side application on an Android device

Notes from building R.O.B.O. Host: a server packed into one file, which runs in Termux on an Android phone and gives offline maps, Wikipedia and notes to a whole group over Wi-Fi.

Where I live, the power and the mobile network sometimes go away for many hours. I wanted something which keeps working at that time for a group of people, for example a family or neighbours. They need a map with their own position, some reference to read, and a place to leave notes for each other. So I made R.O.B.O. Host. R.O.B.O. means "Ready Offline, Before Outage".

The idea is simple. One Android phone is the server. Other people connect to its Wi-Fi hotspot, or to the same Wi-Fi network, and open it in a browser. They do not need to install anything. The host phone does not need internet or a SIM card after the setup.

When I started, I was not sure that this is possible at all. A web server with a database, maps and search, on a phone, without root, started from a terminal app. It turned out that it is possible. Below are the parts which I found the most interesting.

The look

From the beginning I wanted it to look like a terminal from the Fallout games. Green text on a black screen, a monospace font, a soft glow around the letters, scanlines, square buttons with thin borders. Even the map is tinted green, and the pins are amber. The texts in the interface are short, like messages of an old computer: "No map data in memory", "Map transfer in progress".

This was not only for fun. The app is for a stressful situation, and I wanted it to feel calm and a little bit playful, not like one more office tool. And a dark screen also uses less battery on many phones.

One file, with Node.js inside

I wanted the install to be one file which you download and run. Node.js can do this with a feature called single executable applications. All the code, pages, fonts, icons and map styles go inside a copy of the node binary. The result is one file of about 99 MB.

The web part is React Router 7 with React 19, and the database is node:sqlite, the SQLite which is built into Node, with full-text search. Because it is built in, I did not need any native addon, and this is what made the single file possible.

To run on Android, the file uses a fully static build of Node, which does not depend on any system library.

What Android did not like

The most unexpected problem: Termux from Google Play cannot run a static program. Termux from F-Droid runs it without problems, but the Play version starts programs through Android's own loader, which refuses them. I did not want to tell people "install Termux only from F-Droid". So I wrote a small launcher in C, which loads the real program into memory and starts it. For Android it is a normal program, and now both versions of Termux work.

The app sandbox blocks some system calls. Android kills the process when it calls, for example, io_uring. The launcher catches this and answers "not supported", and then Node simply uses the older way.

There is no /etc/resolv.conf. The program does not know which DNS server to use, so all downloads failed. The server now has its own DNS lookup: it reads the DNS servers from Termux's config, and if there is no config, it uses public resolvers.

An app cannot list the network interfaces. But the server must show the address where other phones can reach it. I used two ways. The first: open a UDP socket "towards" some address and read which local address the system chose. Nothing is sent. The second: a small helper, started by the launcher, which asks Android itself and passes the answer back. The hotspot address on new Android versions is random, so the server checks the addresses again every few seconds and prints the new one.

Kiwix's reader (more about it below) had the same problem, but worse: it asks for the list of interfaces at every start, Android refuses, and then it crashes. The launcher answers this request itself, with a list where there is only lo, and it starts normally.

Android stops background apps. When the screen is locked, after some minutes the phone goes into deep sleep, and the server stops answering. termux-wake-lock together with the battery optimisation exemption solves this. I tested it with the screen off and the tablet forced into deep sleep for 10 minutes, and all requests from another device got an answer.

HTTPS without internet

Browsers give the GPS position to a page only over HTTPS. Without internet there is no Let's Encrypt, so the server creates its own small certificate authority on the first start and signs its own certificate. I used @peculiar/x509 for this.

Other phones can accept the browser warning one time, and GPS works after that. Or they can install the host's certificate, and then there are no warnings at all. The second way gives more than I expected: Chrome allows the offline mode and the "Install app" button only when the certificate is trusted.

Maps

The map is MapLibre GL JS. The map data comes from VersaTiles, which gives one big file (about 66 GB) with the map of the whole planet. The good thing is that you can download only a part of this file. The admin draws an area, and the server downloads only the pieces which this area needs. A city with full details is a few megabytes, and a small country with streets is about 30 MB.

My first version was very slow on the tablet, 1 to 2 frames per second. After I changed how the downloaded areas are given to MapLibre, the same map runs at 40 to 43 frames per second.

GPS has its own problem. In some regions the GPS signal is jammed or spoofed, and one test tablet "jumped" more than 12,000 km away. The app ignores such positions, and a person can always set the position by hand with the place search.

Wikipedia

The library uses Kiwix files, which contain the whole of Wikipedia or a part of it, for example only medicine, in more than 300 languages. Kiwix's own reader, kiwix-serve, travels inside the same file and runs as a separate process. The main server checks who is signed in and shows every article in the same green terminal style.

On the tablet, an article opens in 26 to 52 ms over Wi-Fi, and a full-text search takes less than half a second.

Notes

Notes can be shared with the whole group or private. Private notes are encrypted with the owner's password, so even the admin cannot read them. A phone which trusts the host's certificate also keeps the last map area and its owner's private notes, so a person can still see them when the host is out of reach.

How it works in practice

I tested most things on an old 12 inch tablet as the host. Five browsers at the same time for 3 minutes (map, search, articles, notes) used about 9% of the CPU and about 145 MB of memory. With the screen on the whole time, the battery went down by about 6 to 11% per hour.

How to use it

You need one Android phone for the host. An old phone is fine.

  1. Install Termux from Google Play or F-Droid and open it.
  2. Paste this line and press Enter:
curl -fsSL https://robo.dbashkatov.com/install.sh | sh
  1. Android asks two questions. Allow file access for Termux (it is needed to restore a backup from Downloads), and allow it to run in the background.
  2. Termux prints an address, for example https://192.168.43.1:8443, and a QR code. Open it on any phone in the same Wi-Fi network. If there is no router, turn on the hotspot on the host phone and let the others connect to it.
  3. Enter the setup code from Termux. You become the admin, and you create accounts for the other people.
  4. While there is still internet, download the map of your area and the Wikipedia books which you need. After that, everything works without internet.

To start it again later, type robo in Termux. To update, stop it with Ctrl+C and paste the same install line again. The accounts, maps and notes stay.

The interface is in English, Russian and Spanish. More details and answers to common questions are on the R.O.B.O. home page. I hope that nobody needs it, but if the power goes out, I will be happy if it helps someone.