在内地机和台湾机安装依赖
1.在脚本所在 VPS安装依赖:
apt-get update && apt-get install -y sshpass netcat-openbsd
2.探测节点(内地机)若缺 nc:
apt-get update && apt-get install -y netcat-openbsd
创建运行脚本
nano ip_probe.sh
复制下面内容到脚本内
#!/usr/bin/env bash
set -euo pipefail
### =========== 配置区 ===========
PORTS=(59141 44299)
CN_HOST="你的内地主机IP"
CN_USER="主机用户名"
CN_PASS="你的root密码"
CN_PORT=22 #主机ssh端口
ATTEMPTS=30
SLEEP_BETWEEN=1
CHANGE_IP_URL="http://100.100.101.101:8002/changeip" #moecloud 更换ip的地址
WAIT_AFTER_CHANGE=180
REVERIFY_ATTEMPTS=10
REVERIFY_INTERVAL=2
MAX_CHANGE_RETRIES=5
TG_BOT_TOKEN="在此填入_BOT_TOKEN"
TG_CHAT_ID="在此填入_CHAT_ID"
LOG_DIR="/root/log/ip_monitor"
LOG_RETENTION_DAYS=7
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/$(date +%F).log"
### =========== 配置区 ===========
ts(){ date "+%F %T"; }
notify_tg(){
local text="$1"
[[ -n "$TG_BOT_TOKEN" && -n "$TG_CHAT_ID" ]] || return 0
curl -sS "https://api.telegram.org/bot${TG_BOT_TOKEN}/sendMessage" \
-d "chat_id=${TG_CHAT_ID}" \
--data-urlencode "text=${text}" >/dev/null || true
}
get_public_ip(){
curl -fsS --max-time 4 https://ifconfig.me \
|| curl -fsS --max-time 4 https://api.ipify.org \
|| curl -fsS --max-time 4 https://ipinfo.io/ip \
|| echo "UNKNOWN"
}
# ===== 本机依赖自检 =====
require_cmd(){
local cmd="$1" hint="$2"
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "[$(ts)] 本机缺少依赖:$cmd"
echo "[$(ts)] 请安装:$hint"
notify_tg "❗本机缺少依赖:$cmd。建议安装:$hint"
exit 10
fi
}
check_local_deps(){
require_cmd sshpass "apt-get update && apt-get install -y sshpass"
require_cmd nc "apt-get update && apt-get install -y netcat-openbsd"
}
# ===== 远端依赖自检 =====
check_remote_nc(){
if sshpass -p "$CN_PASS" ssh -p "$CN_PORT" -o StrictHostKeyChecking=no \
-o ConnectTimeout=5 -o BatchMode=no -o PreferredAuthentications=password \
"${CN_USER}@${CN_HOST}" "command -v nc >/dev/null 2>&1"; then
echo "[$(ts)] 探测节点 ${CN_HOST}:${CN_PORT} 已安装 nc"
else
local msg="❗探测节点 ${CN_HOST}:${CN_PORT} 未安装 nc。请执行:apt-get update && apt-get install -y netcat-openbsd"
echo "[$(ts)] $msg"; notify_tg "$msg"; exit 11
fi
}
# ===== 探测与判定 =====
probe_once_from_cn(){
local ip="$1" port="$2"
sshpass -p "$CN_PASS" ssh \
-p "$CN_PORT" \
-o StrictHostKeyChecking=no \
-o ConnectTimeout=5 \
-o BatchMode=no \
-o PreferredAuthentications=password \
"${CN_USER}@${CN_HOST}" \
"nc -z -w3 ${ip} ${port}" >/dev/null 2>&1
}
# 任一端口在 tries 次内“全部失败”即返回 1(=需要换);否则返回 0(=不需要换)
probe_port_with_retries(){
local ip="$1" port="$2" tries="$3" interval="$4" tag="$5"
local i
for i in $(seq 1 "$tries"); do
if probe_once_from_cn "$ip" "$port"; then
echo "[$(ts)] ${tag} 第 ${i}/${tries} 次:${ip}:${port} 可访问"
return 0
else
echo "[$(ts)] ${tag} 第 ${i}/${tries} 次:${ip}:${port} 不可访问"
sleep "$interval"
fi
done
return 1
}
need_change_ip(){
local ip="$1" tries="$2" interval="$3" tag="$4"
local port
for port in "${PORTS[@]}"; do
echo "[$(ts)] ${tag} 检测 ${ip}:${port} ..."
if probe_port_with_retries "$ip" "$port" "$tries" "$interval" "$tag"; then
echo "[$(ts)] ${tag} 结论:${ip}:${port} 可访问"
else
echo "[$(ts)] ${tag} 结论:${ip}:${port} 在 ${tries} 次内均不通 → 需要换 IP"
return 1
fi
done
return 0
}
change_ip(){ curl -fsS --max-time 15 "$CHANGE_IP_URL" || true; }
clean_old_logs(){
local count
count=$(find "$LOG_DIR" -type f -name "*.log" -mtime +"$LOG_RETENTION_DAYS" 2>/dev/null | wc -l | awk '{print $1}')
if [[ "$count" -gt 0 ]]; then
find "$LOG_DIR" -type f -name "*.log" -mtime +"$LOG_RETENTION_DAYS" -delete 2>/dev/null || true
echo "[$(ts)] 日志清理:删除 $count 个超过 ${LOG_RETENTION_DAYS} 天的日志文件"
notify_tg "🧹 日志清理:删除 $count 个超过 ${LOG_RETENTION_DAYS} 天的日志($LOG_DIR)"
else
echo "[$(ts)] 日志清理:无超过 ${LOG_RETENTION_DAYS} 天的日志需要删除"
fi
}
main(){
check_local_deps
check_remote_nc
clean_old_logs
local ip old_ip
ip="$(get_public_ip)"; old_ip="$ip"
echo "[$(ts)] 当前公网 IP: $ip"
notify_tg "开始检测:VPS ${ip};端口:${PORTS[*]};规则:任一端口连续 ${ATTEMPTS} 次不通即更换 IP;探测节点:${CN_USER}@${CN_HOST}:${CN_PORT}。"
# —— 首检:如果“不需要换”(返回0),直接通过并结束;否则进入换IP流程
if need_change_ip "$ip" "$ATTEMPTS" "$SLEEP_BETWEEN" "首检"; then
echo "[$(ts)] 首检通过:无需更换 IP。"
notify_tg "✅ 首检通过:${ip} 端口组 ${PORTS[*]} 可访问,未换 IP。"
return 0
else
notify_tg "⚠️ 首检不通过:${ip}(端口组:${PORTS[*]})。开始尝试更换 IP(最多 ${MAX_CHANGE_RETRIES} 次)..."
fi
# —— 更换与复检:最多 MAX_CHANGE_RETRIES 次
local attempt
for attempt in $(seq 1 "$MAX_CHANGE_RETRIES"); do
echo "[$(ts)] 第 ${attempt}/${MAX_CHANGE_RETRIES} 次更换 IP ..."
change_ip
echo "[$(ts)] 等待 ${WAIT_AFTER_CHANGE}s 让新 IP 生效..."
sleep "$WAIT_AFTER_CHANGE"
ip="$(get_public_ip)"
echo "[$(ts)] 更换完成:旧 IP:${old_ip};当前 IP:${ip}"
notify_tg "♻️ 更换完成(第 ${attempt} 次):旧 IP:${old_ip};当前 IP:${ip};开始复检..."
# 复检:如果“不需要换”(返回0),说明通过;否则继续下一轮或停止
if need_change_ip "$ip" "$REVERIFY_ATTEMPTS" "$REVERIFY_INTERVAL" "复检"; then
echo "[$(ts)] 复检通过:新 IP ${ip} 所有端口可访问。"
notify_tg "✅ 复检通过:新 IP ${ip} 所有端口可访问。"
exit 0
else
echo "[$(ts)] 复检不通过(第 ${attempt} 次更换后)。"
notify_tg "❗复检不通过(第 ${attempt} 次更换后):${ip} 仍有端口不可访问。"
old_ip="$ip"
if [[ "$attempt" -eq "$MAX_CHANGE_RETRIES" ]]; then
echo "[$(ts)] 已连续更换 ${MAX_CHANGE_RETRIES} 次仍不通,停止继续更换。"
notify_tg "⛔ 已连续更换 ${MAX_CHANGE_RETRIES} 次仍不通,停止继续更换。请排查服务/防火墙/上游策略。"
exit 3
fi
fi
done
}
# 同时输出到屏幕和日志
main "$@" | tee -a "$LOG_FILE"
保存脚本到 /root/ip_probe.sh,授权:
chmod +x /root/ip_probe.sh
定时每3分钟运行一次检测
crontab -e
*/3 * * * * /root/ip_probe.sh >> /dev/null 2>&1
脚本说明
可手动更换的变量 / 参数(配置区)
脚本开头 ### =========== 配置区 =========== 里的变量,都是你后期可以直接改的:
端口组
PORTS=(59141 44299)
想加端口:PORTS=(59141 44299 50000 50001)
想只测一个端口:PORTS=(59141)
大陆探测节点
CN_HOST="xx.xx.xx"
CN_USER="root"
CN_PASS="你的root密码"
CN_PORT=54322
换主机:改 CN_HOST
不用 root:改 CN_USER(更安全),并给该用户 nc 权限即可
换 SSH 端口:改 CN_PORT
强烈建议后期改成密钥免密登录(避免明文密码),做法见后面建议
首检重试策略
ATTEMPTS=30
SLEEP_BETWEEN=1
每端口尝试次数、尝试间隔;
你要更保守可提到 50/1~2 秒;要更激进可降到 10/0.5 秒(但误判风险增大)。
换 IP 接口
CHANGE_IP_URL="http://100.100.101.101:8002/changeip"
如果换 IP 的 API 改了,或者需要带 token、POST JSON 等,直接改这里;
脚本目前用 curl -fsS --max-time 15 “CHANGE_IP_URL”
换 IP 后的复检策略
WAIT_AFTER_CHANGE=180 # 等 3 分钟
REVERIFY_ATTEMPTS=10 # 每端口 10 次
REVERIFY_INTERVAL=2 # 每次间隔 2s
MAX_CHANGE_RETRIES=5 # 最多换 5 次
网络收敛慢:把 WAIT_AFTER_CHANGE 提大(如 300 秒);
更严格复检:加大 REVERIFY_ATTEMPTS 或间隔;
更激进换 IP:把 MAX_CHANGE_RETRIES 提高(谨慎,避免死循环式频繁换 IP)。
Telegram 通知
TG_BOT_TOKEN="在此填入_BOT_TOKEN"
TG_CHAT_ID="在此填入_CHAT_ID"
换机器人/群:改这两项即可;
不想发通知:留空或注释掉这两行,通知函数内部会自动忽略。
日志
LOG_DIR="/root/log/ip_monitor"
LOG_RETENTION_DAYS=7
换路径:改 LOG_DIR;
保留时长:改 LOG_RETENTION_DAYS(单位:天)。
脚本做什么(一图读懂)
1.依赖自检
本机必须有:sshpass、nc(建议 netcat-openbsd 版)。
大陆探测节点(xx.xx.xx:54322)上必须有:nc。
—> 缺了就直接提示/发 TG,并退出,避免误判。
2.首检(当前 IP)
对端口组(默认 59141, 44299)逐个做连通性重试:每端口尝试 ATTEMPTS=30 次、间隔 SLEEP_BETWEEN=1s;
任一端口在 30 次内全部失败 ➜ 判定“不通,需要换 IP”;
只要每个端口里有一次成功,就视为该端口可用。
3.换 IP & 复检
首检不通过 → 调用 CHANGE_IP_URL 换 IP;
等 WAIT_AFTER_CHANGE=180s 再拿新 IP;
对端口组做复检(每端口 REVERIFY_ATTEMPTS=10 次、REVERIFY_INTERVAL=2s);
若仍判定“不通需要换 IP”,继续下一轮换 IP;最多 MAX_CHANGE_RETRIES=5 轮;
超过 5 次仍不通 → 停止,更明显地告警(日志 + Telegram)。
4.日志 & 通知
日志路径:/root/log/ip_monitor/YYYY-MM-DD.log;保留 LOG_RETENTION_DAYS=7 天;
每个关键节点都有 Telegram 通知(开始检测、换 IP、复检结果、停止等)。
评论区