本期主题:解决ipv6稳定性和DDNS同步问题
前两期回顾:
【超详细】手机搭建服务器 · 第 一 期
【超详细】手机搭建服务器 · 第 二 期
【超详细】手机搭建服务器 · 第 三 期
[!warning]第三期的内容可以不用管
第三期的内容主要是做一个记录,告诉大家在实现手机做服务器这个事情上,有这么一个环节需要去做,如果第三期没有实现成功,不用管,可以直接忽略,然后用本期的代码。因为这一期会有新的代码变更。用本期的代码就好了
前言:
[!note]域名服务商推荐
在DDNS同步这个问题上,我推荐大家去华为购买服务器,因为我发现,只有华为的服务器能够做到给免费用户的TTL解析值降低到1s。阿里云是10分钟,腾讯云是5分钟。如果要设置1s,就需要成为付费用户。之前我一直用阿里云的服务器。后来因为这个原因改成华为了。
教程开始
一、如何让ipv6一直保持健
这里先说一下,当你的手机连接wifi以后,不是说你的ipv6就一直可以用了,因为ipv6是有有效期的,一旦过了有效期。ipv6就被废弃了,需要新的ipv6地址。所以ipv6不是大家想的,只要一直连着Wi-Fi,ipv6就是永远有效的。所以第一步,就是如何让手机的ipv6处于一直可用的状态。
1. 打开MT管理器,进入
/data/adb/service.d/文件夹,更新99-phone-server.sh代码
#!/system/bin/sh
#
# 99-wifi-keeper.sh
# Magisk service.d 启动脚本
#
BASE_DIR="/data/local/wifi-keeper"
MAIN_SCRIPT="$BASE_DIR/wifi-keeper.sh"
LAUNCH_LOG="$BASE_DIR/service.d-launcher.log"
mkdir -p "$BASE_DIR" 2>/dev/null
echo "============================================================" >> "$LAUNCH_LOG"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] service.d launcher started" >> "$LAUNCH_LOG"
echo "MAIN_SCRIPT=$MAIN_SCRIPT" >> "$LAUNCH_LOG"
if [ ! -f "$MAIN_SCRIPT" ]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: main script not found" >> "$LAUNCH_LOG"
exit 0
fi
chmod 755 "$MAIN_SCRIPT" 2>/dev/null
(
echo "[$(date '+%Y-%m-%d %H:%M:%S')] launching main script in background" >> "$LAUNCH_LOG"
if command -v nohup >/dev/null 2>&1; then
nohup sh "$MAIN_SCRIPT" >/dev/null 2>&1 &
else
sh "$MAIN_SCRIPT" >/dev/null 2>&1 &
fi
echo "[$(date '+%Y-%m-%d %H:%M:%S')] launcher finished" >> "$LAUNCH_LOG"
) &
exit 0
2 进入
/data/local/文件夹,新建wifi-keeper文件夹,然后进入wifi-server文件夹再新建wifi-keeper.sh文件;输入以下内容并保存:
#!/system/bin/sh
#
# wifi-keeper.sh
#
# 作用:
# 1. 开机后自动保障 Wi-Fi 连接。
# 2. 使用 cmd wifi connect-network 主动连接指定 Wi-Fi。
# 3. 不使用 wpa_cli / add-suggestion / add-request。
# 4. 获取 2 开头、非 temporary、带 mngtmpaddr 的长期 IPv6。
# 5. 每隔固定时间检查 IPv6 健康度。
# 6. 如果 IPv6 不健康,则重启 Wi-Fi,再重新连接。
# 7. 如果有效 IPv6 相比上一次发生变化,则执行 DDNS 脚本。
#
# ============================================================
# 一、基础配置
# ============================================================
# 开机后等待多久再开始执行主逻辑。
# 原因:
# Android 刚开机时,Wi-Fi 服务、网络服务、Magisk service.d 可能还没有完全稳定。
# 这里等待 10 秒,可以减少开机初期误判。
BOOT_DELAY_SECONDS=10
# IPv6 健康度检查间隔。
# 每隔多少秒检查一次是否仍然存在有效 IPv6。
# 你当前要求是每 10 秒检查一次。
IPV6_HEALTH_INTERVAL=10
# 打开 Wi-Fi 后,最多等待多少秒确认 Wi-Fi 已经开启。
# 如果超过这个时间仍然检测不到 Wi-Fi 开启,就认为本轮失败。
WAIT_WIFI_ON_SECONDS=40
# 扫描 Wi-Fi 后等待多少秒,让扫描结果刷新。
# Android 的扫描结果有时不是马上返回,适当等待更稳。
WAIT_SCAN_SECONDS=8
# 发起 Wi-Fi 连接后,最多等待多少秒确认已经连接成功。
# 如果第一个 Wi-Fi 在这个时间内没有连接成功,就尝试下一个 Wi-Fi。
WAIT_CONNECT_SECONDS=60
# Wi-Fi 连接成功后,最多等待多少秒获取有效 IPv6。
# 有些路由器下发 IPv6 会比 Wi-Fi 连接慢,所以这里不能太短。
WAIT_IPV6_SECONDS=80
# 各类等待循环中的检查间隔。
# 比如每 2 秒检查一次 Wi-Fi 是否开启、是否连接成功、IPv6 是否出现。
CHECK_INTERVAL=2
# 当 IPv6 不健康时,需要重启 Wi-Fi。
# 关闭 Wi-Fi 后等待多少秒再重新打开。
WIFI_RESTART_OFF_SECONDS=5
# 连接 Wi-Fi 时是否尝试使用固定 MAC,而不是随机 MAC。
# 1 = 优先使用 -r none
# 0 = 不使用 -r none
#
# 对于手机做服务器,建议为 1。
# 好处:
# 路由器里看到的 MAC 更稳定;
# DHCP 绑定、设备识别、IPv6 分配可能更稳定。
PREFER_MAC_RANDOMIZATION_NONE=1
# 日志保留天数。
# 超过这个天数的动作日志会被自动删除。
LOG_RETENTION_DAYS=7
# 是否在首次获取到有效 IPv6 时执行 DDNS 脚本。
# 1 = 执行
# 0 = 只记录,不执行
#
# 建议为 1。
# 因为手机重启后,即使 IPv6 和上次一样,也可能需要重新同步一次 DDNS。
DDNS_ON_FIRST_IP=1
# DDNS 脚本路径。
# 当检测到有效 IPv6 发生变化时,会执行这个脚本。
#
# 执行方式:
# sh /data/local/ipv6-ddns/ipv6-ddns.sh 当前IPv6地址
#
# 同时也会传递环境变量:
# CURRENT_IPV6
# VALID_IPV6
# WIFI_SSID
DDNS_SCRIPT="/data/local/ipv6-ddns/ipv6-ddns.sh"
# IPv6 历史记录最多保留多少行。
# 这个不是主日志,只是专门记录每次有效 IPv6 变化。
IPV6_HISTORY_MAX_LINES=500
# ============================================================
# 二、Wi-Fi 列表
# ============================================================
#
# 格式:
# WiFi名称|WiFi密码|加密方式
#
# 加密方式:
# WPA2 写 WPA2 即可,脚本内部会自动转成 cmd wifi 需要的 wpa2。
# 如果是开放网络,可以写 open。
#
# 说明:
# 1. 每一行代表一个 Wi-Fi。
# 2. 按照从上到下的顺序优先连接。
# 3. 第一个连接失败,才会尝试下一个。
# 4. 不管系统是否保存过 Wi-Fi,都会用这里写死的名称和密码主动连接。
#
WIFI_LIST='
CMCC-Semmering|Semmering|WPA2
'
# ============================================================
# 三、路径配置
# ============================================================
SELF="$0"
case "$SELF" in
/*)
SELF_PATH="$SELF"
;;
*)
SELF_PATH="$(pwd)/$SELF"
;;
esac
SCRIPT_DIR="$(cd "$(dirname "$SELF_PATH")" 2>/dev/null && pwd)"
[ -n "$SCRIPT_DIR" ] || SCRIPT_DIR="$(pwd)"
# 主日志。
# 最新动作在最上面。
LOG_FILE="$SCRIPT_DIR/wifi-keeper.log"
# 临时目录。
# 用于临时存放单个动作日志,动作结束后再整体插入到 LOG_FILE 顶部。
# 这个目录不是业务数据,脚本停止后可以删除。
TEMP_DIR="$SCRIPT_DIR/wifi-keeper-temp"
# 保存有效 IPv6 记录。
# 格式:
# YYYY-MM-DD HH:mm:ss|IPv6地址|来源动作|previous=上一次IPv6
#
# 说明:
# 1. 只保留这一个 IPv6 记录文件,不再单独保存 last-valid-ipv6.txt。
# 2. 脚本会从这个文件尾部读取最新 IPv6,用于和本次 IPv6 做 diff。
# 3. 只有首次记录或者 IPv6 发生变化时,才会追加新记录。
IPV6_HISTORY_FILE="$SCRIPT_DIR/valid-ipv6-history.txt"
mkdir -p "$TEMP_DIR" 2>/dev/null
touch "$LOG_FILE" "$IPV6_HISTORY_FILE" 2>/dev/null
if [ ! -w "$LOG_FILE" ]; then
echo "ERROR: 无法写入日志文件:$LOG_FILE"
echo "建议把脚本放到可写目录,例如:/data/local/phone-server/wifi-keeper.sh"
exit 1
fi
# ============================================================
# 四、全局变量
# ============================================================
WIFI_IFACE="wlan0"
ACTION_FILE=""
ACTION_NAME=""
ACTION_SEQ=0
LAST_CONNECTED_SSID=""
LAST_VALID_IPV6=""
HEALTH_FAIL_REASON=""
# ============================================================
# 五、基础工具函数
# ============================================================
now_ts() {
date '+%Y-%m-%d %H:%M:%S' 2>/dev/null
}
now_epoch() {
date '+%s' 2>/dev/null
}
print_line() {
printf '%s\n' "$*"
}
get_wifi_iface() {
IFACE="$(ip link 2>/dev/null \
| sed -n 's/^[0-9][0-9]*: \(wlan[0-9][^: ]*\).*/\1/p' \
| head -n 1 \
| sed 's/@.*//')"
[ -n "$IFACE" ] || IFACE="wlan0"
print_line "$IFACE"
}
normalize_security() {
SEC="$(print_line "$1" | tr 'A-Z' 'a-z')"
case "$SEC" in
wap2|wpa-psk|psk)
SEC="wpa2"
;;
wpa2|wpa3|open|owe)
;;
*)
SEC="wpa2"
;;
esac
print_line "$SEC"
}
# ============================================================
# 六、日志函数
# ============================================================
start_action() {
ACTION_NAME="$1"
ACTION_SEQ=$((ACTION_SEQ + 1))
TS="$(now_ts)"
EPOCH="$(now_epoch)"
[ -n "$EPOCH" ] || EPOCH="0"
ACTION_FILE="$TEMP_DIR/action.$$.${ACTION_SEQ}.log"
{
print_line "##### ACTION_BLOCK_BEGIN #####"
print_line "============================================================"
print_line "===== $TS | $ACTION_NAME | START ====="
print_line "============================================================"
print_line "LOG_EPOCH: $EPOCH"
} > "$ACTION_FILE"
}
log_detail() {
TS="$(now_ts)"
if [ -n "$ACTION_FILE" ]; then
print_line "[$TS] $*" >> "$ACTION_FILE"
else
print_line "[$TS] $*" >> "$LOG_FILE"
fi
}
log_multiline() {
PREFIX="$1"
CONTENT="$2"
if [ -n "$CONTENT" ]; then
print_line "$CONTENT" | while IFS= read -r LINE || [ -n "$LINE" ]; do
log_detail "$PREFIX$LINE"
done
else
log_detail "${PREFIX}<empty>"
fi
}
prune_log_by_days() {
[ "$LOG_RETENTION_DAYS" -gt 0 ] 2>/dev/null || return 0
NOW="$(now_epoch)"
[ -n "$NOW" ] || return 0
CUTOFF=$((NOW - LOG_RETENTION_DAYS * 86400))
PRUNED="$TEMP_DIR/pruned.$$.log"
awk -v cutoff="$CUTOFF" '
BEGIN {
inside = 0
block = ""
keep = 1
}
$0 == "##### ACTION_BLOCK_BEGIN #####" {
if (inside == 1 && keep == 1) {
printf "%s", block
}
inside = 1
block = $0 "\n"
keep = 1
next
}
inside == 1 {
block = block $0 "\n"
if ($1 == "LOG_EPOCH:") {
if (($2 + 0) < cutoff) {
keep = 0
}
}
if ($0 == "##### ACTION_BLOCK_END #####") {
if (keep == 1) {
printf "%s", block
}
inside = 0
block = ""
keep = 1
}
next
}
{
print
}
END {
if (inside == 1 && keep == 1) {
printf "%s", block
}
}
' "$LOG_FILE" > "$PRUNED" 2>/dev/null
if [ -s "$PRUNED" ] || [ -f "$PRUNED" ]; then
mv "$PRUNED" "$LOG_FILE" 2>/dev/null
else
rm -f "$PRUNED" 2>/dev/null
fi
}
end_action() {
RESULT="$1"
TS="$(now_ts)"
{
print_line "[$TS] RESULT: $RESULT"
print_line "============================================================"
print_line "===== $TS | $ACTION_NAME | END: $RESULT ====="
print_line "============================================================"
print_line "##### ACTION_BLOCK_END #####"
print_line ""
} >> "$ACTION_FILE"
MERGED="$TEMP_DIR/merged.$$.log"
cat "$ACTION_FILE" "$LOG_FILE" > "$MERGED" 2>/dev/null && mv "$MERGED" "$LOG_FILE"
rm -f "$ACTION_FILE" 2>/dev/null
ACTION_FILE=""
ACTION_NAME=""
prune_log_by_days
}
run_cmd_args() {
SHOW="$1"
shift
log_detail "CMD : $SHOW"
OUT="$("$@" 2>&1)"
RC="$?"
log_multiline "OUT : " "$OUT"
log_detail "RC : $RC"
return "$RC"
}
# ============================================================
# 七、Wi-Fi 状态判断
# ============================================================
is_wifi_enabled() {
STATUS="$(cmd wifi status 2>/dev/null)"
print_line "$STATUS" | grep -qi "Wifi is disabled" && return 1
print_line "$STATUS" | grep -qi "Wi-Fi is disabled" && return 1
print_line "$STATUS" | grep -qi "Wifi is enabled" && return 0
print_line "$STATUS" | grep -qi "Wi-Fi is enabled" && return 0
print_line "$STATUS" | grep -qi "Wifi is connected" && return 0
print_line "$STATUS" | grep -qi "Wi-Fi is connected" && return 0
return 1
}
is_connected_to_ssid() {
TARGET_SSID="$1"
STATUS="$(cmd wifi status 2>/dev/null)"
print_line "$STATUS" | grep -qi "Wifi is disabled" && return 1
print_line "$STATUS" | grep -qi "Wi-Fi is disabled" && return 1
if print_line "$STATUS" | grep -qi "Wifi is connected" \
&& print_line "$STATUS" | grep -F "\"$TARGET_SSID\"" >/dev/null 2>&1; then
return 0
fi
if print_line "$STATUS" | grep -qi "Wi-Fi is connected" \
&& print_line "$STATUS" | grep -F "\"$TARGET_SSID\"" >/dev/null 2>&1; then
return 0
fi
if command -v iw >/dev/null 2>&1; then
IW_STATUS="$(iw dev "$WIFI_IFACE" link 2>/dev/null)"
print_line "$IW_STATUS" | grep -q "^Connected to " || return 1
print_line "$IW_STATUS" | grep -F "SSID: $TARGET_SSID" >/dev/null 2>&1 && return 0
fi
return 1
}
get_connected_ssid() {
STATUS="$(cmd wifi status 2>/dev/null)"
SSID_NOW="$(print_line "$STATUS" | sed -n 's/.*connected to "\([^"]*\)".*/\1/p' | head -n 1)"
if [ -n "$SSID_NOW" ]; then
print_line "$SSID_NOW"
return 0
fi
if command -v iw >/dev/null 2>&1; then
iw dev "$WIFI_IFACE" link 2>/dev/null | sed -n 's/^[[:space:]]*SSID: //p' | head -n 1
return 0
fi
print_line ""
}
is_connected_to_any_configured_wifi() {
LIST_FILE="$TEMP_DIR/wifi-list-any.$$.txt"
printf '%s\n' "$WIFI_LIST" > "$LIST_FILE"
while IFS='|' read -r SSID PASS SEC EXTRA || [ -n "$SSID" ]; do
[ -z "$SSID" ] && continue
case "$SSID" in
\#*)
continue
;;
esac
if is_connected_to_ssid "$SSID"; then
rm -f "$LIST_FILE" 2>/dev/null
return 0
fi
done < "$LIST_FILE"
rm -f "$LIST_FILE" 2>/dev/null
return 1
}
# ============================================================
# 八、Wi-Fi 操作函数
# ============================================================
log_status() {
log_detail "---------- 当前 Wi-Fi / IP 状态 ----------"
run_cmd_args "cmd wifi status" cmd wifi status
if command -v iw >/dev/null 2>&1; then
run_cmd_args "iw dev $WIFI_IFACE link" iw dev "$WIFI_IFACE" link
else
log_detail "INFO : 当前系统没有 iw 命令,跳过 iw dev link"
fi
run_cmd_args "ip addr show dev $WIFI_IFACE" ip addr show dev "$WIFI_IFACE"
run_cmd_args "ip -6 addr show dev $WIFI_IFACE" ip -6 addr show dev "$WIFI_IFACE"
log_detail "---------- 当前状态结束 ----------"
}
wait_wifi_enabled() {
log_detail "INFO : 等待 Wi-Fi 开启,最多 ${WAIT_WIFI_ON_SECONDS}s"
ELAPSED=0
while [ "$ELAPSED" -le "$WAIT_WIFI_ON_SECONDS" ]; do
if is_wifi_enabled; then
log_detail "OK : Wi-Fi 已开启"
return 0
fi
sleep "$CHECK_INTERVAL"
ELAPSED=$((ELAPSED + CHECK_INTERVAL))
log_detail "WAIT : Wi-Fi 尚未确认开启,已等待 ${ELAPSED}s"
done
log_detail "ERROR: Wi-Fi 开启超时"
return 1
}
ensure_wifi_on() {
if is_wifi_enabled; then
log_detail "OK : Wi-Fi 当前已经开启,不重复打开"
return 0
fi
log_detail "INFO : Wi-Fi 当前关闭,开始打开"
if command -v svc >/dev/null 2>&1; then
run_cmd_args "svc wifi enable" svc wifi enable
else
log_detail "WARN : 当前系统没有 svc 命令,跳过 svc wifi enable"
fi
run_cmd_args "cmd wifi set-wifi-enabled enabled" cmd wifi set-wifi-enabled enabled
wait_wifi_enabled
}
scan_wifi() {
log_detail "INFO : 开始扫描周围 Wi-Fi"
run_cmd_args "cmd wifi start-scan" cmd wifi start-scan
log_detail "INFO : 等待扫描结果刷新 ${WAIT_SCAN_SECONDS}s"
sleep "$WAIT_SCAN_SECONDS"
SCAN_RESULT="$(cmd wifi list-scan-results 2>&1)"
log_detail "CMD : cmd wifi list-scan-results"
log_multiline "SCAN : " "$SCAN_RESULT"
LIST_FILE="$TEMP_DIR/wifi-list-scan.$$.txt"
printf '%s\n' "$WIFI_LIST" > "$LIST_FILE"
while IFS='|' read -r SSID PASS SEC EXTRA || [ -n "$SSID" ]; do
[ -z "$SSID" ] && continue
case "$SSID" in
\#*)
continue
;;
esac
if print_line "$SCAN_RESULT" | grep -F "$SSID" >/dev/null 2>&1; then
log_detail "OK : 扫描结果中发现配置 Wi-Fi:$SSID"
else
log_detail "WARN : 扫描结果中没有发现配置 Wi-Fi:$SSID;仍会继续尝试连接"
fi
done < "$LIST_FILE"
rm -f "$LIST_FILE" 2>/dev/null
}
wait_connected_to_ssid() {
TARGET_SSID="$1"
log_detail "INFO : 等待连接到 Wi-Fi:$TARGET_SSID,最多 ${WAIT_CONNECT_SECONDS}s"
ELAPSED=0
while [ "$ELAPSED" -le "$WAIT_CONNECT_SECONDS" ]; do
if is_connected_to_ssid "$TARGET_SSID"; then
LAST_CONNECTED_SSID="$TARGET_SSID"
log_detail "OK : 已连接到目标 Wi-Fi:$TARGET_SSID"
return 0
fi
sleep "$CHECK_INTERVAL"
ELAPSED=$((ELAPSED + CHECK_INTERVAL))
log_detail "WAIT : 尚未连接到 $TARGET_SSID,已等待 ${ELAPSED}s"
done
log_detail "ERROR: 连接 $TARGET_SSID 超时"
return 1
}
connect_one_wifi() {
TARGET_SSID="$1"
TARGET_PASS="$2"
TARGET_SEC="$(normalize_security "$3")"
log_detail "------------------------------------------------------------"
log_detail "INFO : 尝试连接 Wi-Fi"
log_detail "SSID : $TARGET_SSID"
log_detail "SEC : $TARGET_SEC"
log_detail "NOTE : 不管系统是否保存过该 Wi-Fi,都会使用 SSID + 密码主动连接"
if [ "$TARGET_SEC" = "open" ] || [ "$TARGET_SEC" = "owe" ]; then
if [ "$PREFER_MAC_RANDOMIZATION_NONE" = "1" ]; then
log_detail "CMD : cmd wifi connect-network \"$TARGET_SSID\" $TARGET_SEC -r none"
OUT="$(cmd wifi connect-network "$TARGET_SSID" "$TARGET_SEC" -r none 2>&1)"
RC="$?"
log_multiline "OUT : " "$OUT"
log_detail "RC : $RC"
if [ "$RC" != "0" ]; then
log_detail "WARN : 带 -r none 的 open/owe 连接失败,回退到不带 -r"
run_cmd_args "cmd wifi connect-network \"$TARGET_SSID\" $TARGET_SEC" \
cmd wifi connect-network "$TARGET_SSID" "$TARGET_SEC"
fi
else
run_cmd_args "cmd wifi connect-network \"$TARGET_SSID\" $TARGET_SEC" \
cmd wifi connect-network "$TARGET_SSID" "$TARGET_SEC"
fi
else
if [ "$PREFER_MAC_RANDOMIZATION_NONE" = "1" ]; then
log_detail "CMD : cmd wifi connect-network \"$TARGET_SSID\" $TARGET_SEC \"******\" -r none"
OUT="$(cmd wifi connect-network "$TARGET_SSID" "$TARGET_SEC" "$TARGET_PASS" -r none 2>&1)"
RC="$?"
log_multiline "OUT : " "$OUT"
log_detail "RC : $RC"
if [ "$RC" != "0" ]; then
log_detail "WARN : 带 -r none 的连接失败,回退到不带 -r"
log_detail "CMD : cmd wifi connect-network \"$TARGET_SSID\" $TARGET_SEC \"******\""
OUT="$(cmd wifi connect-network "$TARGET_SSID" "$TARGET_SEC" "$TARGET_PASS" 2>&1)"
RC="$?"
log_multiline "OUT : " "$OUT"
log_detail "RC : $RC"
fi
else
log_detail "CMD : cmd wifi connect-network \"$TARGET_SSID\" $TARGET_SEC \"******\""
OUT="$(cmd wifi connect-network "$TARGET_SSID" "$TARGET_SEC" "$TARGET_PASS" 2>&1)"
RC="$?"
log_multiline "OUT : " "$OUT"
log_detail "RC : $RC"
fi
fi
wait_connected_to_ssid "$TARGET_SSID"
}
connect_by_wifi_list() {
LIST_FILE="$TEMP_DIR/wifi-list-connect.$$.txt"
printf '%s\n' "$WIFI_LIST" > "$LIST_FILE"
INDEX=0
while IFS='|' read -r SSID PASS SEC EXTRA || [ -n "$SSID" ]; do
[ -z "$SSID" ] && continue
case "$SSID" in
\#*)
continue
;;
esac
INDEX=$((INDEX + 1))
log_detail "INFO : 开始尝试第 ${INDEX} 个 Wi-Fi:$SSID"
if connect_one_wifi "$SSID" "$PASS" "$SEC"; then
log_detail "OK : 第 ${INDEX} 个 Wi-Fi 连接成功:$SSID"
rm -f "$LIST_FILE" 2>/dev/null
return 0
fi
log_detail "WARN : 第 ${INDEX} 个 Wi-Fi 连接失败:$SSID,准备尝试下一个"
done < "$LIST_FILE"
rm -f "$LIST_FILE" 2>/dev/null
log_detail "ERROR: Wi-Fi 列表全部尝试失败"
return 1
}
# ============================================================
# 九、IPv6 相关函数
# ============================================================
get_valid_ipv6() {
ip -6 addr show dev "$WIFI_IFACE" scope global 2>/dev/null | while IFS= read -r ADDR_LINE || [ -n "$ADDR_LINE" ]; do
# 只处理 inet6 地址行
set -- $ADDR_LINE
[ "$1" = "inet6" ] || continue
ADDR_CIDR="$2"
IPV6_ADDR="${ADDR_CIDR%%/*}"
# 读取下一行 lifetime 信息
IFS= read -r LFT_LINE || LFT_LINE=""
# 规则 1:必须是 2 开头
case "$IPV6_ADDR" in
2*)
;;
*)
continue
;;
esac
# 规则 2:必须是 mngtmpaddr
echo "$ADDR_LINE" | grep -q "mngtmpaddr" || continue
# 规则 3:不能是 temporary
echo "$ADDR_LINE" | grep -q "temporary" && continue
# 规则 4:preferred_lft 不能是 0sec
echo "$LFT_LINE" | grep -q "preferred_lft 0sec" && continue
echo "$IPV6_ADDR"
exit 0
done | head -n 1
}
log_ipv6_detail() {
IPV6_ALL="$(ip -6 addr show dev "$WIFI_IFACE" 2>&1)"
IPV6_GLOBAL="$(ip -6 addr show dev "$WIFI_IFACE" scope global 2>&1)"
VALID_IPV6="$(get_valid_ipv6)"
log_detail "---------- IPv6 地址详情 ----------"
log_multiline "IPv6-ALL : " "$IPV6_ALL"
log_multiline "IPv6-GLOBAL : " "$IPV6_GLOBAL"
if [ -n "$VALID_IPV6" ]; then
log_detail "IPv6-VALID : $VALID_IPV6"
log_detail "IPv6-RULE : 命中规则:2 开头 + 非 temporary + mngtmpaddr + preferred_lft 非 0sec"
else
log_detail "IPv6-VALID : <none>"
log_detail "IPv6-RULE : 未找到 2 开头 + 非 temporary + mngtmpaddr 的长期 IPv6"
print_line "$IPV6_GLOBAL" | grep -q "temporary" && \
log_detail "IPv6-INFO : 存在 temporary IPv6,但不作为服务器长期地址"
print_line "$IPV6_GLOBAL" | grep -E "inet6 f" >/dev/null 2>&1 && \
log_detail "IPv6-INFO : 存在 f 开头 IPv6,不符合要求"
print_line "$IPV6_GLOBAL" | grep -q "inet6" || \
log_detail "IPv6-INFO : 当前没有 scope global IPv6"
fi
log_detail "---------- IPv6 地址详情结束 ----------"
}
wait_valid_ipv6() {
log_detail "INFO : 等待获取有效 IPv6,最多 ${WAIT_IPV6_SECONDS}s"
ELAPSED=0
while [ "$ELAPSED" -le "$WAIT_IPV6_SECONDS" ]; do
VALID_IPV6="$(get_valid_ipv6)"
if [ -n "$VALID_IPV6" ]; then
LAST_VALID_IPV6="$VALID_IPV6"
log_detail "OK : 已获取有效 IPv6:$VALID_IPV6"
return 0
fi
sleep "$CHECK_INTERVAL"
ELAPSED=$((ELAPSED + CHECK_INTERVAL))
log_detail "WAIT : 尚未获取有效 IPv6,已等待 ${ELAPSED}s"
done
log_detail "ERROR: 获取有效 IPv6 超时"
return 1
}
trim_ipv6_history() {
[ -f "$IPV6_HISTORY_FILE" ] || return 0
TMP_HISTORY="$TEMP_DIR/ipv6-history.$$.tmp"
tail -n "$IPV6_HISTORY_MAX_LINES" "$IPV6_HISTORY_FILE" > "$TMP_HISTORY" 2>/dev/null \
&& mv "$TMP_HISTORY" "$IPV6_HISTORY_FILE" 2>/dev/null
rm -f "$TMP_HISTORY" 2>/dev/null
}
ipv6_change_action() {
CURRENT_IPV6="$1"
SOURCE_ACTION="$2"
[ -n "$CURRENT_IPV6" ] || return 0
PREVIOUS_LINE=""
PREVIOUS_IPV6=""
# 只从 valid-ipv6-history.txt 的最后一行读取上一次 IPv6,
# 不再使用独立的 last-valid-ipv6.txt 文件。
if [ -f "$IPV6_HISTORY_FILE" ]; then
PREVIOUS_LINE="$(tail -n 1 "$IPV6_HISTORY_FILE" 2>/dev/null)"
PREVIOUS_IPV6="$(print_line "$PREVIOUS_LINE" | awk -F'|' '{print $2}')"
fi
start_action "IPv6地址变更检查"
TS="$(now_ts)"
CONNECTED_SSID="$(get_connected_ssid)"
log_detail "来源动作: $SOURCE_ACTION"
log_detail "当前连接 Wi-Fi: ${CONNECTED_SSID:-<none>}"
log_detail "有效 IPv6 记录文件: $IPV6_HISTORY_FILE"
log_detail "上一条记录: ${PREVIOUS_LINE:-<none>}"
log_detail "上一次 IPv6: ${PREVIOUS_IPV6:-<none>}"
log_detail "本次 IPv6: $CURRENT_IPV6"
if [ -n "$PREVIOUS_IPV6" ] && [ "$CURRENT_IPV6" = "$PREVIOUS_IPV6" ]; then
log_detail "DIFF : IPv6 未变化:$CURRENT_IPV6"
log_detail "SAVE : IPv6 未变化,不追加有效 IPv6 记录文件"
log_detail "DDNS : IPv6 未变化,不执行 DDNS 脚本"
end_action "unchanged"
return 0
fi
if [ -z "$PREVIOUS_IPV6" ]; then
log_detail "DIFF : 这是首次记录有效 IPv6"
if [ "$DDNS_ON_FIRST_IP" = "1" ]; then
SHOULD_RUN_DDNS=1
log_detail "DDNS : DDNS_ON_FIRST_IP=1,首次获取 IPv6 也会执行 DDNS 脚本"
else
SHOULD_RUN_DDNS=0
log_detail "DDNS : DDNS_ON_FIRST_IP=0,首次获取 IPv6 只记录,不执行 DDNS 脚本"
fi
else
SHOULD_RUN_DDNS=1
log_detail "DIFF : IPv6 已变化:$PREVIOUS_IPV6 -> $CURRENT_IPV6"
fi
if [ "$SHOULD_RUN_DDNS" != "1" ]; then
print_line "$TS|$CURRENT_IPV6|$SOURCE_ACTION|previous=${PREVIOUS_IPV6:-none}|ddns=not_required" >> "$IPV6_HISTORY_FILE" 2>/dev/null
log_detail "SAVE : DDNS 不需要执行,已追加有效 IPv6 记录文件:$IPV6_HISTORY_FILE"
trim_ipv6_history
end_action "done_without_ddns"
return 0
fi
if [ ! -f "$DDNS_SCRIPT" ]; then
log_detail "DDNS : DDNS 脚本不存在,暂不执行:$DDNS_SCRIPT"
log_detail "DDNS : 本次 IPv6 不会写入有效 IPv6 记录文件,等待下次健康检查继续重试"
end_action "ddns_script_missing"
return 1
fi
log_detail "DDNS : 准备执行 DDNS 脚本"
log_detail "CMD : CURRENT_IPV6=\"$CURRENT_IPV6\" VALID_IPV6=\"$CURRENT_IPV6\" WIFI_SSID=\"$CONNECTED_SSID\" sh \"$DDNS_SCRIPT\" \"$CURRENT_IPV6\""
DDNS_OUT="$(
CURRENT_IPV6="$CURRENT_IPV6" \
VALID_IPV6="$CURRENT_IPV6" \
WIFI_SSID="$CONNECTED_SSID" \
sh "$DDNS_SCRIPT" "$CURRENT_IPV6" 2>&1
)"
DDNS_RC="$?"
log_multiline "DDNS-OUT : " "$DDNS_OUT"
log_detail "DDNS-RC : $DDNS_RC"
if [ "$DDNS_RC" = "0" ]; then
log_detail "DDNS : DDNS 脚本执行成功"
print_line "$TS|$CURRENT_IPV6|$SOURCE_ACTION|previous=${PREVIOUS_IPV6:-none}|ddns=success" >> "$IPV6_HISTORY_FILE" 2>/dev/null
log_detail "SAVE : DDNS 成功后,已追加有效 IPv6 记录文件:$IPV6_HISTORY_FILE"
trim_ipv6_history
end_action "done"
return 0
fi
log_detail "DDNS : DDNS 脚本执行失败"
log_detail "SAVE : DDNS 失败,本次 IPv6 不写入有效 IPv6 记录文件,下一轮会继续重试"
end_action "ddns_failed"
return 1
}
# ============================================================
# 十、IPv6 健康检查
# ============================================================
check_ipv6_health() {
HEALTH_FAIL_REASON=""
CURRENT_SSID="$(get_connected_ssid)"
log_detail "INFO : 当前连接 SSID:${CURRENT_SSID:-<none>}"
if ! is_connected_to_any_configured_wifi; then
HEALTH_FAIL_REASON="当前没有连接到脚本配置的 Wi-Fi"
log_detail "ERROR: $HEALTH_FAIL_REASON"
log_status
log_ipv6_detail
return 1
fi
log_status
log_ipv6_detail
VALID_IPV6="$(get_valid_ipv6)"
if [ -n "$VALID_IPV6" ]; then
LAST_VALID_IPV6="$VALID_IPV6"
log_detail "OK : IPv6 健康,有效 IPv6:$VALID_IPV6"
return 0
fi
HEALTH_FAIL_REASON="没有找到 2 开头、非 temporary、mngtmpaddr 的长期 IPv6 地址"
log_detail "ERROR: IPv6 不健康:$HEALTH_FAIL_REASON"
return 1
}
# ============================================================
# 十一、恢复动作
# ============================================================
restart_wifi_switch() {
start_action "Wi-Fi重启恢复"
log_detail "INFO : 开始重启 Wi-Fi 开关"
log_status
if command -v svc >/dev/null 2>&1; then
run_cmd_args "svc wifi disable" svc wifi disable
else
log_detail "WARN : 当前系统没有 svc 命令,跳过 svc wifi disable"
fi
run_cmd_args "cmd wifi set-wifi-enabled disabled" cmd wifi set-wifi-enabled disabled
log_detail "INFO : Wi-Fi 关闭命令已发送,等待 ${WIFI_RESTART_OFF_SECONDS}s"
sleep "$WIFI_RESTART_OFF_SECONDS"
if command -v svc >/dev/null 2>&1; then
run_cmd_args "svc wifi enable" svc wifi enable
else
log_detail "WARN : 当前系统没有 svc 命令,跳过 svc wifi enable"
fi
run_cmd_args "cmd wifi set-wifi-enabled enabled" cmd wifi set-wifi-enabled enabled
if wait_wifi_enabled; then
log_status
end_action "success"
return 0
fi
log_status
end_action "failed_wifi_not_enabled"
return 1
}
ensure_connection_and_ipv6() {
REASON="$1"
start_action "Wi-Fi连接保障"
log_detail "触发原因: $REASON"
log_detail "脚本目录: $SCRIPT_DIR"
log_detail "主日志: $LOG_FILE"
log_detail "临时目录: $TEMP_DIR"
log_detail "有效 IPv6 记录文件: $IPV6_HISTORY_FILE"
log_detail "Wi-Fi 网卡: $WIFI_IFACE"
log_detail "Wi-Fi 列表:"
log_multiline " - " "$WIFI_LIST"
log_status
if ! ensure_wifi_on; then
log_status
end_action "failed_wifi_not_enabled"
return 1
fi
scan_wifi
if ! connect_by_wifi_list; then
log_status
end_action "failed_wifi_connect"
return 1
fi
log_status
log_ipv6_detail
if wait_valid_ipv6; then
log_ipv6_detail
CURRENT_VALID_IPV6="$LAST_VALID_IPV6"
end_action "success"
ipv6_change_action "$CURRENT_VALID_IPV6" "$REASON"
return 0
fi
log_ipv6_detail
end_action "failed_ipv6_not_ready"
return 1
}
# ============================================================
# 十二、主流程
# ============================================================
# 清理上一次异常退出残留的临时文件。
rm -f "$TEMP_DIR"/action.*.log "$TEMP_DIR"/merged.*.log "$TEMP_DIR"/pruned.*.log 2>/dev/null
WIFI_IFACE="$(get_wifi_iface)"
start_action "脚本启动"
log_detail "START: wifi-keeper"
log_detail "PID : $$"
log_detail "DIR : $SCRIPT_DIR"
log_detail "LOG : $LOG_FILE"
log_detail "IFACE: $WIFI_IFACE"
log_detail "BOOT_DELAY_SECONDS: $BOOT_DELAY_SECONDS"
log_detail "IPV6_HEALTH_INTERVAL: $IPV6_HEALTH_INTERVAL"
log_detail "LOG_RETENTION_DAYS: $LOG_RETENTION_DAYS"
if ! command -v cmd >/dev/null 2>&1; then
log_detail "ERROR: 当前系统没有 cmd 命令,无法使用 cmd wifi"
end_action "fatal_no_cmd"
exit 1
fi
run_cmd_args "id" id
run_cmd_args "getprop ro.build.version.release" getprop ro.build.version.release
run_cmd_args "getprop ro.build.version.sdk" getprop ro.build.version.sdk
run_cmd_args "getprop ro.product.model" getprop ro.product.model
run_cmd_args "getprop ro.product.device" getprop ro.product.device
run_cmd_args "cmd wifi status" cmd wifi status
log_detail "INFO : 开机后先等待 ${BOOT_DELAY_SECONDS}s"
sleep "$BOOT_DELAY_SECONDS"
log_detail "INFO : 开机等待结束,进入主流程"
end_action "success"
ensure_connection_and_ipv6 "开机初始化"
while true; do
sleep "$IPV6_HEALTH_INTERVAL"
start_action "IPv6健康度检查"
if check_ipv6_health; then
CURRENT_VALID_IPV6="$LAST_VALID_IPV6"
end_action "healthy"
ipv6_change_action "$CURRENT_VALID_IPV6" "IPv6健康度检查"
continue
fi
log_detail "处理策略: IPv6 不健康,准备重启 Wi-Fi 并重新连接"
log_detail "失败原因: ${HEALTH_FAIL_REASON:-unknown}"
end_action "unhealthy_need_recovery"
restart_wifi_switch
ensure_connection_and_ipv6 "IPv6健康度异常后的恢复连接"
done
[!note]重点说下wifi-keeper.sh
- 这一次wifi-keeper.sh相比较第三期,做出了极大的改变,可以说废弃了第三期的代码。因为我发现了一个重大的问题:在国产的系统中,控制wifi的命令,比如cmd wifi是无法使用的,根本不支持。只有类原生才支持。如果不支持cmd命令就无法做到这些事情:
· 无法扫描wifi列表;
· 无法查看Wi-Fi状态;
· 无法通过wifi名称和Wi-Fi密码连接Wi-Fi(这个才是最致命的);
所以我把手里的MIUI 11刷成了crDroid。这里不推荐刷入 Pixel Experience,因为 Pixel Experience对cmd wifi的支持也有问题,当然,也可能是小米8SE的包原因,大家自行测试吧。- 在wifi-keeper.sh这个脚本中,大家要修改的地方是WIFI_LIST部分,这里需要把你家里的wifi名称和Wi-Fi密码和加密方式写到脚本中,这样不管是你手机重新开关机还是取消保存了wifi,还是开启了飞行模式,脚本都可以很好的自动连接上Wi-Fi。Wi-Fi是不会再断的,哪怕你手动断开wifi,wifi过10s都会自动连接上的。
大家不用担心脚本安全问题,因为脚本是公开透明的。
3.记得修改wifi-keeper.sh文件的权限,可以参考上一期如何修改权限,这里就不截图了
二、如何实现ipv6自动更新到DDNS,实现用域名访问
1. 自行去华为云购买服务器。这里只拿华为云举例,原因开头已经说了。大家自行注册和购买域名。注册好域名后看下一步。
2. 配置AK和SK,后面脚本会用到
2.1 访问统一身份认证服务IAM,然后左边菜单找到用户,在用户页面右上角点击创建用户
2.2 如图操作,然后下一步
2.3 到这一步的时候,一定要点击确定下载密钥文件,因为密钥只能在这里下载一次,如果不小心点了取消,只能重新创建用户
3.进入
/data/local/文件夹,新建ipv6-ddns文件夹,然后进入ipv6-ddns文件夹再新建ipv6-ddns.sh文件;输入以下内容并保存:
[!warning]提醒
#!/system/bin/sh
#
# ipv6-ddns.sh
#
# 作用:
# 接收 wifi-keeper.sh 传入的有效 IPv6,然后同步到华为云 DNS AAAA 记录。
#
# 核心逻辑:
# 1. 接收第一个参数作为新的 IPv6。
# 2. 查询华为云 DNS,获取 ZONE_ID。
# 3. 查询目标 AAAA 记录,读取华为云当前 IPv6。
# 4. 如果华为云当前 IPv6 与传入 IPv6 一致,则不更新。
# 5. 如果不一致,则 PUT 更新记录。
# 6. 每次执行作为一个动作写入日志,最新动作在日志最上方,动作内部细节正序。
#
# 注意:
# 这个脚本是给 wifi-keeper.sh 调用的:
# sh /data/local/ipv6-ddns/ipv6-ddns.sh 2409:xxxx:xxxx::xxxx
#
# 因为 wifi-keeper.sh 使用 sh 调用 DDNS 脚本,所以本脚本尽量使用 /system/bin/sh 兼容写法,
# 不使用 bash 数组。
#
# ============================================================
# 一、Termux 工具路径
# ============================================================
# Termux 的安装目录。
# 因为 Magisk service.d 启动的脚本不一定有 Termux 的 PATH,
# 所以这里主动把 Termux bin 目录加进去。
PREFIX="/data/data/com.termux/files/usr"
export PATH="$PREFIX/bin:/system/bin:/system/xbin:/vendor/bin:$PATH"
export LD_LIBRARY_PATH="$PREFIX/lib:${LD_LIBRARY_PATH:-}"
# 华为云签名需要 openssl 和 xxd。
# HTTP 请求需要 curl。
OPENSSL_BIN="$PREFIX/bin/openssl"
XXD_BIN="$PREFIX/bin/xxd"
CURL_BIN="$PREFIX/bin/curl"
# 常用命令。
DATE_BIN="/system/bin/date"
[ -x "$PREFIX/bin/date" ] && DATE_BIN="$PREFIX/bin/date"
MKDIR_BIN="/system/bin/mkdir"
[ -x "$PREFIX/bin/mkdir" ] && MKDIR_BIN="$PREFIX/bin/mkdir"
CAT_BIN="/system/bin/cat"
[ -x "$PREFIX/bin/cat" ] && CAT_BIN="$PREFIX/bin/cat"
MV_BIN="/system/bin/mv"
[ -x "$PREFIX/bin/mv" ] && MV_BIN="$PREFIX/bin/mv"
RM_BIN="/system/bin/rm"
[ -x "$PREFIX/bin/rm" ] && RM_BIN="$PREFIX/bin/rm"
AWK_BIN="/system/bin/awk"
[ -x "$PREFIX/bin/awk" ] && AWK_BIN="$PREFIX/bin/awk"
SED_BIN="/system/bin/sed"
[ -x "$PREFIX/bin/sed" ] && SED_BIN="$PREFIX/bin/sed"
GREP_BIN="/system/bin/grep"
[ -x "$PREFIX/bin/grep" ] && GREP_BIN="$PREFIX/bin/grep"
TAIL_BIN="/system/bin/tail"
[ -x "$PREFIX/bin/tail" ] && TAIL_BIN="$PREFIX/bin/tail"
# ============================================================
# 二、用户配置区
# ============================================================
# 华为云访问密钥。
# 为避免把你的 AK/SK 在聊天记录里再次明文展示,
# 请把你参考脚本里的 AK 和 SK 填到下面两行。
#
# 也可以通过环境变量传入:
# HUAWEICLOUD_AK="你的AK" HUAWEICLOUD_SK="你的SK" sh ipv6-ddns.sh 2409:...
AK="${填入AK}"
SK="${填入SK}"
# 华为云 DNS API Host。
HOST="dns.myhuaweicloud.com"
# 主域名 Zone 名称。
# 注意:华为云 DNS API 返回的 zone name 通常带结尾点。
ZONE_NAME="自己的域名." # 后面的点不能去掉
# 需要同步的 AAAA 记录列表。
# 一行一个,必须带结尾点。
# 示例:
# xxxx.top.
# www.xxxx.top.
# ssh.xxxx.top.
RECORD_NAMES='
xxxx.top.
'
# 记录类型,IPv6 固定使用 AAAA。
RECORD_TYPE="AAAA"
# DNS TTL。
# 这里沿用你参考脚本里的 TTL=1。
TTL="1"
# 调试开关:
# 0 = 常规日志
# 1 = 写入更多签名和响应调试信息
# 注意:即使 DEBUG=1,也不会打印 SK。
DEBUG="0"
# 日志保留天数。
# 超过这个天数的动作日志会被清理。
LOG_RETENTION_DAYS=7
# HTTP 请求最大重试次数。
# 主要用于处理刚开机、Wi-Fi 刚恢复、IPv6 刚出现时,网络栈还不稳定导致的 curl 56、HTTP 000、5xx 等临时错误。
HTTP_MAX_RETRIES=3
# HTTP 重试基础等待秒数。
# 第 1 次失败后等待 3 秒,第 2 次失败后等待 6 秒。
HTTP_RETRY_BASE_SLEEP_SECONDS=3
# ============================================================
# 三、路径配置
# ============================================================
BASE_DIR="/data/local/ipv6-ddns"
LOG_FILE="$BASE_DIR/ipv6-ddns.log"
RUNTIME_DIR="$BASE_DIR/.runtime"
RUN_LOG="$RUNTIME_DIR/ipv6-ddns.run.$$.log"
# ============================================================
# 四、运行参数
# ============================================================
NEW_IPV6="$1"
# ============================================================
# 五、全局变量
# ============================================================
RUN_START_TIME=""
RUN_START_EPOCH=""
SCRIPT_NAME="ipv6-ddns.sh"
FINAL_EXIT_CODE=0
LAST_HTTP_STATUS=""
LAST_RESPONSE_BODY=""
LAST_CURL_RC=""
TOTAL_COUNT=0
UPDATED_COUNT=0
SKIPPED_COUNT=0
FAILED_COUNT=0
# ============================================================
# 六、基础函数
# ============================================================
now_ts() {
"$DATE_BIN" '+%Y-%m-%d %H:%M:%S' 2>/dev/null
}
now_epoch() {
"$DATE_BIN" '+%s' 2>/dev/null
}
print_line() {
printf '%s\n' "$*"
}
log_line() {
LEVEL="$1"
shift
TS="$(now_ts)"
[ -n "$TS" ] || TS="unknown-time"
printf '[%s] %-5s %s\n' "$TS" "$LEVEL" "$*" >> "$RUN_LOG"
}
log_info() {
log_line "INFO" "$*"
}
log_warn() {
log_line "WARN" "$*"
}
log_error() {
log_line "ERROR" "$*"
}
log_debug() {
if [ "$DEBUG" = "1" ]; then
log_line "DEBUG" "$*"
fi
}
log_multiline() {
PREFIX="$1"
CONTENT="$2"
if [ -n "$CONTENT" ]; then
print_line "$CONTENT" | while IFS= read -r L || [ -n "$L" ]; do
log_info "$PREFIX$L"
done
else
log_info "${PREFIX}<empty>"
fi
}
is_placeholder_secret() {
case "$1" in
*请把参考脚本中的AK填到这里*|*请把参考脚本中的SK填到这里*|"")
return 0
;;
*)
return 1
;;
esac
}
mask_text() {
VALUE="$1"
LEN=${#VALUE}
if [ "$LEN" -le 8 ]; then
printf '******'
else
HEAD=$(printf '%s' "$VALUE" | cut -c 1-4)
TAIL=$(printf '%s' "$VALUE" | cut -c "$((LEN - 3))"-"$LEN")
printf '%s******%s' "$HEAD" "$TAIL"
fi
}
# ============================================================
# 七、日志动作:插入主日志顶部 + 清理旧日志
# ============================================================
prune_log_by_days() {
[ "$LOG_RETENTION_DAYS" -gt 0 ] 2>/dev/null || return 0
[ -f "$LOG_FILE" ] || return 0
NOW="$(now_epoch)"
[ -n "$NOW" ] || return 0
CUTOFF=$((NOW - LOG_RETENTION_DAYS * 86400))
PRUNED="$RUNTIME_DIR/ipv6-ddns.pruned.$$.log"
"$AWK_BIN" -v cutoff="$CUTOFF" '
BEGIN {
inside = 0
block = ""
keep = 1
}
$0 == "##### ACTION_BLOCK_BEGIN #####" {
if (inside == 1 && keep == 1) {
printf "%s", block
}
inside = 1
block = $0 "\n"
keep = 1
next
}
inside == 1 {
block = block $0 "\n"
if ($1 == "LOG_EPOCH:") {
if (($2 + 0) < cutoff) {
keep = 0
}
}
if ($0 == "##### ACTION_BLOCK_END #####") {
if (keep == 1) {
printf "%s", block
}
inside = 0
block = ""
keep = 1
}
next
}
{
print
}
END {
if (inside == 1 && keep == 1) {
printf "%s", block
}
}
' "$LOG_FILE" > "$PRUNED" 2>/dev/null
if [ -f "$PRUNED" ]; then
"$MV_BIN" "$PRUNED" "$LOG_FILE" 2>/dev/null
fi
}
prepend_run_log_to_main_log() {
TMP_LOG="$RUNTIME_DIR/ipv6-ddns.merged.$$.log"
END_TIME="$(now_ts)"
{
print_line "##### ACTION_BLOCK_BEGIN #####"
print_line "============================================================"
print_line "===== $RUN_START_TIME | IPv6-DDNS同步 | START ====="
print_line "============================================================"
print_line "LOG_EPOCH: $RUN_START_EPOCH"
print_line "[$RUN_START_TIME] ACTION : IPv6-DDNS同步"
print_line "[$RUN_START_TIME] SCRIPT : $0"
print_line "[$RUN_START_TIME] LOG : $LOG_FILE"
print_line "[$RUN_START_TIME] ZONE : $ZONE_NAME"
print_line "[$RUN_START_TIME] TYPE : $RECORD_TYPE"
print_line "[$RUN_START_TIME] TTL : $TTL"
print_line "[$RUN_START_TIME] INPUT : $NEW_IPV6"
print_line "[$RUN_START_TIME] RESULT : exit_code=$FINAL_EXIT_CODE total=$TOTAL_COUNT updated=$UPDATED_COUNT skipped=$SKIPPED_COUNT failed=$FAILED_COUNT"
print_line "------------------------------------------------------------"
[ -f "$RUN_LOG" ] && "$CAT_BIN" "$RUN_LOG"
print_line "------------------------------------------------------------"
print_line "[$END_TIME] RESULT: exit_code=$FINAL_EXIT_CODE total=$TOTAL_COUNT updated=$UPDATED_COUNT skipped=$SKIPPED_COUNT failed=$FAILED_COUNT"
print_line "============================================================"
print_line "===== $END_TIME | IPv6-DDNS同步 | END ====="
print_line "============================================================"
print_line "##### ACTION_BLOCK_END #####"
print_line ""
[ -f "$LOG_FILE" ] && "$CAT_BIN" "$LOG_FILE"
} > "$TMP_LOG"
"$MV_BIN" "$TMP_LOG" "$LOG_FILE" 2>/dev/null
prune_log_by_days
}
finish() {
EXIT_CODE=$?
FINAL_EXIT_CODE="$EXIT_CODE"
if [ -f "$RUN_LOG" ]; then
prepend_run_log_to_main_log
"$RM_BIN" -f "$RUN_LOG" 2>/dev/null
fi
exit "$EXIT_CODE"
}
# ============================================================
# 八、签名函数:只沿用参考脚本里的 AK/SK 使用方式
# ============================================================
sha256_hex() {
DATA="$1"
printf '%s' "$DATA" | "$OPENSSL_BIN" dgst -sha256 -binary | "$XXD_BIN" -p -c 256
}
hmac_sha256_hex() {
KEY="$1"
DATA="$2"
printf '%s' "$DATA" | "$OPENSSL_BIN" dgst -sha256 -mac HMAC -macopt "key:${KEY}" -binary | "$XXD_BIN" -p -c 256
}
is_retryable_http_status() {
case "$1" in
000|408|429|500|502|503|504)
return 0
;;
*)
return 1
;;
esac
}
signed_request() {
METHOD="$1"
REQUEST_PATH="$2"
CANONICAL_URI="$3"
CANONICAL_QUERY="$4"
BODY="$5"
CONTENT_TYPE="$6"
URL="https://${HOST}${REQUEST_PATH}"
if [ -n "$CANONICAL_QUERY" ]; then
URL="${URL}?${CANONICAL_QUERY}"
fi
ATTEMPT=1
[ "$HTTP_MAX_RETRIES" -gt 0 ] 2>/dev/null || HTTP_MAX_RETRIES=1
while [ "$ATTEMPT" -le "$HTTP_MAX_RETRIES" ]; do
X_SDK_DATE="$(TZ=UTC "$DATE_BIN" +%Y%m%dT%H%M%SZ 2>/dev/null)"
PAYLOAD_HASH="$(sha256_hex "$BODY")"
if [ -n "$CONTENT_TYPE" ]; then
CANONICAL_HEADERS="content-type:${CONTENT_TYPE}
host:${HOST}
x-sdk-date:${X_SDK_DATE}
"
SIGNED_HEADERS="content-type;host;x-sdk-date"
else
CANONICAL_HEADERS="host:${HOST}
x-sdk-date:${X_SDK_DATE}
"
SIGNED_HEADERS="host;x-sdk-date"
fi
CANONICAL_REQUEST="${METHOD}
${CANONICAL_URI}
${CANONICAL_QUERY}
${CANONICAL_HEADERS}
${SIGNED_HEADERS}
${PAYLOAD_HASH}"
HASHED_CANONICAL_REQUEST="$(sha256_hex "$CANONICAL_REQUEST")"
STRING_TO_SIGN="SDK-HMAC-SHA256
${X_SDK_DATE}
${HASHED_CANONICAL_REQUEST}"
SIGNATURE="$(hmac_sha256_hex "$SK" "$STRING_TO_SIGN")"
AUTHORIZATION="SDK-HMAC-SHA256 Access=${AK}, SignedHeaders=${SIGNED_HEADERS}, Signature=${SIGNATURE}"
log_info "HTTP 请求开始: attempt=${ATTEMPT}/${HTTP_MAX_RETRIES} method=$METHOD url=$URL"
log_debug "X-Sdk-Date=$X_SDK_DATE"
log_debug "SignedHeaders=$SIGNED_HEADERS"
log_debug "PayloadHash=$PAYLOAD_HASH"
log_debug "CanonicalRequest=$(printf '%s\n' "$CANONICAL_REQUEST" | "$SED_BIN" ':a;N;$!ba;s/\n/|/g')"
[ -n "$BODY" ] && log_debug "RequestBody=$BODY"
if [ -n "$CONTENT_TYPE" ]; then
RESPONSE="$(
"$CURL_BIN" -sS \
--connect-timeout 10 \
--max-time 30 \
-w '\n__HTTP_STATUS__:%{http_code}' \
-X "$METHOD" \
"$URL" \
-H "Host: ${HOST}" \
-H "X-Sdk-Date: ${X_SDK_DATE}" \
-H "Authorization: ${AUTHORIZATION}" \
-H "Content-Type: ${CONTENT_TYPE}" \
--data "$BODY" 2>&1
)"
CURL_RC="$?"
else
RESPONSE="$(
"$CURL_BIN" -sS \
--connect-timeout 10 \
--max-time 30 \
-w '\n__HTTP_STATUS__:%{http_code}' \
-X "$METHOD" \
"$URL" \
-H "Host: ${HOST}" \
-H "X-Sdk-Date: ${X_SDK_DATE}" \
-H "Authorization: ${AUTHORIZATION}" 2>&1
)"
CURL_RC="$?"
fi
LAST_CURL_RC="$CURL_RC"
if [ "$CURL_RC" != "0" ]; then
LAST_HTTP_STATUS="CURL_ERROR"
LAST_RESPONSE_BODY="$RESPONSE"
log_error "HTTP 请求失败: attempt=${ATTEMPT}/${HTTP_MAX_RETRIES} curl_rc=$CURL_RC method=$METHOD url=$URL"
log_multiline "curl输出: " "$RESPONSE"
if [ "$ATTEMPT" -lt "$HTTP_MAX_RETRIES" ]; then
SLEEP_SECONDS=$((HTTP_RETRY_BASE_SLEEP_SECONDS * ATTEMPT))
log_warn "HTTP 请求将重试: ${SLEEP_SECONDS}s 后进行第 $((ATTEMPT + 1)) 次尝试"
sleep "$SLEEP_SECONDS"
ATTEMPT=$((ATTEMPT + 1))
continue
fi
log_error "HTTP 请求失败且已达到最大重试次数: $HTTP_MAX_RETRIES"
return 1
fi
LAST_HTTP_STATUS="$(printf '%s' "$RESPONSE" | "$SED_BIN" -n 's/^__HTTP_STATUS__://p' | "$TAIL_BIN" -n 1)"
LAST_RESPONSE_BODY="$(printf '%s' "$RESPONSE" | "$SED_BIN" '/^__HTTP_STATUS__:/d')"
[ -n "$LAST_HTTP_STATUS" ] || LAST_HTTP_STATUS="000"
log_info "HTTP 请求结束: attempt=${ATTEMPT}/${HTTP_MAX_RETRIES} status=${LAST_HTTP_STATUS} method=$METHOD url=$URL"
log_debug "ResponseBody=$LAST_RESPONSE_BODY"
if is_retryable_http_status "$LAST_HTTP_STATUS" && [ "$ATTEMPT" -lt "$HTTP_MAX_RETRIES" ]; then
SLEEP_SECONDS=$((HTTP_RETRY_BASE_SLEEP_SECONDS * ATTEMPT))
log_warn "HTTP 状态可能是临时错误: status=${LAST_HTTP_STATUS},${SLEEP_SECONDS}s 后进行第 $((ATTEMPT + 1)) 次尝试"
sleep "$SLEEP_SECONDS"
ATTEMPT=$((ATTEMPT + 1))
continue
fi
return 0
done
return 1
}
# ============================================================
# 九、华为云 DNS API 操作函数
# ============================================================
get_zone_id() {
signed_request \
"GET" \
"/v2/zones" \
"/v2/zones/" \
"limit=500&type=public" \
"" \
""
if [ "$LAST_HTTP_STATUS" != "200" ]; then
log_error "查询 ZONE_ID 失败: HTTP=$LAST_HTTP_STATUS"
log_multiline "响应内容: " "$LAST_RESPONSE_BODY"
return 1
fi
ZONE_ID="$(printf '%s' "$LAST_RESPONSE_BODY" \
| "$SED_BIN" 's/},{/},\
{/g' \
| "$GREP_BIN" -F "\"name\":\"${ZONE_NAME}\"" \
| "$SED_BIN" -n 's/.*"id":"\([^"]*\)".*/\1/p' \
| "$TAIL_BIN" -n 1)"
if [ -n "$ZONE_ID" ]; then
log_info "解析到 ZONE_ID: $ZONE_ID"
return 0
fi
log_error "没有从华为云 zones 响应中找到 ZONE_NAME=$ZONE_NAME"
return 1
}
get_record_line() {
G_ZONE_ID="$1"
G_RECORD_NAME="$2"
signed_request \
"GET" \
"/v2.1/zones/${G_ZONE_ID}/recordsets" \
"/v2.1/zones/${G_ZONE_ID}/recordsets/" \
"limit=500" \
"" \
""
if [ "$LAST_HTTP_STATUS" != "200" ]; then
log_error "查询 recordsets 失败: record=$G_RECORD_NAME HTTP=$LAST_HTTP_STATUS"
log_multiline "响应内容: " "$LAST_RESPONSE_BODY"
return 1
fi
MATCH_LINE="$(printf '%s' "$LAST_RESPONSE_BODY" \
| "$SED_BIN" 's/},{/},\
{/g' \
| "$GREP_BIN" -F "\"name\":\"${G_RECORD_NAME}\"" \
| "$GREP_BIN" -F "\"type\":\"${RECORD_TYPE}\"" \
| "$TAIL_BIN" -n 1)"
printf '%s' "$MATCH_LINE"
return 0
}
parse_recordset_id() {
printf '%s' "$1" | "$SED_BIN" -n 's/.*"id":"\([^"]*\)".*/\1/p' | "$TAIL_BIN" -n 1
}
parse_record_ipv6() {
printf '%s' "$1" | "$SED_BIN" -n 's/.*"records":\["\([^"]*\)"\].*/\1/p' | "$TAIL_BIN" -n 1
}
parse_record_ttl() {
printf '%s' "$1" | "$SED_BIN" -n 's/.*"ttl":\([0-9]*\).*/\1/p' | "$TAIL_BIN" -n 1
}
update_recordset() {
U_ZONE_ID="$1"
U_RECORDSET_ID="$2"
U_RECORD_NAME="$3"
U_NEW_IPV6="$4"
BODY="{\"name\":\"${U_RECORD_NAME}\",\"type\":\"${RECORD_TYPE}\",\"ttl\":${TTL},\"records\":[\"${U_NEW_IPV6}\"]}"
log_info "准备更新华为云记录: record=$U_RECORD_NAME recordset_id=$U_RECORDSET_ID"
log_info "更新目标 IPv6: $U_NEW_IPV6"
log_debug "更新请求 body=$BODY"
signed_request \
"PUT" \
"/v2.1/zones/${U_ZONE_ID}/recordsets/${U_RECORDSET_ID}" \
"/v2.1/zones/${U_ZONE_ID}/recordsets/${U_RECORDSET_ID}/" \
"" \
"$BODY" \
"application/json"
if [ "$LAST_HTTP_STATUS" = "202" ] || [ "$LAST_HTTP_STATUS" = "200" ]; then
log_info "更新请求已被华为云接受: record=$U_RECORD_NAME HTTP=$LAST_HTTP_STATUS"
log_debug "更新响应: $LAST_RESPONSE_BODY"
return 0
fi
log_error "更新记录失败: record=$U_RECORD_NAME HTTP=$LAST_HTTP_STATUS"
log_multiline "响应内容: " "$LAST_RESPONSE_BODY"
return 1
}
# ============================================================
# 十、环境检查与参数检查
# ============================================================
prepare_runtime() {
"$MKDIR_BIN" -p "$RUNTIME_DIR" 2>/dev/null
touch "$RUN_LOG" 2>/dev/null
touch "$LOG_FILE" 2>/dev/null
RUN_START_TIME="$(now_ts)"
RUN_START_EPOCH="$(now_epoch)"
[ -n "$RUN_START_TIME" ] || RUN_START_TIME="unknown-time"
[ -n "$RUN_START_EPOCH" ] || RUN_START_EPOCH="0"
}
check_environment() {
log_info "脚本开始执行"
log_info "接收到的 IPv6 参数: ${NEW_IPV6:-<empty>}"
log_info "脚本路径: $0"
log_info "日志文件: $LOG_FILE"
log_info "运行目录: $RUNTIME_DIR"
log_info "华为云 HOST: $HOST"
log_info "主域名 ZONE_NAME: $ZONE_NAME"
log_info "记录类型: $RECORD_TYPE"
log_info "目标 TTL: $TTL"
log_info "AK: $(mask_text "$AK")"
log_info "SK: ******"
if is_placeholder_secret "$AK" || is_placeholder_secret "$SK"; then
log_error "AK/SK 尚未配置。请把参考脚本中的 AK/SK 填入本脚本,或通过 HUAWEICLOUD_AK/HUAWEICLOUD_SK 环境变量传入。"
return 1
fi
if [ ! -x "$OPENSSL_BIN" ]; then
log_error "找不到 openssl: $OPENSSL_BIN"
return 1
fi
if [ ! -x "$XXD_BIN" ]; then
log_error "找不到 xxd: $XXD_BIN"
return 1
fi
if [ ! -x "$CURL_BIN" ]; then
log_error "找不到 curl: $CURL_BIN"
return 1
fi
log_info "工具检查通过: openssl=$OPENSSL_BIN"
log_info "工具检查通过: xxd=$XXD_BIN"
log_info "工具检查通过: curl=$CURL_BIN"
if [ -z "$NEW_IPV6" ]; then
log_error "没有收到 IPv6 参数。正确用法: sh $0 2409:xxxx:xxxx::xxxx"
return 1
fi
if ! printf '%s' "$NEW_IPV6" | "$GREP_BIN" -qiE '^[0-9a-f:]+$'; then
log_error "IPv6 格式看起来不正确: $NEW_IPV6"
return 1
fi
case "$NEW_IPV6" in
2*)
log_info "IPv6 参数基础校验通过: 以 2 开头"
;;
*)
log_warn "IPv6 参数不是 2 开头: $NEW_IPV6"
log_warn "wifi-keeper.sh 正常情况下只会传入 2 开头的长期 IPv6,这里仍继续执行。"
;;
esac
return 0
}
# ============================================================
# 十一、主流程
# ============================================================
process_one_record() {
P_ZONE_ID="$1"
P_RECORD_NAME="$2"
TOTAL_COUNT=$((TOTAL_COUNT + 1))
log_info "------------------------------------------------------------"
log_info "开始处理记录: $P_RECORD_NAME"
RECORD_LINE="$(get_record_line "$P_ZONE_ID" "$P_RECORD_NAME")"
GET_RECORD_RC="$?"
if [ "$GET_RECORD_RC" != "0" ]; then
log_error "查询目标 AAAA 记录失败,无法判断记录是否存在: $P_RECORD_NAME"
FAILED_COUNT=$((FAILED_COUNT + 1))
return 1
fi
if [ -z "$RECORD_LINE" ]; then
log_error "华为云没有找到目标 AAAA 记录: $P_RECORD_NAME"
FAILED_COUNT=$((FAILED_COUNT + 1))
return 1
fi
log_info "华为云记录原始信息: $RECORD_LINE"
RECORDSET_ID="$(parse_recordset_id "$RECORD_LINE")"
REMOTE_IPV6="$(parse_record_ipv6 "$RECORD_LINE")"
REMOTE_TTL="$(parse_record_ttl "$RECORD_LINE")"
log_info "解析到 RECORDSET_ID: ${RECORDSET_ID:-<empty>}"
log_info "从华为云读取到的原 IPv6: ${REMOTE_IPV6:-<empty>}"
log_info "从华为云读取到的 TTL: ${REMOTE_TTL:-<unknown>}"
log_info "wifi-keeper.sh 传入的新 IPv6: $NEW_IPV6"
if [ -z "$RECORDSET_ID" ]; then
log_error "无法解析 RECORDSET_ID,跳过记录: $P_RECORD_NAME"
FAILED_COUNT=$((FAILED_COUNT + 1))
return 1
fi
if [ "$REMOTE_IPV6" = "$NEW_IPV6" ]; then
log_info "对比结果: IPv6 一致,不需要更新"
log_info "跳过更新: $P_RECORD_NAME"
SKIPPED_COUNT=$((SKIPPED_COUNT + 1))
return 0
fi
log_info "对比结果: IPv6 不一致,需要更新"
log_info "变化详情: ${REMOTE_IPV6:-<empty>} -> $NEW_IPV6"
if ! update_recordset "$P_ZONE_ID" "$RECORDSET_ID" "$P_RECORD_NAME" "$NEW_IPV6"; then
FAILED_COUNT=$((FAILED_COUNT + 1))
return 1
fi
log_info "开始二次查询,验证华为云是否已经更新为新 IPv6"
VERIFY_LINE="$(get_record_line "$P_ZONE_ID" "$P_RECORD_NAME")"
VERIFY_RC="$?"
if [ "$VERIFY_RC" != "0" ]; then
log_error "二次查询失败,无法验证华为云是否已经更新: $P_RECORD_NAME"
FAILED_COUNT=$((FAILED_COUNT + 1))
return 1
fi
VERIFY_IPV6="$(parse_record_ipv6 "$VERIFY_LINE")"
log_info "二次查询记录原始信息: ${VERIFY_LINE:-<empty>}"
log_info "二次查询解析到的 IPv6: ${VERIFY_IPV6:-<empty>}"
if [ "$VERIFY_IPV6" = "$NEW_IPV6" ]; then
log_info "更新验证成功: $P_RECORD_NAME 已经是 $NEW_IPV6"
UPDATED_COUNT=$((UPDATED_COUNT + 1))
return 0
fi
log_error "更新验证失败: $P_RECORD_NAME 当前仍是 ${VERIFY_IPV6:-<empty>},期望 $NEW_IPV6"
FAILED_COUNT=$((FAILED_COUNT + 1))
return 1
}
main() {
prepare_runtime
trap finish EXIT
if ! check_environment; then
exit 1
fi
log_info "开始查询华为云 ZONE_ID"
if ! get_zone_id; then
exit 1
fi
if [ -z "$ZONE_ID" ]; then
log_error "ZONE_ID 为空,无法继续"
exit 1
fi
log_info "ZONE_ID 查询成功: $ZONE_ID"
log_info "开始处理 RECORD_NAMES 列表"
log_multiline "目标记录: " "$RECORD_NAMES"
RECORD_LIST_FILE="$RUNTIME_DIR/record-list.$$.txt"
printf '%s\n' "$RECORD_NAMES" > "$RECORD_LIST_FILE"
while IFS= read -r RECORD_NAME || [ -n "$RECORD_NAME" ]; do
[ -z "$RECORD_NAME" ] && continue
case "$RECORD_NAME" in
\#*)
continue
;;
esac
process_one_record "$ZONE_ID" "$RECORD_NAME"
done < "$RECORD_LIST_FILE"
"$RM_BIN" -f "$RECORD_LIST_FILE" 2>/dev/null
log_info "全部记录处理完成: total=$TOTAL_COUNT updated=$UPDATED_COUNT skipped=$SKIPPED_COUNT failed=$FAILED_COUNT"
if [ "$FAILED_COUNT" -gt 0 ]; then
log_error "存在失败记录,脚本以失败状态退出"
exit 1
fi
log_info "脚本执行成功"
exit 0
}
main
4. 同步DDNS时,需要用到openssl,所以先安装termux。
4.1 执行命令
pkg update
可能会提示:
No mirror or mirror group selected. You might want to select one by running 'termux-change-repo'
Testing the available mirrors:
[*] (10) https://packages-cf.termux.dev/apt/termux-main: ok
[*] (1) https://mirror.freedif.org/termux/termux-main: ok
[*] (1) https://mirror.nevacloud.com/applications/termux/termux-main: ok
[*] (1) https://mirrors.krnk.org/apt/termux/termux-main: ok
[*] (1) https://mirrors.saswata.cc/termux/termux-main: bad
[*] (1) https://tmx.xvx.my.id/apt/termux-main: bad
[*] (1) https://mirror.rinarin.dev/termux/termux-main: ok
[*] (1) https://mirror.albony.in/termux/termux-main: ok
[*] (1) https://mirror.meowsmp.net/termux/termux-main: bad
[*] (1) https://mirrors.in.sahilister.net/termux/termux-main/: ok
[*] (1) https://mirrors.ravidwivedi.in/termux/termux-main: ok
[*] (1) https://linux.domainesia.com/applications/termux/termux-main: ok
[*] (1) https://mirror.jeonnam.school/termux/termux-main: ok
[*] (1) https://termux.niranjan.co/termux-main: ok
[*] (1) https://mirrors.cbrx.io/apt/termux/termux-main: ok
[*] (1) https://mirror.bardia.tech/termux/termux-main: bad
[*] (1) https://mirror.textcord.xyz/termux/termux-main: ok
[*] (1) https://mirror.twds.com.tw/termux/termux-main: ok
[*] (1) https://mirrors.nguyenhoang.cloud/termux/termux-main: ok
[*] (1) https://mirrors.zju.edu.cn/termux/apt/termux-main: ok
[*] (1) https://mirrors.pku.edu.cn/termux/termux-main/: ok
[*] (1) https://mirrors.cernet.edu.cn/termux/apt/termux-main: ok
[*] (1) https://mirrors.hust.edu.cn/termux/apt/termux-main: ok
[*] (1) https://mirrors.bfsu.edu.cn/termux/apt/termux-main: ok
[*] (1) https://mirrors.cqupt.edu.cn/termux/termux-main: ok
[*] (1) https://mirrors.tuna.tsinghua.edu.cn/termux/apt/termux-main: ok
[*] (1) https://mirror.nyist.edu.cn/termux/apt/termux-main: ok
[*] (1) https://mirror.sjtu.edu.cn/termux/termux-main/: ok
[*] (1) https://mirrors.sau.edu.cn/termux/apt/termux-main: ok
[*] (1) https://mirrors.sdu.edu.cn/termux/termux-main: ok
[*] (1) https://mirrors.nju.edu.cn/termux/apt/termux-main: ok
[*] (1) https://mirrors.aliyun.com/termux/termux-main: ok
[*] (1) https://mirror.iscas.ac.cn/termux/apt/termux-main: ok
[*] (1) https://mirrors.sustech.edu.cn/termux/apt/termux-main: ok
[*] (1) https://mirrors.ustc.edu.cn/termux/termux-main: ok
[*] (1) https://mirrors.de.sahilister.net/termux/termux-main: ok
[*] (1) https://ro.mirror.flokinet.net/termux/termux-main: ok
[*] (1) https://mirrors.medzik.dev/termux/termux-main: bad
[*] (1) https://mirror.leitecastro.com/termux/termux-main: bad
[*] (1) https://mirror.mwt.me/termux/main: ok
[*] (1) https://termux.librehat.com/apt/termux-main: ok
[*] (1) https://ftp.fau.de/termux/termux-main: ok
[*] (1) https://packages.termux.dev/apt/termux-main: ok
[*] (1) https://termux.mentality.rip/termux-main: ok
[*] (1) https://md.mirrors.hacktegic.com/termux/termux-main: bad
[*] (1) https://mirror.termux.dev/termux-main: bad
[*] (1) https://mirror.bouwhuis.network/termux/termux-main: ok
[*] (1) https://ftp.agdsn.de/termux/termux-main: ok
[*] (1) https://mirrors.cfe.re/termux/termux-main: bad
[*] (1) https://is.mirror.flokinet.net/termux/termux-main: ok
[*] (1) https://termux.3san.dev/termux/termux-main: ok
[*] (1) https://mirror.accum.se/mirror/termux.dev/termux-main: ok
[*] (1) https://mirror.sunred.org/termux/termux-main: ok
[*] (4) https://grimler.se/termux/termux-main: ok
[*] (1) https://mirror.autkin.net/termux/termux-main: ok
[*] (1) https://mirror.polido.pt/termux/termux-main: bad
[*] (1) https://nl.mirror.flokinet.net/termux/termux-main: ok
[*] (1) https://termux.cdn.lumito.net/termux-main: ok
[*] (1) https://mirrors.utermux.dev/termux/termux-main: ok
[*] (1) https://mirror.quantum5.ca/termux/termux-main: ok
[*] (1) https://gnlug.org/pub/termux/termux-main: ok
[*] (1) https://mirror.mwt.me/termux/main: ok
[*] (1) https://plug-mirror.rcac.purdue.edu/termux/termux-main: bad
[*] (1) https://termux.danyael.xyz/termux/termux-main: ok
[*] (1) https://mirror.vern.cc/termux/termux-main: bad
[*] (1) https://mirror.csclub.uwaterloo.ca/termux/termux-main: ok
[*] (1) https://dl.kcubeterm.com/termux-main: bad
[*] (1) https://mirror.fcix.net/termux/termux-main: bad
[*] (1) https://mirrors.middlendian.com/termux/termux-main: bad
[*] (1) http://mirror.mephi.ru/termux/termux-main: ok
[*] (1) https://repository.su/termux/termux-main/: bad
Picking mirror: (40) /data/data/com.termux/files/usr/etc/termux/mirrors/europe/mirrors.de.sahilister.net
Get:1 https://mirrors.de.sahilister.net/termux/termux-main stable InRelease [14.0 kB]
Get:2 https://mirrors.de.sahilister.net/termux/termux-main stable/main aarch64 Packages [547 kB]
Fetched 561 kB in 6s (91.4 kB/s)
Reading package lists... Done
Building dependency tree... Done
73 packages can be upgraded. Run 'apt list --upgradable' to see them.
~ $
意思是:你当前 Termux 还没有固定选择一个软件源镜像,所以它自动测试了一堆可用镜像。
不用管,termux会自动选择可用源帮你更新。
4.2然后执行命令安装openssl:
pkg install openssl openssl-tool curl vim -y
安装过程中,如果提示输入内容,默认输入Y就行。
如果安装过程缓慢,可以搭载全局节点,也可以切换源地址后重新执行命令安装。
[!success]
到此,ipv6的健康度检查和ddns自动同步问题就解决了。手机重启以后,wifi会自动连接,然后ipv6会做健康度检查,然后自动同步DDNS。
[!warning]
由于论坛的文本编辑框,内容输入过多的时候,就会变得非常卡,由于本期代码太多了,所以帖子写的很慢。有很多细节上的东西不太重要我就暂时不补充了。不管是ipv6的检查脚本还是DDNS的同步脚本,都有非常详细的日志记录,如果有什么问题,大家可以把日志发出来,到时候我看看
4 个帖子 - 4 位参与者





