#!/system/bin/sh # Android DDNS client installer. # # Run directly on the Android device (via telnet / serial / adb shell). # Writes ddns-report.sh, ddns-runner.sh, and ddns.env to $PREFIX, # then starts the daemon. # # Usage (on the box): # sh install.sh --token SECRET [--name p291] [--url http://...] [--iface eth0] PREFIX="/data/local/tmp" WORKER_URL="http://ddns.eachtime.me/update" TOKEN="123456789" NAME="p291" IFACE="eth0" INTERVAL="60" SKIP_START=0 usage() { echo "usage: install.sh [options]" echo " --url URL Worker HTTP URL (default: http://ddns.eachtime.me/update)" echo " --token TOKEN shared bearer token (required)" echo " --name PREFIX subdomain prefix (e.g. p291 -> p291.eachtime.me)" echo " --iface NAME network interface (default: eth0)" echo " --interval SECS poll interval (default: 60)" echo " --skip-start install but don't start daemon" echo " -h, --help show this help" } while [ $# -gt 0 ]; do case "$1" in --url) WORKER_URL="$2"; shift 2 ;; --token) TOKEN="$2"; shift 2 ;; --name) NAME="$2"; shift 2 ;; --iface) IFACE="$2"; shift 2 ;; --interval) INTERVAL="$2"; shift 2 ;; --skip-start) SKIP_START=1; shift ;; -h|--help) usage; exit 0 ;; *) echo "unknown option: $1" >&2; usage; exit 2 ;; esac done if [ -z "$TOKEN" ]; then echo "error: --token is required" >&2 usage exit 2 fi echo "[1/3] writing config" cat > "$PREFIX/ddns.env" << ENVEOF DDNS_WORKER_URL='$WORKER_URL' DDNS_TOKEN='$TOKEN' DDNS_NAME='$NAME' DDNS_IFACE='$IFACE' DDNS_INTERVAL='$INTERVAL' ENVEOF chmod 600 "$PREFIX/ddns.env" echo "[2/3] writing scripts" cat > "$PREFIX/ddns-report.sh" << 'REPORTEOF' #!/system/bin/sh set -u DDNS_ENV_FILE="${DDNS_ENV_FILE:-/data/local/tmp/ddns.env}" if [ -f "$DDNS_ENV_FILE" ]; then . "$DDNS_ENV_FILE" fi WORKER_URL="${DDNS_WORKER_URL:-http://ddns.eachtime.me/update}" TOKEN="${DDNS_TOKEN:-REPLACE_ME_SHARED_TOKEN}" NAME="${DDNS_NAME:-}" IFACE="${DDNS_IFACE:-eth0}" INTERVAL="${DDNS_INTERVAL:-60}" HTTPURL="${DDNS_HTTPURL:-/system/xbin/httpurl}" STATE_FILE="${DDNS_STATE_FILE:-/data/local/tmp/ddns.last}" LOG_FILE="${DDNS_LOG_FILE:-/data/local/tmp/ddns.log}" MAX_LOG_BYTES="${DDNS_MAX_LOG_BYTES:-524288}" BACKOFF_MAX="${DDNS_BACKOFF_MAX:-600}" ONESHOT="${DDNS_ONESHOT:-0}" DATE_CMD="/system/bin/toybox date" log() { ts=$($DATE_CMD '+%Y-%m-%d %H:%M:%S') printf '%s %s\n' "$ts" "$*" >>"$LOG_FILE" 2>/dev/null if [ -f "$LOG_FILE" ]; then size=$(/system/bin/toybox stat -c '%s' "$LOG_FILE" 2>/dev/null || echo 0) if [ "${size:-0}" -gt "$MAX_LOG_BYTES" ] 2>/dev/null; then mv "$LOG_FILE" "${LOG_FILE}.1" 2>/dev/null fi fi } get_ip() { /system/bin/ip -4 route get 8.8.8.8 2>/dev/null \ | /system/bin/toybox sed -n 's/.*src \([0-9.]*\).*/\1/p' \ | /system/bin/toybox head -n1 } is_public_ipv4() { case "$1" in ""|0.*|127.*|169.254.*|10.*|192.168.*) return 1 ;; 172.16.*|172.17.*|172.18.*|172.19.*|172.2[0-9].*|172.3[0-1].*) return 1 ;; 100.6[4-9].*|100.[7-9][0-9].*|100.1[0-1][0-9].*|100.12[0-7].*) return 1 ;; 22[4-9].*|23[0-9].*|24[0-9].*|25[0-5].*) return 1 ;; esac case "$1" in *.*.*.*) return 0 ;; *) return 1 ;; esac } send_update() { ip="$1" url="${WORKER_URL}?t=${TOKEN}&ip=${ip}&name=${NAME}" raw=$("$HTTPURL" "$url" 2>&1) || { echo "$raw"; return 1; } body=$(echo "$raw" | grep '^{' | head -n1) echo "$body" echo "$body" | grep -q '"ok":true' } run_once() { ip=$(get_ip) if ! is_public_ipv4 "$ip"; then log "skip: no public ipv4 detected (got '$ip')" return 2 fi last="" [ -f "$STATE_FILE" ] && last=$(cat "$STATE_FILE" 2>/dev/null) if [ "$ip" = "$last" ]; then return 0 fi resp=$(send_update "$ip" 2>&1) rc=$? if [ $rc -eq 0 ]; then printf '%s' "$ip" >"$STATE_FILE" log "ok: $last -> $ip :: $resp" return 0 else log "fail($rc): attempt $ip :: $resp" return 1 fi } main_loop() { backoff=0 trap 'log "stopping (signal)"; exit 0' TERM INT log "starting: iface=$IFACE interval=${INTERVAL}s url=$WORKER_URL" while :; do if run_once; then backoff=0 sleep "$INTERVAL" else if [ "$backoff" -eq 0 ]; then backoff="$INTERVAL" else backoff=$((backoff * 2)) [ "$backoff" -gt "$BACKOFF_MAX" ] && backoff="$BACKOFF_MAX" fi log "retry in ${backoff}s" sleep "$backoff" fi done } if [ ! -x "$HTTPURL" ]; then log "fatal: httpurl binary not found at $HTTPURL" exit 127 fi if [ "$TOKEN" = "REPLACE_ME_SHARED_TOKEN" ]; then log "fatal: DDNS_TOKEN is unset" exit 2 fi if [ -z "$NAME" ]; then NAME=$(cat /proc/sys/kernel/hostname 2>/dev/null | /system/bin/toybox cut -d_ -f1) [ -z "$NAME" ] && NAME="dd" log "auto name=$NAME (set DDNS_NAME to override)" fi if [ "$ONESHOT" = "1" ]; then run_once exit $? fi main_loop REPORTEOF cat > "$PREFIX/ddns-runner.sh" << 'RUNEOF' #!/system/bin/sh SCRIPT="${DDNS_SCRIPT:-/data/local/tmp/ddns-report.sh}" PIDFILE="${DDNS_PIDFILE:-/data/local/tmp/ddns.pid}" LOG_FILE="${DDNS_LOG_FILE:-/data/local/tmp/ddns.log}" is_running() { [ -f "$PIDFILE" ] || return 1 pid=$(cat "$PIDFILE" 2>/dev/null) [ -n "$pid" ] || return 1 kill -0 "$pid" 2>/dev/null } cmd_start() { if is_running; then echo "already running (pid=$(cat "$PIDFILE"))" return 0 fi if [ ! -x "$SCRIPT" ]; then echo "script not executable: $SCRIPT" >&2 return 127 fi nohup "$SCRIPT" >/dev/null 2>&1 & pid=$! echo "$pid" >"$PIDFILE" echo "started pid=$pid" } cmd_stop() { if ! is_running; then echo "not running" rm -f "$PIDFILE" return 0 fi pid=$(cat "$PIDFILE") kill "$pid" 2>/dev/null i=0 while kill -0 "$pid" 2>/dev/null && [ $i -lt 5 ]; do sleep 1 i=$((i + 1)) done kill -0 "$pid" 2>/dev/null && kill -9 "$pid" 2>/dev/null rm -f "$PIDFILE" echo "stopped pid=$pid" } cmd_status() { if is_running; then echo "running pid=$(cat "$PIDFILE")" else echo "stopped" fi if [ -f "$LOG_FILE" ]; then echo "--- last 10 log lines ---" /system/bin/toybox tail -n 10 "$LOG_FILE" fi } case "${1:-}" in start) cmd_start ;; stop) cmd_stop ;; restart) cmd_stop; cmd_start ;; status) cmd_status ;; *) echo "usage: $0 {start|stop|restart|status}" >&2; exit 2 ;; esac RUNEOF chmod 755 "$PREFIX/ddns-report.sh" "$PREFIX/ddns-runner.sh" echo "[3/3] testing & starting" # One-shot test first DDNS_ONESHOT=1 "$PREFIX/ddns-report.sh" echo "" cat "$PREFIX/ddns.log" 2>/dev/null | /system/bin/toybox tail -n 5 echo "" if [ "$SKIP_START" = "1" ]; then echo "skipped start. run: $PREFIX/ddns-runner.sh start" else "$PREFIX/ddns-runner.sh" start "$PREFIX/ddns-runner.sh" status fi echo "" echo "done. logs: cat $PREFIX/ddns.log"