207 lines
5.4 KiB
Bash
Executable File
207 lines
5.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# mihomo-guard.sh
|
|
# 自动保活 + 自动刷新订阅 + 自动切换可用线路
|
|
#
|
|
# 目标:
|
|
# - 定期通过 mihomo 代理探测 Telegram 官方入口连通性
|
|
# - 连续失败后,自动刷新订阅并切换到候选节点
|
|
# - 若切换后仍失败,再 fallback 重启 mihomo
|
|
#
|
|
# 说明:
|
|
# - 依赖 mihomo external-controller API (9090)
|
|
# - 当前按你的环境写死了 controller / secret / provider / group 名称
|
|
|
|
# ===== 可调参数 =====
|
|
|
|
PROBE_URL="https://api.telegram.org"
|
|
PROXY_ADDR="http://127.0.0.1:7897"
|
|
PROBE_TIMEOUT=4
|
|
SLEEP_INTERVAL=15
|
|
MAX_FAIL_COUNT=5
|
|
|
|
MIHOMO_BIN="/usr/local/opt/mihomo/bin/mihomo"
|
|
MIHOMO_DIR="/usr/local/etc/mihomo"
|
|
LOG_FILE="/usr/local/var/mihomo-guard.log"
|
|
|
|
# Mihomo controller API
|
|
CONTROLLER_URL="http://127.0.0.1:9090"
|
|
CONTROLLER_SECRET="123456"
|
|
SUB_PROVIDER="my_sub"
|
|
|
|
# 代理组名称(基于你当前 API 返回)
|
|
MANUAL_GROUP="手动选择"
|
|
AI_GROUP="🤖 AI服务"
|
|
DEV_GROUP="💻 开发服务"
|
|
|
|
# 故障恢复时优先尝试的候选节点(亚洲优先)
|
|
CANDIDATE_PROXIES=(
|
|
"🇭🇰香港01-IPv6|AI"
|
|
"🇭🇰香港03-IPv6|AI"
|
|
"🇭🇰香港05-流媒体解锁|AI"
|
|
"🇸🇬新加坡01-流媒体解锁|AI"
|
|
"🇸🇬新加坡02-流媒体解锁|AI"
|
|
"🇨🇳台湾03-流媒体解锁|AI"
|
|
"🇨🇳台湾04-流媒体解锁|AI"
|
|
"🇯🇵日本03"
|
|
"🇯🇵日本01"
|
|
)
|
|
|
|
FAIL_COUNT=0
|
|
LAST_SWITCH_INDEX=-1
|
|
|
|
log() {
|
|
local ts
|
|
ts="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
echo "[$ts] $*" | tee -a "$LOG_FILE"
|
|
}
|
|
|
|
api_call() {
|
|
local method="$1"
|
|
local path="$2"
|
|
local data="${3:-}"
|
|
|
|
if [ -n "$data" ]; then
|
|
curl -sS -X "$method" \
|
|
-H "Authorization: Bearer $CONTROLLER_SECRET" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$data" \
|
|
"$CONTROLLER_URL$path"
|
|
else
|
|
curl -sS -X "$method" \
|
|
-H "Authorization: Bearer $CONTROLLER_SECRET" \
|
|
"$CONTROLLER_URL$path"
|
|
fi
|
|
}
|
|
|
|
probe_once() {
|
|
curl -x "$PROXY_ADDR" -m "$PROBE_TIMEOUT" -sS "$PROBE_URL" >/dev/null
|
|
}
|
|
|
|
refresh_subscription() {
|
|
log "[ACTION] Refreshing provider: $SUB_PROVIDER"
|
|
api_call PUT "/providers/proxies/$SUB_PROVIDER" >/dev/null 2>&1
|
|
}
|
|
|
|
switch_group_to() {
|
|
local group_name="$1"
|
|
local target_proxy="$2"
|
|
|
|
log "[ACTION] Switching group [$group_name] -> [$target_proxy]"
|
|
api_call PUT "/proxies/$group_name" "{\"name\":\"$target_proxy\"}" >/dev/null 2>&1
|
|
}
|
|
|
|
try_switch_routes() {
|
|
log "[ACTION] Starting provider refresh + route switching workflow"
|
|
|
|
refresh_subscription
|
|
sleep 3
|
|
|
|
local total=${#CANDIDATE_PROXIES[@]}
|
|
local i
|
|
for ((i=1; i<=total; i++)); do
|
|
local idx=$(((LAST_SWITCH_INDEX + i) % total))
|
|
local candidate="${CANDIDATE_PROXIES[$idx]}"
|
|
|
|
log "[INFO] Trying candidate proxy: $candidate"
|
|
|
|
# 1) 手动选择 -> 指定节点
|
|
switch_group_to "$MANUAL_GROUP" "$candidate"
|
|
sleep 1
|
|
|
|
# 2) 关键业务组切到手动选择,避免继续受 URLTest 组影响
|
|
switch_group_to "$AI_GROUP" "$MANUAL_GROUP"
|
|
sleep 1
|
|
switch_group_to "$DEV_GROUP" "$MANUAL_GROUP"
|
|
sleep 1
|
|
|
|
# 给 mihomo 一点时间收敛
|
|
sleep 3
|
|
|
|
if probe_once; then
|
|
LAST_SWITCH_INDEX=$idx
|
|
log "[OK] Probe success after switching to [$candidate]"
|
|
log "[RECOVERED] 当前恢复到线路: [$candidate]"
|
|
return 0
|
|
else
|
|
log "[WARN] Probe still failed after switching to [$candidate]"
|
|
fi
|
|
done
|
|
|
|
log "[ERROR] All candidate proxies failed after refresh + switching"
|
|
return 1
|
|
}
|
|
|
|
restart_mihomo() {
|
|
log "[ACTION] Restarting mihomo..."
|
|
|
|
if pgrep -f "$MIHOMO_BIN" >/dev/null 2>&1; then
|
|
log "[INFO] Existing mihomo process found, sending TERM"
|
|
pkill -f "$MIHOMO_BIN" || log "[WARN] pkill mihomo returned non-zero"
|
|
sleep 3
|
|
else
|
|
log "[INFO] No existing mihomo process found"
|
|
fi
|
|
|
|
if [ ! -x "$MIHOMO_BIN" ]; then
|
|
log "[ERROR] mihomo binary not found or not executable: $MIHOMO_BIN"
|
|
return 1
|
|
fi
|
|
|
|
if [ ! -d "$MIHOMO_DIR" ]; then
|
|
log "[ERROR] mihomo directory not found: $MIHOMO_DIR"
|
|
return 1
|
|
fi
|
|
|
|
log "[INFO] Starting mihomo with: $MIHOMO_BIN -d $MIHOMO_DIR"
|
|
nohup "$MIHOMO_BIN" -d "$MIHOMO_DIR" >>"$LOG_FILE" 2>&1 &
|
|
sleep 3
|
|
|
|
if pgrep -f "$MIHOMO_BIN" >/dev/null 2>&1; then
|
|
log "[INFO] mihomo started successfully (pid: $(pgrep -f "$MIHOMO_BIN" | tr '\n' ' '))"
|
|
else
|
|
log "[ERROR] Failed to start mihomo"
|
|
return 1
|
|
fi
|
|
|
|
sleep 5
|
|
|
|
if probe_once; then
|
|
log "[OK] Probe success after mihomo restart"
|
|
log "[RECOVERED] 当前恢复到线路: [mihomo重启后默认线路]"
|
|
return 0
|
|
else
|
|
log "[WARN] Probe still failed after mihomo restart"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
log "=== mihomo-guard started (PROXY=$PROXY_ADDR, URL=$PROBE_URL, INTERVAL=${SLEEP_INTERVAL}s, MAX_FAIL=$MAX_FAIL_COUNT) ==="
|
|
log "=== controller=$CONTROLLER_URL provider=$SUB_PROVIDER manual_group=$MANUAL_GROUP ==="
|
|
|
|
while true; do
|
|
if probe_once; then
|
|
if [ "$FAIL_COUNT" -gt 0 ]; then
|
|
log "[OK] Probe success after $FAIL_COUNT failure(s)"
|
|
fi
|
|
FAIL_COUNT=0
|
|
else
|
|
FAIL_COUNT=$((FAIL_COUNT + 1))
|
|
log "[WARN] Probe failed ($FAIL_COUNT/$MAX_FAIL_COUNT)"
|
|
|
|
if [ "$FAIL_COUNT" -ge "$MAX_FAIL_COUNT" ]; then
|
|
log "[WARN] Consecutive probe failures reached $FAIL_COUNT, trying refresh + route switch"
|
|
|
|
if try_switch_routes; then
|
|
FAIL_COUNT=0
|
|
else
|
|
log "[WARN] Refresh + route switch failed, falling back to mihomo restart"
|
|
restart_mihomo || log "[ERROR] Restart fallback did not recover probe"
|
|
FAIL_COUNT=0
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
sleep "$SLEEP_INTERVAL"
|
|
done
|