build

Android WLED Dashboard Server

Part of Home Lighting System

This is the small local server that keeps my WLED dashboard available when my development computer is turned off.

The dashboard started as an Astro project running on my Ubuntu computer. That was fine while developing it, but not as a permanent interface for the lights: turning off the computer also turned off the dashboard.

I already had an unused Redmi Note 12 Pro Android phone, so instead of buying another computer just to serve a small static site, I repurposed the phone.

The phone does not contain the Astro project or build it. Development stays on Ubuntu. The phone only stores the finished production files and serves them over the home network.

How it works

                         HOME NETWORK

WLED devices
ESP8266 + LED installations

        │ WLED HTTP API

Browser running dashboard

        │ HTTP :4321

Redmi Note 12 Pro
Android + Termux
├── serve
├── sshd
└── ~/lights-dashboard/

        │ rsync over SSH :8022

Ubuntu workstation
Astro source
└── pnpm deploy

The responsibilities are deliberately simple:

Ubuntu is the development environment. I edit, test, and build the dashboard there.

The Android phone is the server. It stores only the production build and exposes it to the LAN.

The browser runs the dashboard itself and talks directly to the WLED devices.

The WLED controllers still own the lights.

That last part matters. The phone is not a central lighting controller. If it stops serving the dashboard, the WLED devices remain available through their own interfaces.

Why use an Android phone?

The workload is tiny: the dashboard is a static site.

The Redmi was already unused, has Wi-Fi, consumes little power, has its own battery, and is much more capable than necessary for serving HTML, CSS, and JavaScript.

Using it avoided adding a Raspberry Pi, mini PC, or another dedicated machine simply to keep the dashboard online.

Termux provides the Unix-like environment needed to turn the phone into something useful for this job without replacing Android.

Inside Termux I only need a few pieces:

  • serve to expose the static dashboard over HTTP;
  • sshd so I can manage the phone remotely;
  • rsync to deploy new builds;
  • termux-services to supervise the long-running processes.

There is no application server, database, container runtime, or other infrastructure because the dashboard does not need any of them.

The phone only serves the finished build

Trying to build the Astro project directly on Android turned out to be unnecessary and problematic.

The Astro build depends on tooling that did not behave reliably in the Termux environment on this phone. More importantly, there is no real reason to move the development toolchain onto the server.

The simpler split is:

Ubuntu
Astro source

    │ pnpm build

  dist/

    │ rsync

Android
~/lights-dashboard/

    │ serve

Browser

This keeps the phone disposable as infrastructure. If I ever replace it, all I need to recreate is the small serving environment and redeploy the current dist/.

Server setup

Prepare Termux

Update the package information and installed packages first:

pkg update
pkg upgrade

Then install the tools used by the server:

pkg install openssh nodejs-lts npm rsync termux-services

Their roles are:

  • openssh provides the SSH server;
  • Node.js and npm provide the runtime needed by serve;
  • rsync receives dashboard deployments;
  • termux-services provides runit-based service supervision.

After installing termux-services, restart the Termux shell so its service environment is initialized. The official Termux service package uses sv-enable to enable services and runit to supervise them.

Install the static HTTP server:

npm install -g serve

The dashboard itself will live at:

~/lights-dashboard/

Create that directory:

mkdir -p ~/lights-dashboard

Give the phone a stable network address

The dashboard URL, SSH connection, and deployment script all need to know where the phone is.

I reserve the phone’s LAN address in the router rather than relying on whatever DHCP address it happens to receive.

I use SERVER_IP throughout this document for that address.

The two services are then reachable at:

Dashboard: http://SERVER_IP:4321
SSH:       SERVER_IP:8022

Remote access with SSH

Create a password for the Termux user:

passwd

Enable the SSH server:

sv-enable sshd

Check it:

sv status sshd

A running service begins with something like:

run: sshd:

Termux uses port 8022 for SSH, so from Ubuntu I connect with:

ssh -p 8022 SERVER_IP

Once this works, almost everything on the phone can be managed from a normal Ubuntu terminal instead of typing commands on the touchscreen.

Run the dashboard as a service

The dashboard server also runs under termux-services.

Create its service directory:

mkdir -p $PREFIX/var/service/dashboard

Then create:

$PREFIX/var/service/dashboard/run

with:

#!/data/data/com.termux/files/usr/bin/sh

exec serve "$HOME/lights-dashboard" -l 4321

serve exposes the production directory on port 4321.

Using exec replaces the temporary shell with the actual server process so runit supervises serve directly.

Make the script executable:

chmod +x $PREFIX/var/service/dashboard/run

Then enable and check it:

sv-enable dashboard
sv status dashboard

At this point, once files exist in ~/lights-dashboard, the site is available at:

http://SERVER_IP:4321

Deploy from Ubuntu

The normal deployment flow stays on the development computer.

First build the Astro project:

pnpm build

Astro writes the production site to:

dist/

Then synchronize that directory to the phone:

rsync -az --delete \
  -e "ssh -p 8022" \
  dist/ \
  SERVER_IP:~/lights-dashboard/

The important parts are:

  • -a recursively synchronizes the directory;
  • -z compresses the transfer;
  • --delete removes files from the phone that no longer exist in the new build;
  • -e "ssh -p 8022" sends the transfer through Termux’s SSH server;
  • the trailing slash in dist/ copies its contents rather than creating a nested dist directory.

--delete is particularly useful for Astro because generated assets use hashed filenames. Without it, files from old builds would accumulate on the phone.

The running HTTP server does not need to restart after deployment. It simply serves the new files.

One-command deployment

Because building and synchronizing always happen together, I wrapped them in:

scripts/deploy.sh
#!/usr/bin/env bash

set -e

pnpm build

rsync \
  -az \
  --delete \
  -e "ssh -p 8022" \
  dist/ \
  SERVER_IP:~/lights-dashboard/

echo "Dashboard deployed."

set -e is important here: if the Astro build fails, the script stops instead of continuing into deployment.

The project exposes that script through:

{
  "scripts": {
    "deploy": "./scripts/deploy.sh"
  }
}

So normal deployment becomes:

pnpm deploy

which gives me:

source

Astro build

dist/

rsync over SSH

Android server

Keeping the server alive

This turned out to be the awkward part of using an Android phone as infrastructure.

I configured Termux with:

Battery usage: No restrictions
Background autostart: enabled
Wake lock: acquired

and acquire the Termux wake lock with:

termux-wake-lock

A wake lock tells Android that Termux has work that should continue while the screen is off. It does not keep the display on.

On this particular Xiaomi phone, those settings still were not enough if Termux was left in the background.

What has actually proven reliable is:

Leave Termux as the foreground app before locking the phone.

The screen can turn off normally. As long as Termux was the displayed app when the phone was locked, the dashboard has continued running reliably.

If I use another app on the phone, I return to Termux before locking it again.

This is the main compromise in the current build.

runit can restart sshd or serve if either supervised process exits, but it cannot help if Android kills the entire Termux environment containing the supervisor.

After a reboot

The current setup does not automatically recreate the whole server environment immediately after Android boots.

The phone needs to be unlocked and Termux started again. Once Termux is running, its enabled services can be supervised normally.

Termux does have a separate Termux:Boot add-on for running scripts at Android startup, but I am not using it in this build.

That distinction matters because simply creating files under:

~/.termux/boot/

does nothing by itself; those scripts are executed by the Termux:Boot application.

Normal operation

There are only a few things I normally need.

Open the dashboard:

http://SERVER_IP:4321

Connect to the phone:

ssh -p 8022 SERVER_IP

Deploy a new dashboard:

pnpm deploy

Check the dashboard server:

sv status dashboard

Check SSH:

sv status sshd

Restart the dashboard process if it actually needs restarting:

sv restart dashboard

Deploying new static files does not normally require that restart.

Result

The Redmi now gives the lighting system a small independent dashboard host without moving development away from Ubuntu or making the dashboard responsible for the lights themselves.

The architecture remains:

WLED devices

      │ API

Browser dashboard

      │ HTTP

Android + Termux

      │ deployment

Ubuntu

It is not a perfect home server. Android’s process management is still an operational constraint, and the phone needs Termux left in the foreground before it is locked.

But for this particular job, that tradeoff has been acceptable: I reused hardware I already had, kept the deployment flow simple, and made the dashboard independent from my main computer.