cf/client/install.sh

257 lines
7.0 KiB
Bash

#!/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"