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