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:
serveto expose the static dashboard over HTTP;sshdso I can manage the phone remotely;rsyncto deploy new builds;termux-servicesto 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:
opensshprovides the SSH server;- Node.js and
npmprovide the runtime needed byserve; rsyncreceives dashboard deployments;termux-servicesprovidesrunit-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:
-arecursively synchronizes the directory;-zcompresses the transfer;--deleteremoves 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 nesteddistdirectory.
--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.