【超详细】手机搭建服务器 · 第 三 期

前两期回顾: 【超详细】手机搭建服务器 · 第 一 期 【超详细】手机搭建服务器 · 第 二 期 前言 第一期讲了必备条件;第二期讲了如何实现旁路供电,解决供电问题; 本期要实现的是:如何让手机的wifi永不断连: 不管是手机意外重启,还是重新开关机; 不管是开启了飞行模式; 不管是不是主动关闭wi...
【超详细】手机搭建服务器 · 第 三 期
【超详细手机搭建服务器 · 第 三 期

前两期回顾:

【超详细】手机搭建服务器 · 第 一 期
【超详细】手机搭建服务器 · 第 二 期

前言

第一期讲了必备条件;第二期讲了如何实现旁路供电,解决供电问题;

本期要实现的是:如何让手机的wifi永不断连:

  • 不管是手机意外重启,还是重新开关机;
  • 不管是开启了飞行模式;
  • 不管是不是主动关闭wifi。

[!warning]建议
在开始之前,建议先把手机的这两个开关关闭,不是必须,但是建议,减少干扰:

  1. 系统设置 - 开发者选项 - WLAN扫描调节 - 关闭
  2. 系统设置 - WLAN - WLAN助理 - 智能选网和网速模式 - 关闭

    补充说明:
    · 机型不同,打开开发者选项的方式也不同,这个自己百度
    · 不是所有手机都有【WLAN助理】这个选项,我是MIUI 11,所有有,其他手机可能是其他名字,或者没有这个功能,自己看自己系统的功能开关。主要是把能影响WIFI稳定的开关关闭,不管是信号调节还是省电方面的设置。

教程开始

1.新建开机自启脚本

1.1 打开MT管理器,进入/data/adb/service.d/文件夹,新建99-phone-server.sh文件

1.2 打开文件,在文件中输入以下代码并保存:

#!/system/bin/sh

# ============================================================
# Phone Server 启动器
# 放在 Magisk service.d 中
# 注意:
# 这个脚本不写其他的复杂业务逻辑,只负责启动真正的脚本。
# 以后 IPv6、DDNS、Debian、SSH 等脚本,也会这么做。
# ============================================================

WIFI_KEEPER="/data/local/phone-server/wifi-keeper/wifi-keeper.sh"
WIFI_LOG="/data/local/phone-server/wifi-keeper/wifi-keeper.log"

mkdir -p /data/local/phone-server/wifi-keeper

echo "$(date '+%Y-%m-%d %H:%M:%S') [INFO] [启动器] 99-phone-server.sh 开始执行" >> "$WIFI_LOG"

if [ -x "$WIFI_KEEPER" ]; then
  nohup "$WIFI_KEEPER" >/dev/null 2>&1 &
  echo "$(date '+%Y-%m-%d %H:%M:%S') [OK] [启动器] 已后台启动 Wi-Fi Keeper" >> "$WIFI_LOG"
else
  echo "$(date '+%Y-%m-%d %H:%M:%S') [ERROR] [启动器] Wi-Fi Keeper 不存在或没有执行权限:$WIFI_KEEPER" >> "$WIFI_LOG"
fi

1.3给脚本设置权限

imageimageimage

2.新建wifi核心脚本

2.1 打开MT管理器,进入/data/local/文件夹,新建phone-server文件夹,然后进入phone-server文件夹再新建wifi-keeper文件夹;

2.2 在/data/local/phone-server/wifi-keeper/文件夹下面新建wifi-keeper.sh文件,并且输入下面的代码进行保存:

#!/system/bin/sh

# ============================================================
# Wi-Fi Keeper
# ============================================================
# 功能:
# 1. 开机后自动检查 wifi 开关
# 2. 如果 wifi 关闭,自动打开 wifi
# 3. 等待系统自动连接“已保存”的 wifi
# 4. 如果系统没有自动连上,则按 WIFI_LIST 优先级选择已保存 wifi
# 5. wifi 断开后,自动重新连接
# 6. 输出详细日志,方便排查
#
# 适用环境:
# Android Root + Magisk service.d
#
# 重要说明:
# 1. 需要提前手动连接一次 wifi,让系统保存 wifi。
# 2. WIFI_LIST 里只写 Wi-Fi 名称,不用写密码、不用写验证方式。
# 3. 如果一个WI-FI没连接成功,系统会连接下一个wifi
# 4. 这是常驻守护脚本,不是执行一次就退出的脚本。
# ============================================================


# ============================================================
# 基础路径配置
# ============================================================

BASE_DIR="/data/local/phone-server/wifi-keeper"
LOG_FILE="$BASE_DIR/wifi-keeper.log"

# 临时锁目录,放在 /dev,重启后自动消失
# 作用:防止同一个 wifi-keeper 被重复启动
LOCK_DIR="/dev/phone-server-wifi-keeper.lock"

WIFI_IFACE="wlan0"


# ============================================================
# Wi-Fi 列表配置
# ============================================================
# 格式:
# 一行一个 Wi-Fi 名称。
#
# 顺序就是优先级:
# 第一行优先级最高,连接失败才尝试下一行。
# ============================================================

WIFI_LIST="
CMCC-Semmering
"


# ============================================================
# 行为参数
# ============================================================

# 开机后等待系统完全启动,最多等待多少秒,单位:秒
BOOT_WAIT_MAX=60

# 系统启动完成后,额外等待多少秒,单位:秒
# 有些 MIUI 设备 sys.boot_completed=1 后,Wi-Fi 服务还没完全准备好。
AFTER_BOOT_EXTRA_WAIT=10

# 打开 Wi-Fi 后,最多等待多久确认 Wi-Fi 进入可连接状态,单位:秒
ENABLE_WIFI_WAIT=10

# 打开 Wi-Fi 后,等待系统自动连接已保存 Wi-Fi 的时间,单位:秒
AUTO_CONNECT_WAIT=10

# 使用 wpa_cli 选择已保存 Wi-Fi 后,最多等待多久拿到 IP,单位:秒
SELECT_WIFI_WAIT=30

# 正常情况下,每隔多少秒检查一次 Wi-Fi,单位:秒
CHECK_INTERVAL=60

# 本轮修复失败后,等待多少秒再重试,单位:秒
FAIL_COOLDOWN=30

# 日志最大大小,单位:字节
# 1048576 字节 = 1MB
# 超过后只保留最后 300 行,避免日志无限增长
MAX_LOG_SIZE=1048576


# ============================================================
# 全局变量
# ============================================================

WPA_CLI_BIN=""
WPA_CTRL_DIR=""
WPA_LAST_OUT=""


# ============================================================
# 日志函数
# ============================================================

now() {
  date '+%Y-%m-%d %H:%M:%S'
}

# 避免日志里输出 MAC 地址 / BSSID
mask_mac() {
  echo "$1" | sed -E 's/([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}/[MAC已隐藏]/g'
}

# 取 WIFI_LIST 中第一个有效 wifi名称,用于“尚未解析到当前 SSID”时的日志提示。
# 这样日志不会把 MIUI 的 BSSID / state 状态误当成 Wi-Fi 名称。
get_first_wifi_name_from_list() {
  printf '%s\n' "$WIFI_LIST" | while IFS= read -r line; do
    [ -z "$line" ] && continue

    case "$line" in
      \#*) continue ;;
    esac

    echo "$line"
    return 0
  done
}

# 清洗从 dumpsys / wpa_cli 解析出来的 SSID。

clean_ssid_value() {
  raw="$1"

  ssid="$(echo "$raw" | sed 's/^ *//;s/ *$//')"

  case "$ssid" in
    ""|"<unknown ssid>"|"unknown"|"null"|"0x"|"WifiSsid.NONE"|"none"|"<none>")
      echo ""
      return 0
      ;;
  esac

  echo "$ssid" | grep -Eiq '([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}|(^|[[:space:]])nid:|(^|[[:space:]])state:|bssid|scanning|associating|disconnected|completed' && {
    echo ""
    return 0
  }

  echo "$ssid"
}

rotate_log_if_needed() {
  [ -f "$LOG_FILE" ] || return 0

  size="$(wc -c < "$LOG_FILE" 2>/dev/null)"
  [ -n "$size" ] || return 0

  if [ "$size" -gt "$MAX_LOG_SIZE" ]; then
    tail -n 300 "$LOG_FILE" > "$LOG_FILE.tmp" 2>/dev/null
    mv "$LOG_FILE.tmp" "$LOG_FILE" 2>/dev/null
  fi
}

log() {
  level="$1"
  action="$2"
  msg="$(mask_mac "$3")"

  mkdir -p "$BASE_DIR"
  rotate_log_if_needed

  echo "$(now) [$level] [$action] $msg" >> "$LOG_FILE"
}

log_info() {
  log "INFO" "$1" "$2"
}

log_ok() {
  log "OK" "$1" "$2"
}

log_error() {
  log "ERROR" "$1" "$2"
}


# ============================================================
# 防止重复运行
# ============================================================

acquire_lock() {
  if mkdir "$LOCK_DIR" 2>/dev/null; then
    trap 'rm -rf "$LOCK_DIR"; log_info "退出脚本" "Wi-Fi Keeper 已退出,释放 lock"' EXIT
    log_ok "启动脚本" "获取 lock 成功,当前实例开始运行"
    return 0
  fi

  log_error "启动脚本" "检测到已有 Wi-Fi Keeper 正在运行,当前实例退出"
  exit 0
}


# ============================================================
# 环境检查
# ============================================================

check_env() {
  mkdir -p "$BASE_DIR"

  if ! command -v svc >/dev/null 2>&1; then
    log_error "检查环境" "系统不支持 svc 命令,无法打开 Wi-Fi"
    exit 1
  fi

  if ! command -v ip >/dev/null 2>&1; then
    log_error "检查环境" "系统不支持 ip 命令,无法检测 wlan0 地址"
    exit 1
  fi

  log_ok "检查环境" "环境检查完成"
}


wait_boot_completed() {
  log_info "等待系统启动" "开始等待 sys.boot_completed=1"

  i=0
  while [ "$i" -lt "$BOOT_WAIT_MAX" ]; do
    boot_completed="$(getprop sys.boot_completed 2>/dev/null)"

    if [ "$boot_completed" = "1" ]; then
      log_ok "等待系统启动" "系统启动完成"
      return 0
    fi

    sleep 2
    i=$((i + 2))
  done

  log_error "等待系统启动" "等待系统启动完成超时,继续执行后续逻辑"
  return 1
}


# ============================================================
# Wi-Fi 状态判断
# ============================================================

wifi_on_value() {
  settings get global wifi_on 2>/dev/null
}

wifi_on_text() {
  v="$(wifi_on_value)"

  case "$v" in
    0)
      echo "关闭"
      ;;
    1)
      echo "已开启"
      ;;
    2)
      echo "正在开启"
      ;;
    3)
      echo "正在关闭"
      ;;
    *)
      echo "未知状态:$v"
      ;;
  esac
}

has_wifi_ip() {
  ip -4 addr show dev "$WIFI_IFACE" 2>/dev/null | grep -q "inet " && return 0
  ip -6 addr show dev "$WIFI_IFACE" scope global 2>/dev/null | grep -q "inet6 " && return 0

  return 1
}

wifi_iface_up() {
  ip link show "$WIFI_IFACE" 2>/dev/null | grep -q "UP"
}

wifi_ready_for_connect() {
  # 已经拿到 IP,说明 Wi-Fi 已连接
  has_wifi_ip && return 0

  # settings 显示已开启
  [ "$(wifi_on_value)" = "1" ] && return 0

  # wlan0 已经 UP,也说明可以等待系统自动连接或尝试选择已保存网络
  wifi_iface_up && return 0

  return 1
}


# ============================================================
# 当前 Wi-Fi 名称解析
# ============================================================

find_wpa_cli_bin_quiet() {
  [ -n "$WPA_CLI_BIN" ] && [ -x "$WPA_CLI_BIN" ] && return 0

  for b in \
    "$(command -v wpa_cli 2>/dev/null)" \
    "/system/bin/wpa_cli" \
    "/vendor/bin/wpa_cli" \
    "/system/xbin/wpa_cli" \
    "/system_ext/bin/wpa_cli"
  do
    [ -z "$b" ] && continue

    if [ -x "$b" ]; then
      WPA_CLI_BIN="$b"
      return 0
    fi
  done

  return 1
}

wpa_cli_raw() {
  if [ -z "$WPA_CTRL_DIR" ]; then
    "$WPA_CLI_BIN" -i "$WIFI_IFACE" "$@" 2>&1
  else
    "$WPA_CLI_BIN" -p "$WPA_CTRL_DIR" -i "$WIFI_IFACE" "$@" 2>&1
  fi
}

get_current_ssid_from_wpa() {
  find_wpa_cli_bin_quiet || return 0

  # 如果已经检测过控制目录,就直接用
  if [ -n "$WPA_CTRL_DIR" ]; then
    out="$(wpa_cli_raw status 2>/dev/null)"
    ssid="$(echo "$out" | sed -n 's/^ssid=//p' | head -n 1)"
    ssid="$(clean_ssid_value "$ssid")"
    [ -n "$ssid" ] && echo "$ssid"
    return 0
  fi

  # 尝试几个常见控制目录
  for p in \
    "" \
    "/data/vendor/wifi/wpa/sockets" \
    "/data/misc/wifi/sockets" \
    "/dev/socket"
  do
    WPA_CTRL_DIR="$p"
    out="$(wpa_cli_raw status 2>/dev/null)"

    echo "$out" | grep -q "wpa_state" || continue

    ssid="$(echo "$out" | sed -n 's/^ssid=//p' | head -n 1)"
    ssid="$(clean_ssid_value "$ssid")"

    if [ -n "$ssid" ]; then
      echo "$ssid"
      return 0
    fi
  done

  WPA_CTRL_DIR=""
  return 0
}

get_current_ssid_from_dumpsys() {
  # 只提取 SSID,避免输出 BSSID/MAC。
  line="$(dumpsys wifi 2>/dev/null | grep -m 1 "SSID:" 2>/dev/null)"

  [ -z "$line" ] && return 0

  ssid="$(echo "$line" | sed -n 's/.*SSID: *"\([^"]*\)".*/\1/p')"

  if [ -z "$ssid" ]; then
    ssid="$(echo "$line" | sed -n 's/.*SSID: *\([^,]*\).*/\1/p')"
  fi

  ssid="$(clean_ssid_value "$ssid")"

  [ -n "$ssid" ] && echo "$ssid"
}

get_current_ssid() {
  ssid="$(get_current_ssid_from_wpa)"

  if [ -n "$ssid" ]; then
    echo "$ssid"
    return 0
  fi

  ssid="$(get_current_ssid_from_dumpsys)"

  if [ -n "$ssid" ]; then
    echo "$ssid"
    return 0
  fi

  echo ""
}

# 获取日志里应该显示的 Wi-Fi 名称。
# 说明:
# MIUI 11 / Android 10 有时不允许从 wpa_cli / dumpsys 稳定解析当前 SSID。
# 如果已经拿到 Wi-Fi IP,但解析不到 SSID,并且 WIFI_LIST 里配置了目标 Wi-Fi,
# 就在日志中显示“目标 WiFi + 推断说明”,避免反复出现“未解析到当前WiFi名称”。
get_display_wifi_name() {
  real_ssid="$(get_current_ssid)"

  if [ -n "$real_ssid" ]; then
    echo "$real_ssid"
    return 0
  fi

  target_ssid="$(get_first_wifi_name_from_list)"

  if has_wifi_ip && [ -n "$target_ssid" ]; then
    echo "$target_ssid(根据WIFI_LIST推断)"
    return 0
  fi

  if [ -n "$target_ssid" ]; then
    echo "未连接;目标WiFi=$target_ssid"
  else
    echo "未连接"
  fi
}


log_wifi_basic_status() {
  wifi_value="$(wifi_on_value)"
  wifi_text="$(wifi_on_text)"
  ssid="$(get_display_wifi_name)"
  ip_info="$(ip addr show dev "$WIFI_IFACE" 2>/dev/null | grep -E 'inet |inet6 ' | tr '\n' ' ')"

  [ -z "$ip_info" ] && ip_info="未拿到IP"

  log_info "WiFi状态" "WiFi开关=$wifi_value($wifi_text);当前WiFi=$ssid;IP=$ip_info"
}


enable_wifi() {
  if wifi_ready_for_connect; then
    log_ok "打开WiFi开关" "Wi-Fi 已经处于可用或可连接状态,无需重复打开"
    log_wifi_basic_status
    return 0
  fi

  log_info "打开WiFi开关" "检测到 Wi-Fi 关闭,开始打开 Wi-Fi"

  out="$(svc wifi enable 2>&1)"
  rc="$?"

  if [ "$rc" -ne 0 ]; then
    log_error "打开WiFi开关" "svc wifi enable 执行失败,rc=$rc,输出:$out"
    return 1
  fi

  log_info "打开WiFi开关" "svc wifi enable 执行成功,开始等待 Wi-Fi 进入可连接状态"

  i=0
  while [ "$i" -lt "$ENABLE_WIFI_WAIT" ]; do
    if wifi_ready_for_connect; then
      log_ok "打开WiFi开关" "Wi-Fi 已进入可用或可连接状态"
      log_wifi_basic_status
      return 0
    fi

    if [ $((i % 10)) -eq 0 ]; then
      log_info "打开WiFi开关" "等待 Wi-Fi 开启中,已等待 ${i} 秒"
      log_wifi_basic_status
    fi

    sleep 2
    i=$((i + 2))
  done

  log_error "打开WiFi开关" "已执行打开 Wi-Fi,但 ${ENABLE_WIFI_WAIT} 秒内没有确认进入可连接状态"
  log_wifi_basic_status
  return 1
}


wifi_connected_ok() {
  if ! has_wifi_ip; then
    return 1
  fi

  ssid="$(get_display_wifi_name)"

  log_ok "检查WiFi状态" "Wi-Fi 已连接,当前WiFi=$ssid,已拿到 IP"

  return 0
}


wait_wifi_connected() {
  action="$1"
  max_wait="$2"

  i=0
  while [ "$i" -lt "$max_wait" ]; do
    if wifi_connected_ok; then
      log_ok "$action" "连接判断成功"
      return 0
    fi

    if [ $((i % 10)) -eq 0 ]; then
      log_info "$action" "等待 Wi-Fi 获取 IP,已等待 ${i} 秒"
      log_wifi_basic_status
    fi

    sleep 2
    i=$((i + 2))
  done

  log_error "$action" "等待 ${max_wait} 秒后仍未拿到 Wi-Fi IP"
  log_wifi_basic_status
  return 1
}


wait_auto_connect() {
  log_info "自动连接WIFI" "等待系统自动连接已保存 Wi-Fi,最长等待 ${AUTO_CONNECT_WAIT} 秒"

  if wait_wifi_connected "自动连接WIFI" "$AUTO_CONNECT_WAIT"; then
    return 0
  fi

  log_error "自动连接WIFI" "系统自动连接超时,准备按 Wi-Fi 名称选择已保存网络"
  return 1
}


# ============================================================
# 使用 wpa_cli 选择已保存 wifi
# 注意:这里只选择已保存网络,不写入密码,不创建新网络
# ============================================================

find_wpa_cli_bin() {
  if find_wpa_cli_bin_quiet; then
    log_ok "查找wpa_cli" "找到 wpa_cli:$WPA_CLI_BIN"
    return 0
  fi

  log_error "查找wpa_cli" "没有找到 wpa_cli,无法主动选择已保存 Wi-Fi,只能依赖系统自动连接"
  return 1
}

wpa_exec() {
  action="$1"
  shift

  WPA_LAST_OUT="$(wpa_cli_raw "$@")"
  rc="$?"

  safe_out="$(mask_mac "$WPA_LAST_OUT")"

  log_info "$action" "执行:$WPA_CLI_BIN ${WPA_CTRL_DIR:+-p $WPA_CTRL_DIR} -i $WIFI_IFACE $*;rc=$rc;输出:$safe_out"

  return "$rc"
}

find_wpa_cli_control() {
  if ! find_wpa_cli_bin; then
    return 1
  fi

  for p in \
    "" \
    "/data/vendor/wifi/wpa/sockets" \
    "/data/misc/wifi/sockets" \
    "/dev/socket"
  do
    WPA_CTRL_DIR="$p"

    out="$(wpa_cli_raw status 2>&1)"
    rc="$?"
    safe_out="$(mask_mac "$out")"

    log_info "检测wpa_cli" "尝试控制目录='${p:-默认}',rc=$rc,输出:$safe_out"

    echo "$out" | grep -q "wpa_state" && {
      log_ok "检测wpa_cli" "wpa_cli 可用,控制目录='${p:-默认}'"
      return 0
    }
  done

  WPA_CTRL_DIR=""

  log_error "检测wpa_cli" "wpa_cli 存在,但无法连接到 Wi-Fi 控制接口,只能依赖系统自动连接"
  return 1
}

find_saved_network_id_by_ssid() {
  target_ssid="$1"

  wpa_exec "查找已保存WIFI" list_networks

  network_id="$(printf '%s\n' "$WPA_LAST_OUT" \
    | awk -F '\t' -v target="$target_ssid" 'NR > 1 && $2 == target {print $1; exit}')"

  if [ -z "$network_id" ]; then
    log_error "查找已保存WIFI" "没有找到已保存的 Wi-Fi:$target_ssid。请先手动连接一次并保存"
    return 1
  fi

  log_ok "查找已保存WIFI" "找到已保存 Wi-Fi:$target_ssid,network_id=$network_id"
  echo "$network_id"
  return 0
}

select_saved_wifi_by_ssid() {
  ssid="$1"

  log_info "选择WIFI" "准备选择已保存 Wi-Fi:$ssid"

  if ! find_wpa_cli_control; then
    return 1
  fi

  network_id="$(find_saved_network_id_by_ssid "$ssid")"

  if [ -z "$network_id" ]; then
    return 1
  fi

  wpa_exec "启用WIFI" enable_network "$network_id"
  wpa_exec "选择WIFI" select_network "$network_id"
  wpa_exec "重连WIFI" reconnect

  if wait_wifi_connected "选择WIFI" "$SELECT_WIFI_WAIT"; then
    current_ssid="$(get_display_wifi_name)"

    log_ok "选择WIFI" "已连接 Wi-Fi:$current_ssid"

    return 0
  fi

  log_error "选择WIFI" "已尝试选择 Wi-Fi:$ssid,但最终没有拿到 IP"
  return 1
}


connect_by_priority() {
  log_info "连接WIFI" "开始按 WIFI_LIST 顺序选择已保存 Wi-Fi"

  tmp_list="/dev/phone-server-wifi-list.tmp"
  printf '%s\n' "$WIFI_LIST" > "$tmp_list"

  while IFS= read -r ssid; do
    [ -z "$ssid" ] && continue

    case "$ssid" in
      \#*) continue ;;
    esac

    log_info "连接WIFI" "按优先级尝试选择 Wi-Fi:$ssid"

    if select_saved_wifi_by_ssid "$ssid"; then
      rm -f "$tmp_list" 2>/dev/null
      return 0
    fi

    log_error "连接WIFI" "Wi-Fi=$ssid 连接失败,继续尝试下一个"
  done < "$tmp_list"

  rm -f "$tmp_list" 2>/dev/null

  log_error "连接WIFI" "WIFI_LIST 中的已保存 Wi-Fi 都没有连接成功"
  return 1
}


# ============================================================
# 主循环
# ============================================================

main_loop() {
  log_info "启动脚本" "Wi-Fi Keeper 启动"

  wait_boot_completed

  if [ "$AFTER_BOOT_EXTRA_WAIT" -gt 0 ]; then
    log_info "等待系统服务" "系统已启动,额外等待 ${AFTER_BOOT_EXTRA_WAIT} 秒,让 Wi-Fi 服务准备完成"
    sleep "$AFTER_BOOT_EXTRA_WAIT"
  fi

  log_wifi_basic_status

  enable_wifi

  if wait_auto_connect; then
    log_ok "启动连接" "系统自动连接 Wi-Fi 成功"
  else
    connect_by_priority
  fi

  while true; do
    if wifi_connected_ok; then
      sleep "$CHECK_INTERVAL"
      continue
    fi

    log_error "守护检测" "检测到 Wi-Fi 不可用,开始自动修复"

    enable_wifi

    if wait_auto_connect; then
      log_ok "守护检测" "系统自动恢复 Wi-Fi 成功"
    else
      if connect_by_priority; then
        log_ok "守护检测" "按 Wi-Fi 名称选择已保存网络成功"
      else
        log_error "守护检测" "本轮 Wi-Fi 修复失败,${FAIL_COOLDOWN} 秒后重试"
        sleep "$FAIL_COOLDOWN"
      fi
    fi
  done
}


# ============================================================
# 程序入口
# ============================================================

acquire_lock
check_env
main_loop

2.3 按照1.3的过程给脚本设置权限。

[!question] 如何验证脚本是否成功
1. 手机先正常连接上你选择Wi-Fi,建议选择一个即可,避免手机因为某些原因在多个wifi之间来回切换,这个对后面的ipv6的解析影响非常大,然后关闭wifi的自动连接开关。

2. 正常连接上一个wifi以后,关闭wifi开关,把手机重启。

3.验证手机重启wifi是否能正常连接: 重启以后,等几秒钟(具体看配置你写的是几秒),然后看看wifi开关是否会自动打开,并且wifi是否会自动连接上。

4.验证手动断开wifi是否能正常连接: 如果wifi能自动连接上,可以手动关闭wifi开关,看看wifi是否能过几秒(具体还是要看配置你写的是几秒)后自动打开并且连接上wifi。

我用的脚本在米8SE上多轮测试没问题,如果你们有问题,记得联系我,我到时候拍查一下

[!question] 如何关闭脚本
方法一: 取消/data/adb/service.d/99-phone-server.sh脚本的执行权限
方法二: 删除/data/adb/service.d/99-phone-server.sh脚本

[!success]结束语
不得不说,AI是真的好用。不仅可以优化脚本,还能把注释写的清清楚楚。按照它的逻辑和日志,就算出问题了也能很好的定位和解决,这一次确实是省了不少时间。可惜两天撸的business 2个月翻车了。

另外,给佬友们送上一份可直接食用的prompt:

我现在正在把一台 Android 手机改造成 7×24 小时运行的家庭小服务器。

目前我已经完成了 ACC 充电控制,解决了长期插电和旁路供电相关的问题。下一步我要解决的是:手机与 Wi-Fi 之间的连接稳定性。

我的目标是:  
手机开机后,或者 Wi-Fi 因为路由器重启、信号波动、系统休眠、网络异常等原因断开后,脚本能够自动恢复 Wi-Fi 连接,让手机尽可能长期稳定在线。

我的手机环境大概如下:

- Android 手机,已 Root;
- 使用 Magisk;
- 开机自启动脚本可以通过 Magisk 的 `/data/adb/service.d/` 实现;
- 手机会长期作为服务器运行;
- 后续还会继续做 IPv6 公网地址检测、DDNS 同步、Debian/SSH 服务守护等脚本;
- 所以我不希望所有业务脚本都直接堆在 `/data/adb/service.d/` 里。

---

# 我希望你帮我实现的事情

请你作为一名专业的 Android / Linux / Shell 脚本开发人员,帮我设计并编写一套 Wi-Fi 连接守护方案。

我希望它能实现这些能力:

## 1. 开机自动执行

手机开机后,Magisk 自动执行启动脚本。

但是我不希望把真正复杂的 Wi-Fi 守护逻辑直接写在 `/data/adb/service.d/` 里面。

我希望 `/data/adb/service.d/` 里只放一个“启动器脚本”,比如:

```text
/data/adb/service.d/99-phone-server.sh
```

这个启动器只负责启动真正的 Wi-Fi 守护脚本。

真正的 Wi-Fi 守护脚本和日志文件,请你帮我设计一个合理的目录,比如放在:

```text
/data/local/phone-server/wifi-keeper/
```

或者你认为更合理的目录也可以,但请你说明为什么这样设计。

---

## 2. 自动打开 Wi-Fi

脚本启动后,需要检查 Wi-Fi 开关状态。

如果 Wi-Fi 是关闭状态,就自动打开 Wi-Fi。

如果 Wi-Fi 已经打开,就不要重复操作。

注意:
不同 Android 版本、不同 ROM 对 Wi-Fi 命令支持可能不一样,所以请你不要想当然地只使用某一种命令。
请你先分析有哪些可能的实现方式,比如:

* `svc wifi enable`
* `settings get global wifi_on`
* `ip link show wlan0`
* `ip addr show wlan0`
* `wpa_cli`
* `cmd wifi`
* `dumpsys wifi`

然后根据兼容性和稳定性,选择更适合 Magisk Root 环境的实现方式。

如果某些命令在部分系统上可能不可用,请在脚本里做好兼容、兜底和日志记录,而不是让脚本直接崩溃。

---

## 3. Wi-Fi 列表和优先级

我希望脚本支持提前配置一个 Wi-Fi 列表。

比如:

```text
主 Wi-Fi
备用 Wi-Fi
手机热点
```

列表顺序就是优先级。

脚本应该优先连接列表中靠前的 Wi-Fi。

如果当前 Wi-Fi 可用,就不要频繁切换,避免影响 SSH、网页服务、IPv6、DDNS 等后续服务。

只有当当前 Wi-Fi 不可用、断开、没有拿到 IP,或者长时间无法恢复时,才尝试切换到下一个可用 Wi-Fi。

---

## 4. 已保存 Wi-Fi 和新 Wi-Fi 的处理

请你根据 Android 实际情况,设计合理逻辑:

* 如果 Wi-Fi 已经在系统中保存过,优先让系统自动连接;
* 如果系统自动连接失败,再尝试根据 Wi-Fi 名称选择已保存网络;
* 如果脚本能够可靠地支持“通过密码添加新 Wi-Fi”,可以设计为可选能力;
* 如果某些 Android 版本或 ROM 不适合脚本直接写入 Wi-Fi 密码,也请你明确说明原因,并给出更稳定的替代方案。

我不希望脚本为了追求“看起来功能完整”,强行使用不可靠的命令。

请你优先保证稳定性。

---

## 5. 不要频繁扫描和切换

手机是作为服务器使用的,所以稳定性比“永远连接最高优先级 Wi-Fi”更重要。

请你设计逻辑时注意:

* 如果当前 Wi-Fi 已经连接成功,并且已经拿到 IP,就不要乱动;
* 不要频繁扫描 Wi-Fi;
* 不要频繁断开当前 Wi-Fi 再切换;
* 不要在信号短暂波动时马上切换;
* 需要有合理的重试间隔和冷却时间;
* 避免脚本疯狂循环导致耗电、发热、刷屏日志。

---

## 6. Wi-Fi 是否连接成功的判断

我希望脚本不要只看 Wi-Fi 开关是否打开。

真正有意义的是:

```text
手机是否已经通过 wlan0 拿到了 IP 地址
```

请你设计连接成功判断逻辑。

比如:

* wlan0 拿到 IPv4;
* 或者 wlan0 拿到全局 IPv6;
* 或者两者都可以。

暂时不需要做外网连通性检测,比如 ping 某个公网 IP。
因为公网 ping 可能受 DNS、运营商、防火墙、网络环境影响,不适合作为 Wi-Fi 是否连接成功的唯一依据。

---

## 7. 日志要求

脚本必须输出清晰日志,方便我排查问题。

日志中需要包含:

* 时间,格式为 `YYYY-MM-DD HH:mm:ss`;
* 日志级别,比如 `INFO`、`OK`、`ERROR`;
* 当前操作名称,比如:

  * 启动脚本
  * 等待系统启动
  * 打开 Wi-Fi 开关
  * 检查 Wi-Fi 状态
  * 等待系统自动连接
  * 选择已保存 Wi-Fi
  * 重连 Wi-Fi
  * 进入守护循环
* 错误原因;
* 关键命令的返回值和输出,方便排查。

日志示例可以类似:

```text
2026-05-15 22:01:23 [OK] [启动脚本] 获取 lock 成功,当前实例开始运行
2026-05-15 22:01:40 [OK] [等待系统启动] 系统启动完成
2026-05-15 22:01:52 [INFO] [打开WiFi开关] 检测到 Wi-Fi 关闭,开始打开 Wi-Fi
2026-05-15 22:01:55 [OK] [打开WiFi开关] Wi-Fi 已进入可连接状态
2026-05-15 22:02:36 [OK] [检查WiFi状态] Wi-Fi 已连接,当前WiFi=xxx,已拿到 IP
```

日志文件也要限制大小,避免长期运行后日志无限变大。
请你在脚本里设计日志轮转,例如超过 1MB 后只保留最后若干行,并且在注释中写清楚单位。

---

## 8. 日志中的 Wi-Fi 名称和隐私

如果能解析当前 Wi-Fi 名称,日志中应该显示 Wi-Fi 名称,而不是 MAC 地址。

如果某些系统状态中出现 BSSID / MAC 地址,请隐藏或过滤掉。

例如不要输出:

```text
00:00:00:00:00:00 nid: 0 state: ASSOCIATING
```

这不是 Wi-Fi 名称,而是系统连接状态。

如果暂时解析不到当前 Wi-Fi 名称,但已经拿到 wlan0 的 IP,请不要误判为失败。
这种情况下可以写:

```text
Wi-Fi 已连接,已拿到 IP,但当前 Wi-Fi 名称暂时无法解析
```

或者如果配置列表里只有一个目标 Wi-Fi,也可以说明是根据配置推断的名称。

---

## 9. 防止重复运行

这个脚本是常驻守护脚本,不是一次性脚本。

所以请你加入 lock 机制,防止多个相同脚本同时运行。

我希望你使用适合 Android / Magisk 环境的方式,比如:

* 使用目录锁;
* lock 放在 `/dev` 这种重启后会自动清理的临时目录;
* 不要用容易残留的持久 PID 文件;
* 如果脚本正常退出,要自动释放 lock;
* 如果检测到已有实例运行,当前脚本应该退出,并写入日志。

请你解释一下 lock 的作用和为什么这样设计。

---

## 10. 守护循环

脚本不应该只在开机时执行一次。

它应该是常驻守护脚本。

大概逻辑应该是:

```text
等待系统启动完成
        ↓
打开 Wi-Fi
        ↓
等待系统自动连接已保存 Wi-Fi
        ↓
如果连接成功,进入守护循环
        ↓
每隔一段时间检查 Wi-Fi 是否仍然连接
        ↓
如果 Wi-Fi 正常,什么都不做
        ↓
如果 Wi-Fi 不正常,开始自动修复
        ↓
重新打开 Wi-Fi / 等待自动连接 / 必要时选择已保存 Wi-Fi
        ↓
失败则等待冷却时间后重试
```

---

# 请你特别注意

我上面的想法可能并不成熟,所以请你不要只是机械照着我的描述写。

请你先站在专业 Android 开发人员和 Linux 运维人员的角度,帮我完善整体逻辑。

在正式给代码之前,请你先做一次简短但准确的设计分析,包括:

1. 推荐目录结构;
2. 为什么 service.d 里只放启动器;
3. Wi-Fi 守护脚本应该承担哪些职责;
4. 哪些事情不应该放在 Wi-Fi 守护脚本里,比如 IPv6 检测、DDNS 同步;
5. 如何判断 Wi-Fi 是否真的可用;
6. 如何避免重复运行;
7. 哪些命令在 Android 上可能存在兼容性问题,脚本应该如何处理。

然后再给我完整代码。

---

# 输出要求

请输出:

## 第一部分:设计说明

用通俗易懂的话解释整体方案。

## 第二部分:目录结构

给出推荐目录结构,并说明每个文件的作用。

## 第三部分:Magisk 启动器脚本

给出完整代码,可以直接复制使用。

## 第四部分:Wi-Fi 守护脚本

给出完整代码,可以直接复制使用。

## 第五部分:安装和测试命令

包括:

* 创建目录;
* 写入脚本;
* 添加执行权限;
* 手动启动测试;
* 查看日志;
* 停止脚本;
* 清理 lock;
* 如何确认脚本是否正在运行。

---

# 我的最终目标

我希望得到的是一套适合 Android Root + Magisk + 手机服务器场景的 Wi-Fi 稳定连接方案。

重点是:

* 稳定;
* 易排查;
* 日志清晰;
* 不重复运行;
* 不乱切 Wi-Fi;
* 不把所有功能写成一个超级大脚本;
* 后续方便扩展 IPv6 检测、DDNS 更新、Debian / SSH 守护等功能。

请你根据这些目标,帮我设计并实现一个更专业、更稳定、更容易维护的版本。

1 个帖子 - 1 位参与者

阅读完整话题

来源: LinuxDo 最新话题查看原文