A couple of weeks ago I stumbled upton textbee on reddit which brought back something I always wanted to do:
- have an Text/SMS Gateway with a throw-away phone number for signups and 2FA for trash accounts
- Text/SMS Alerts in case my home network is down or offline
Textbee was quite new when writing this and there are other projects out there like httpsms or sms-gate that felt better suited.
Text/SMS Gateway
I went with httpsms as textbee doesn’t support outgoing webhooks - yet.
Install the app, configure a webhook target (I am using n8n) which then sends me a message (telegram, signal, whatsapp, whatever) on my phone with the content of the text message.
Easy and straight forward and all self-hosted!
Monitoring your homeserver and internet connection
Prometheus + Alertmanager seemed like overkill to monitor my little homeserver(it’s thinclient with 32GB Memory and 2TB SSD), so I went with Beszel and uptime-kuma.
Also running monitoring on the node itself has it’s constraints (you can’t detect an outage of your own server), but it has to be done. Well knowing the limitations.
So I reviewed my Scope for Monitoring:
- Monitoring basic health metrics of homeserver (done with Beszel)
- Monitoring the containers and some web endpoints running on my homeserver (done with uptime-kuma)
- Monitor if my router is reachable (e.g. detect power or network outage)
- Monitor if my internet-connection is working (e.g. power outage, network outage or ISP Outage)
- Monitor recurring jobs (cron) like Backups (done with healthchecks.io)
Why detecting a network outage can make sense
While a non-running backup can be an indicator, that your network is down, it’s not proof but only an indicator. I wanted to properly monitor my network for a larger outage. When we are traveling and the power is gone for hours, I maybe want to call a neighbor to check on things. This might be even more relevant for other parts of the world where power outages are more common.
So the device to monitor power-outages should be able to keep running (battery!) and needs to be able to reach me on a different communication channel.
Setting up Android
I picked up my old Xiaomi Mi A1, flashed a new ROM on it, without any Bloatware and installed the f-droid App-Store.
I did not root my phone - at first(!), as it’s not necessary for the monitoring part.
After installing f-droid I installed termux, termux-api and termux-boot.
For easier management I set up sshd follwing the remote access docs of termux.
Make sure to start sshd on every boot like described at https://wiki.termux.com/wiki/Termux:Boot.
I installed nginx, cronie,vim and python packages.
I gave my phone a static IP via DHCP reservation and added the default HTTP endpoint from
nginxtouptime-kumato monitor my monitor ;)
Add a SIM-Card so you can send and receive Text-Messages/SMS! This is crucial. You do NOT need a data plan, in fact: turn off your mobile data, because the script will report a false positive otherwise (reaching the internet, just not via your network but via 4G/5G).
Autostarting some services
I use termux via ssh and have nginx running to monitor my monitor.
A simple python script exports battery infos as simple API, which I consume from Homeassistant.
The Wake-Lock is ensuring the device stays online.
cat .termux/boot/start-nginx
#!/data/data/com.termux/files/usr/bin/sh
termux-wake-lock
nginx
cat .termux/boot/start-python
#!/data/data/com.termux/files/usr/bin/sh
termux-wake-lock
python ~/battery_server.py
cat .termux/boot/start-sshd
#!/data/data/com.termux/files/usr/bin/sh
termux-wake-lock
sshd
cat .termux/boot/start-cronie
#!/data/data/com.termux/files/usr/bin/sh
termux-wake-lock
crond
And the android script:
#!/data/data/com.termux/files/usr/bin/python
from http.server import BaseHTTPRequestHandler, HTTPServer
import subprocess
import json
class BatteryHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/battery":
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
try:
output = subprocess.check_output(['termux-battery-status'])
self.wfile.write(output)
except Exception as e:
error_msg = json.dumps({"error": str(e)})
self.wfile.write(error_msg.encode())
else:
self.send_response(404)
self.end_headers()
self.wfile.write(b'Not Found')
server = HTTPServer(('0.0.0.0', 5050), BatteryHandler)
print("Battery API server running on port 5050...")
server.serve_forever()Protecting your battery (this needs root!)
Even though you don’t need root for the first part, you need to root your phone, if you want to control the battery charging. As I attached my phone to my router, to act as a internet-failover via usb tethering, the battery would be charging the whole, which isn’t good for multiple reasons (battery lifetime, fire risk, etc). If you only want monitoring, you can use a smartplug and homeassisant to control the charging.
I used Magisk to root my phone, the way to root your phone depends on your phone, so I will not go into details here.
vi /data/adb/battery_charge.sh
#!/system/bin/sh
# --- Configuration ---
# Path to read battery percentage
CAPACITY_PATH="/sys/class/power_supply/battery/capacity"
# Path to control charging (provided by you)
# Writing 1 enables charging, writing 0 disables it.
CONTROL_PATH="/sys/class/power_supply/battery/battery_charging_enabled"
# Limits
LOW_LIMIT=60
HIGH_LIMIT=80
# Loop forever
while true; do
# 1. Get current battery level
if [ -f "$CAPACITY_PATH" ]; then
CURRENT_LEVEL=$(cat "$CAPACITY_PATH")
else
echo "Error: Capacity path not found."
exit 1
fi
# 2. Check Logic
if [ "$CURRENT_LEVEL" -lt "$LOW_LIMIT" ]; then
# Battery is below 60% -> ENABLE CHARGING
echo "Level is $CURRENT_LEVEL%. Enabling charging..."
echo 1 > "$CONTROL_PATH"
elif [ "$CURRENT_LEVEL" -ge "$HIGH_LIMIT" ]; then
# Battery is 80% or above -> DISABLE CHARGING
echo "Level is $CURRENT_LEVEL%. Disabling charging..."
echo 0 > "$CONTROL_PATH"
fi
# 3. Wait for 5 minutes (300 seconds) before checking again
sleep 300
doneMake it executable with chmod +x $FILENAME.
And let’s run it on boot with
vi /data/adb/services.d/battery_boot.sh
#!/system/bin/sh
sleep 60
nohup /data/adb/battery_charge.sh.sh &Make it executable and reboot to test.
I activated adb over network and also made it persistent with
vi /data/adb/service.d/adb_net.sh
#!/system/bin/sh
setprop service.adb.tcp.port 5555
stop adbd
start adbdSending text messages
I wrote a simple bash script like this (from within termux, as we need chronie):
We are using
tracerouteas the 4G Connection from the phone otherwise could lead to a false-positive.
vi /data/data/com.termux/files/home/ping_google.sh
#!/data/data/com.termux/files/usr/bin/bash
##########################################
# Configuration
##########################################
TARGET_IP="8.8.8.8"
ROUTER_IP="192.168.10.1"
# Traceroute Options:
# -n: Numeric output (no DNS lookup)
# -w 1: Wait max 1 second per hop
# -q 1: Send only 1 probe per hop
# -m 20: Max 20 hops
TRACE_CMD="traceroute -n -w 1 -q 1 -m 20"
SMS_TARGET="+1223456789"
SMS_MESSAGE_INTERNET="ALERT: Internet outage (Router reachable, but cannot reach Internet)"
SMS_MESSAGE_POWER="ALERT: Power/Local outage (Phone is not routing via $ROUTER_IP)"
HEALTHCHECKS_URL="https://hc-ping.com/foobar"
STATE_FILE="./.internet_status"
LOG_FILE="./online_check.log"
##########################################
# Logic
##########################################
echo "$(date): Starting Monitoring" >> "$LOG_FILE"
# 1. Initialize State
if [ ! -f "$STATE_FILE" ]; then echo "online" > "$STATE_FILE"; fi
PREV_STATE=$(cat "$STATE_FILE")
# 2. Run Traceroute (The Single Source of Truth)
TRACE_OUTPUT=$($TRACE_CMD $TARGET_IP 2>&1)
echo "$TRACE_OUTPUT" >> "$LOG_FILE"
# 3. Analyze Results
# Extract the IP of Hop 1.
# We look for the line where column 1 ($1) equals "1".
FIRST_HOP=$(echo "$TRACE_OUTPUT" | awk '$1 == "1" {print $2; exit}')
# Check if we reached the target.
# We look for ANY line where column 1 is a number AND column 2 is the Target IP.
# This avoids matching the header line "traceroute to 8.8.8.8..."
if echo "$TRACE_OUTPUT" | awk -v ip="$TARGET_IP" '$1 ~ /^[0-9]+$/ && $2 == ip {found=1} END {if (found) exit 0; else exit 1}'; then
REACHED_TARGET=true
else
REACHED_TARGET=false
fi
# 4. Determine Status
if [ "$FIRST_HOP" != "$ROUTER_IP" ]; then
# Hop 1 is NOT 192.168.0.1 (It might be empty, "*", or a 4G Carrier IP)
CURRENT_STATE="power_down"
echo "$(date): Power/Local Issue. First hop: '$FIRST_HOP' (Expected $ROUTER_IP)" >> "$LOG_FILE"
elif [ "$REACHED_TARGET" = false ]; then
# Hop 1 was correct, but we never found the line with "8.8.8.8" as a hop.
CURRENT_STATE="internet_down"
echo "$(date): Internet Issue. Router OK, but target $TARGET_IP never reached." >> "$LOG_FILE"
else
# Hop 1 is Router AND we found the Target IP in the hop list.
echo "$(date): Online - found $TARGET_IP and $ROUTER_IP" >> "$LOG_FILE"
CURRENT_STATE="online"
fi
##########################################
# State Transitions & Notifications
##########################################
if [ "$CURRENT_STATE" != "$PREV_STATE" ]; then
echo "$(date): State change detected: $PREV_STATE -> $CURRENT_STATE" >> "$LOG_FILE"
if [ "$CURRENT_STATE" = "internet_down" ]; then
termux-sms-send -n "$SMS_TARGET" "$SMS_MESSAGE_INTERNET"
curl -fsS --max-time 5 "${HEALTHCHECKS_URL}/fail" > /dev/null
elif [ "$CURRENT_STATE" = "power_down" ]; then
termux-sms-send -n "$SMS_TARGET" "$SMS_MESSAGE_POWER"
curl -fsS --max-time 5 "${HEALTHCHECKS_URL}/fail" > /dev/null
elif [ "$CURRENT_STATE" = "online" ]; then
echo "$(date): Connectivity restored." >> "$LOG_FILE"
curl -fsS --max-time 5 "$HEALTHCHECKS_URL" > /dev/null
fi
echo "$CURRENT_STATE" > "$STATE_FILE"
elif [ "$CURRENT_STATE" = "online" ]; then
# Send Heartbeat
curl -fsS --max-time 5 "$HEALTHCHECKS_URL" > /dev/null
fiThe script uses termux-sms-send to send the text. Double check the app permissions if sending texts does not work.
Add the script to crontab -e like
*/5 * * * * /data/data/com.termux/files/home/ping_google.shand you are done.
Summary
Termux is a mighty tool for android and with the coming Linux Shell for Android 16, this will give everyone completely new opportunities to use their phones! You are basically carrying your server around.