Droidian compatible phone as a home server
Droidian is just Debian with a phone kernel. That makes a $40 Pixel 3a or Redmi Note 9s in my case a perfect 2-4W ARM64 server — until Phosh, ModemManager, and Android-style deep sleep keep putting it to bed. This is the minimal setup I use to turn Droidian into a reliable, always-on box you can SSH into .
Heads-up: Do this over SSH with a static IP or USB-ethernet first. Once Phosh is masked, the screen goes permanently black.
1) Droidian suspend
Traditional suspend is broken on Android kernels. As a result, Droidian was using batman which was deprecated in favor of Mobile Power Saver and is enabled by default.
Traditional suspend wakes up the device every now and then without actually suspending. This behaviour can be disabled with this schema override file.
Disabling it won’t do any harm to the system as batman is the main daemon in charge of battery management on Droidian.
The override file schema:
[org.gnome.settings-daemon.plugins.power] sleep-inactive-ac-type = ‘nothing’ sleep-inactive-battery-type = ‘nothing’
- Overrides for (MPS)
Mobile Power Saver (MPS) uses GLib schemas and overrides to manage power-saving behaviors
https://github.com/droidian/mobile-power-saver/blob/droidian/Overrides.md
With override configurations we can prevent suspension of services and exclusion from power saving:
1. rename to proper name that loads LAST
sudo mv /usr/share/glib-2.0/schemas/90-org.adishatz.Mps.homelab.override /usr/share/glib-2.0/schemas/99-homelab.gschema.override
2. make sure it contains all 5 keys
sudo tee /usr/share/glib-2.0/schemas/99-homelab.gschema.override > /dev/null «‘EOF’ [org.adishatz.Mps] suspend-services=false suspend-processes=[] screen-off-power-saving=false radio-power-saving=false bluetooth-power-saving=false EOF
3. recompile
sudo glib-compile-schemas /usr/share/glib-2.0/schemas/
4. reset to pick up new defaults
gsettings reset-recursively org.adishatz.Mps
Verify:
| gsettings list-recursively org.adishatz.Mps | grep -E ‘suspend-services | suspend-processes | screen-off’ |
Reset and re verify: gsettings reset-recursively org.adishatz.Mps gsettings get org.adishatz.Mps suspend-services
Final check: systemctl –user restart mobile-power-saver journalctl –user -u mobile-power-saver -f
2) Strip phone hardware you don’t need
1
2
3
4
5
# Disable cellular modem
sudo systemctl disable --now ModemManager
# Disable the telephony stack
sudo systemctl disable --now ofono
3) Memory tuning
1
2
3
4
5
# check current swappiness
cat /proc/sys/vm/swappiness
# set to 10
sudo sh -c 'echo "vm.swappiness=10" >> /etc/sysctl.conf'
sudo sysctl -p
4) Murder all sleep targets
1
sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target
Edit logind:
1
sudo nano /etc/systemd/logind.conf
Set:
1
2
3
4
HandleLidSwitch=ignore
HandleLidSwitchExternalPower=ignore
HandleLidSwitchDocked=ignore
IdleAction=ignore
Then:
1
sudo systemctl restart systemd-logind
5) why not android kernel wakelock
Android wakelocks are broken on droidian it uses batman or mobile powersaver
6) systemd-inhibit and delayed phosh disabling after boot.
instead of Kernel-level mechanism (Android legacy) wakelocks we can use a more refined Userspace policy mechanism Also a failsafe to boot with phosh enabled and stop it after 120safe is used instead of masking it as i dont have a working ssh over usb and wifi may fail.
Force network manager ro never enable powersave mode on wifi
1
2
3
4
5
sudo tee /etc/NetworkManager/conf.d/99-wifi-powersave.conf >/dev/null <<'EOF'
[connection]
wifi.powersave=2
EOF
sudo systemctl restart NetworkManager
sudo nano /etc/systemd/system/server-mode.service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Unit]
Description=Server Mode
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/systemd-inhibit \
--what=sleep:idle \
--mode=block \
--why="server mode active" \
/usr/local/bin/server-mode.sh
Restart=always
RestartSec=2
[Install]
WantedBy=multi-user.target
sudo nano /usr/local/bin/server-mode.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/sh
PATH=/usr/sbin:/usr/bin:/sbin:/bin
sleep 10
/usr/sbin/iw dev wlan0 set power_save off 2>/dev/null || true
echo on > /sys/class/net/wlan0/device/power/control 2>/dev/null || true
STABLE=0
for i in $(seq 1 60); do
ping -c1 1.1.1.1 >/dev/null 2>&1 && STABLE=$((STABLE+1)) || STABLE=0
[ "$STABLE" -ge 15 ] && break
sleep 2
done
sleep 120
systemctl stop phosh.service || true
echo 0 > /sys/class/backlight/panel0-backlight/brightness 2>/dev/null || true
wifi.powersave=2 → NM never re-enables powersave power/control = on → kernel can’t runtime-suspend
verify inhibit
1
systemd-inhibit --list
7) Clean up the last power bits
Even headless, gsettings can still fire:
1
2
3
gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-type 'nothing'
gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type 'nothing'
gsettings set org.gnome.desktop.session idle-delay 0
8) The “Hard Limit” (Systemd Cgroups)
We can tell the Linux kernel to physically prevent apps e.g. Jellyfin from taking more than a certain amount of RAM. If it tries to cross the line, the kernel will force it to clean up.
Run:
1
sudo systemctl edit jellyfin
paste this in
1
2
3
4
5
6
7
[Service]
# Limit Jellyfin to 600MB of RAM
MemoryMax=600M
MemoryHigh=500M
# Lower its priority so it doesn't lag the phone's UI
CPUWeight=50
IOWeight=50
9) An oneliner verifier
run this to verify all changes preserved
1
2
3
4
systemctl list-unit-files | grep -E "sleep|suspend|hibernate" && \
loginctl show-logind -p IdleAction -p HandleLidSwitch && \
systemd-inhibit --list && \
sudo iw dev wlan0 get power_save
10) WiFi
You need a static lan ip : set that on you router admin page . In addition to that you may need (i did) to hardcode the lan ip to the network manager config eg if a wifi extender in nat mod used that may switch the ip .
run this as root with your ssid and the lan ip from your router
sudo -i
1
2
3
4
5
6
7
8
9
10
11
12
13
nmcli connection modify "vippo_EXT" \
ipv4.method manual \
ipv4.addresses 192.168.1.198/24 \
ipv4.gateway 192.168.1.1 \
ipv4.dns "1.1.1.1,8.8.8.8"
nmcli connection modify "vippo_EXT" \
802-11-wireless.cloned-mac-address permanent
nmcli connection down "vippo_EXT"
nmcli connection up "vippo_EXT"
exit
11) Disable USB Autosuspend
I got a hard drive constanly pluged in the ohone through an otg/pd split cable . Android kernel aggressively tries to save power on external USB devices
Get your Drive’s IDs
1
lsusb
eg
Bus 001 Device 002: ID 0480:0900 Toshiba America Inc MQ04UBF100
idVendor: 0480 idProduct: 0900
Create the Udev Rule
1
sudo nano /etc/udev/rules.d/99-toshiba-autosuspend.rules
add
1
ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="0480", ATTR{idProduct}=="0900", ATTR{power/control}="on"
What this rule means:
- ACTION==”add”: Trigger this only when a device is plugged in.
- ATTR{idVendor}==”0480” and ATTR{idProduct}==”0900”: Ensure this rule only applies to your specific Toshiba drive, leaving other USB devices alone.
- ATTR{power/control}=”on”: This is the elegant built-in Udev way of doing your echo “on” command. It dynamically finds the correct device path and forces the power control to stay awake.
Reload Udev to Apply
1
2
3
sudo udevadm control --reload-rules
sudo udevadm trigger
cat /sys/bus/usb/devices/*/power/control
12) force cpu policy
Prevent kernel to default to powersave after reboot and force schedutil at boot.
sudo nano /etc/systemd/system/cpufreq-schedutil.service
1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=Force CPU governor to schedutil
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c 'for p in /sys/devices/system/cpu/cpufreq/policy*/scaling_governor; do echo schedutil > "$p"; done'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
enable it
sudo systemctl daemon-reload sudo systemctl enable cpufreq-schedutil.service
Result
upower gives us the power state of our server and battery related stats . ps filtered out gives us the ram used by the top 12 ram consumption process.
1
2
3
upower -i $(upower -e | grep 'battery')
ps -eo size,pid,user,command --sort -size | awk '{ hr=$1/1024 ; printf("%13.2f Mb ",hr) } { for ( x=4 ; x<=NF ; x++ ) { printf("%s ",$x) } print "" }' | head -n 12
My curtana now idles at: • 0,3W , 900MB RAM used • 67 days uptime (before I updated) • Running tailscale , vaultwarden, jellyfin and a amall go tsnet service .
Halium/Android layer stuff like Netmgrd,Qcrild and Cnd/Qseecomd occupy like 200mb of ram and cant be disabled without risking a brick .