logo NodeSeekbeta

分享一个基于Cloudflare的定时dns切换脚本,带健康监测以及通知,允许从KV获取

自己用AI写的
优化鸡和普通鸡上部署好一模一样的面板,就可以实现非高峰期用普通鸡省流量,高峰期用优化鸡,无缝切换

#!/bin/bash

# ================= 配置区 =================
# Cloudflare 认证信息
CF_TOKEN=""        # ⚠️ 注意:你的 Token 需要同时拥有 "DNS 修改" 和 "Workers KV 基础读取" 权限
ZONE_ID=""
ACCOUNT_ID="" # 💡 Cloudflare 账户 ID(在域名概述页右下角可以找到)
KV_NAMESPACE_ID=""     # 💡 KV 命名空间 ID

# 域名全称 (如 test.example.com)
RECORD_NAME="example.com"

# 指定用于解析 SCHEDULE 中域名的 DNS 服务器
DNS_SERVER="8.8.4.4"

# 🌟 本地备份数据(格式: "HH:MM@域名:主TCP端口:备用TCP端口")
# 当 Cloudflare 网络故障、API 限制或获取不到云端配置时,作为保底死数据使用
SCHEDULE=(
    "02:[email protected]:22:8080"
    "18:[email protected]:22:8080"
)


# 是否开启 Cloudflare 代理
PROXIED=false
# TCP 探测超时时间(秒)
CHECK_TIMEOUT=2

# Bark 通知配置
BARK_SERVER="https://api.day.app"
BARK_KEY=""  # ⚠️ 注意:请填写你自己的 Bark device Key
BARK_TITLE="🚀 节点切换通知"
# ==========================================

# Bark 发送通知函数
send_bark_notification() {
    local message="$1"
    local group="CF-Switch"
    local encoded_message=$(jq -rn --arg msg "$message" '$msg | @uri')
    local encoded_title=$(jq -rn --arg title "$BARK_TITLE" '$title | @uri')
    curl -s -X GET "$BARK_SERVER/$BARK_KEY/$encoded_title/$encoded_message?group=$group" &>/dev/null
}

# 检查系统依赖
for cmd in jq dig nc; do
    if ! command -v $cmd &> /dev/null; then
        echo "❌ 错误: 未检测到必要工具 [$cmd],请先运行: sudo apt install jq dnsutils netcat-openbsd -y"
        exit 1
    fi
done

CURRENT_TIME=$(date +"%H:%M")
echo "=========================================================="
echo "⏰ 运行时间: $(date '+%Y-%m-%d %H:%M:%S') ($CURRENT_TIME)"

# ========================================================
# 🌟 核心优化:动态从 Cloudflare KV 获取最新的实时时间表
# ========================================================
echo "☁️ 正在尝试从 Cloudflare KV 同步最新时间表..."
KV_URL="https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/storage/kv/namespaces/$KV_NAMESPACE_ID/values/uk_schedule"
KV_RESPONSE=$(curl -s -w "\n%{http_code}" -X GET "$KV_URL" -H "Authorization: Bearer $CF_TOKEN")

HTTP_CODE=$(echo "$KV_RESPONSE" | tail -n 1)
KV_VALUE=$(echo "$KV_RESPONSE" | head -n -1)

if [ "$HTTP_CODE" -eq 200 ] && [ -n "$KV_VALUE" ] && [ "$KV_VALUE" != "null" ]; then
    # 使用 jq 解析 JSON 数组并将其转换为 Bash 数组
    readarray -t CLOUD_SCHEDULE < <(echo "$KV_VALUE" | jq -r '.[]')
    if [ ${#CLOUD_SCHEDULE[@]} -gt 0 ]; then
        SCHEDULE=("${CLOUD_SCHEDULE[@]}")
        echo "✅ [云端同步] 成功加载 Cloudflare KV 实时时间表!节点数: ${#SCHEDULE[@]}"
    fi
else
    echo "⚠️ [降级预案] 无法从云端获取配置 (HTTP $HTTP_CODE)。切换为本地保底死数据。"
fi
# ========================================================

# 1. 匹配时段
TARGET_ENTRY=""
SORTED_SCHEDULE=$(printf "%s\n" "${SCHEDULE[@]}" | sort)
for entry in $SORTED_SCHEDULE; do
    CONF_TIME=$(echo $entry | cut -d'@' -f1)
    if [[ "$CURRENT_TIME" > "$CONF_TIME" ]] || [[ "$CURRENT_TIME" == "$CONF_TIME" ]]; then
        TARGET_ENTRY=$entry
    fi
done
[ -z "$TARGET_ENTRY" ] && TARGET_ENTRY=$(echo "$SORTED_SCHEDULE" | tail -n 1)

# 解析域名与双端口
TARGET_DOMAIN=$(echo "$TARGET_ENTRY" | cut -d'@' -f2 | cut -d':' -f1)
PORT_MAIN=$(echo "$TARGET_ENTRY" | cut -d'@' -f2 | cut -d':' -f2)
PORT_BACKUP=$(echo "$TARGET_ENTRY" | cut -d'@' -f2 | cut -d':' -f3)

echo "🔍 当前目标: $TARGET_DOMAIN (主端口: $PORT_MAIN / 备用: ${PORT_BACKUP:-无})"

# 2. 多 DNS 交叉解析(严格防御版:防止域名无效时误抓 DNS 自身 IP)
echo "🌐 正在解析域名..."
TARGET_IP=""
for dns in "$DNS_SERVER" "1.1.1.1"; do
    # 🌟 增加严格过滤:dig 结果必须包含纯公网解析,且排除掉传入的 DNS 变量自身
    RAW_DIG=$(dig @$dns +short +time=2 +tries=1 "$TARGET_DOMAIN" 2>/dev/null)
    
    if [ -n "$RAW_DIG" ] && [ "$RAW_DIG" != "$dns" ]; then
        TARGET_IP=$(echo "$RAW_DIG" | grep -E -o '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -n 1)
        [ -n "$TARGET_IP" ] && break
    fi
done

# 如果真的解析不到(比如域名写错了,或者国外DNS还没生效)
if [ -z "$TARGET_IP" ]; then
    echo "❌ [DNS 错误] 无法解析域名 [$TARGET_DOMAIN]!请检查该域名是否已在公网配置 A 记录。"
    echo "🛑 本次操作终止。"
    exit 1
fi
echo "🎯 解析结果: $TARGET_IP"

# 3. 智能双端口复合探测
echo "📡 正在进行网络可用性测试..."
IS_ALIVE=false
DETECTED_PORT=""

if nc -z -w $CHECK_TIMEOUT $TARGET_IP $PORT_MAIN &>/dev/null; then
    IS_ALIVE=true
    DETECTED_PORT=$PORT_MAIN
elif [ -n "$PORT_BACKUP" ] && nc -z -w $CHECK_TIMEOUT $TARGET_IP $PORT_BACKUP &>/dev/null; then
    IS_ALIVE=true
    DETECTED_PORT=$PORT_BACKUP
    echo "💡 提示: 主端口 $PORT_MAIN 好像没通,但备用端口 $PORT_BACKUP 响应正常。"
fi

if [ "$IS_ALIVE" = false ]; then
    echo "⚠️ [安全拦截] 目标服务器 $TARGET_IP 端口全灭!已取消本次切换。"
    exit 0
fi

# 4. 获取 CF 当前状态
RECORD_INFO=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?name=$RECORD_NAME&type=A" \
     -H "Authorization: Bearer $CF_TOKEN" \
     -H "Content-Type: application/json")

RECORD_ID=$(echo $RECORD_INFO | jq -r '.result[0].id')
CURRENT_CF_IP=$(echo $RECORD_INFO | jq -r '.result[0].content')

if [ "$RECORD_ID" == "null" ] || [ -z "$RECORD_ID" ]; then
    echo "❌ [CF 错误] 无法获取 $RECORD_NAME 的 A 记录。"
    exit 1
fi

if [ "$TARGET_IP" == "$CURRENT_CF_IP" ]; then
    echo "💤 解析已是最新 ($TARGET_IP),无需改动。"
    exit 0
fi

# 5. 执行平滑切换并推送通知
echo "🔄 正在将 $RECORD_NAME 切换至 $TARGET_IP..."
UPDATE_RESULT=$(curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \
     -H "Authorization: Bearer $CF_TOKEN" \
     -H "Content-Type: application/json" \
     --data "{\"content\": \"$TARGET_IP\", \"proxied\": $PROXIED}")

if [[ $(echo $UPDATE_RESULT | jq -r '.success') == "true" ]]; then
    msg="切换目标:$RECORD_NAME
时段匹配:$TARGET_DOMAIN
新IP地址:$TARGET_IP
原IP地址:$CURRENT_CF_IP
探测端口:$DETECTED_PORT 通畅"
    echo "$msg"
    send_bark_notification "$msg"
else
    echo "❌ [切换失败] CF 返回错误: $(echo $UPDATE_RESULT | jq -r '.errors[0].message')"
fi

xhj015

12
12

你好啊,陌生人!

我的朋友,看起来你是新来的,如果想参与到讨论中,点击下面的按钮!

📈用户数目📈

目前论坛共有72267位seeker

🎉欢迎新用户🎉