Logo迷失博客
返回文章列表
常用工具阅读时长:13 分钟

cloudflare 域名脚本

发布时间:2022年05月14日 08:00最后修改:2026年09月15日 06:15

这是一个用于管理 Cloudflare DNS 双栈(IPv4+IPv6)记录的 Bash 脚本,主要功能是自动检测本机公网 IP 并同步到 Cloudflare 域名解析,支持多种操作模式,具体介绍

这是一个用于管理 Cloudflare DNS 双栈(IPv4+IPv6)记录的 Bash 脚本,主要功能是自动检测本机公网 IP 并同步到 Cloudflare 域名解析,支持多种操作模式,具体介绍如下:

核心功能

DNS 记录管理:支持 A 记录(IPv4)和 AAAA 记录(IPv6)的自动添加、更新和删除

多模式操作:

  • 自动适配模式(默认):根据检测到的 IP 类型(IPv4/IPv6)自动管理对应记录
  • 仅 IPv4模式:只处理 A 记录
  • 仅 IPv6模式:只处理 AAAA 记录
  • 删除所有模式:删除指定域名的所有 A/AAAA 记录
  • 增强错误处理:包含 API 重试机制、JSON 响应验证、网络连通性检测等

主要组件

配置区:需手动设置 Cloudflare 账号邮箱、API 令牌、TTL 和代理状态等参数

工具函数:

  • 依赖检查与自动安装(curl、jq、ping6等)
  • 网络连通性验证(特别是 IPv6)
  • Cloudflare API 调用封装(带重试机制)
  • 日志输出格式化(错误 / 信息 / 警告分类)

核心逻辑:

  • 获取公网 IP(同时检测 IPv4和 IPv6)
  • 查询 Cloudflare 域名 Zone ID 和现有 DNS 记录
  • 根据操作模式执行相应的 DNS 记录管理(添加 / 更新 / 删除)

使用方法

# 自动适配模式
bash cloudflare2026.sh 域名

# 单独操作模式
bash cloudflare2026.sh 域名 4   # 仅IPv4
bash cloudflare2026.sh 域名 6   # 仅IPv6
bash cloudflare2026.sh 域名 1   # 删除所有记录

特点

  • 自动检测并安装缺失的依赖工具
  • 对 IPv6连通性进行多重验证
  • API 调用包含重试机制,提高稳定性
  • 详细的日志输出,便于排查问题
  • 自动清理无效记录(如仅 IPv4模式下会删除 IPv6记录)

使用前需确保已在 Cloudflare 上添加域名,并配置正确的 API 令牌(需包含 DNS:Edit 权限)。

申请 CloudFlare API

前往 https://dash.cloudflare.com/profile/api-tokens 登录后,右上角选择个人资料,点击创建令牌

为了安全,用自定义令牌,没有用全局令牌。如你非要用全局令牌,请修改脚本 Authorization 全部替换为 X-Auth-Key

编辑 cloudFlare.sh 内的参数(API 与 Email)后。运行脚本 添加/更新 域名的解析

其他问题

官方 API 文档

脚本开源

#!/usr/bin/env bash
# 功能: Cloudflare DNS 双栈管理(IPv4+IPv6)+ 增强错误处理
# 使用方法: 
#   1. 自动适配:bash script.sh 域名
#   2. 单独操作:bash script.sh 域名 操作类型(4=仅IPv4 6=仅IPv6 1=删除所有 0=自动)

# -------------------------- 配置区(必改)--------------------------
AUTH_EMAIL="你的参数"       # Cloudflare账号邮箱
AUTH_TOKEN="你的参数"  # Bearer令牌(需DNS:Edit权限)
TTL=1                                  # DNS缓存时间(1=自动)
PROXIED=false                          # 是否启用Cloudflare代理(true/false)
# IPv6连通性验证节点
IPV6_PING_NODES=(
    "2606:4700:4700::1111"  # Cloudflare DNS IPv6
    "2001:4860:4860::8888"  # Google DNS IPv6
    "2400:3200::1"          # Alibaba DNS IPv6
)
# -------------------------------------------------------------------

# 初始化核心变量
RECORD_NAME="$1"
OPERATION="${2:-0}"
ZONE_NAME="${RECORD_NAME#*.}"
IPV4=""
IPV6=""
ZONE_ID=""
API_BASE="https://api.cloudflare.com/client/v4/zones"
EXISTING_IDS=()
EXISTING_TYPES=()
EXISTING_CONTENTS=()

# -------------------------- 工具函数 --------------------------
# 错误处理(红色)
error_exit() {
    echo -e "\033[31m错误:$1\033[0m" >&2
    exit 1
}

# 日志输出(绿色)
log_info() {
    echo -e "\033[32m$1\033[0m"
}

# 警告输出(黄色)
log_warn() {
    echo -e "\033[33m警告:$1\033[0m"
}

# 安装依赖
install_dependency() {
    local dep=$1
    log_info "正在安装依赖: $dep"
    
    if command -v apt-get &> /dev/null; then
        sudo apt-get update -qq > /dev/null
        sudo apt-get install -qq -y "$dep" > /dev/null
    elif command -v yum &> /dev/null; then
        sudo yum install -y "$dep" > /dev/null
    elif command -v dnf &> /dev/null; then
        sudo dnf install -y "$dep" > /dev/null
    elif command -v pacman &> /dev/null; then
        sudo pacman -S --noconfirm "$dep" > /dev/null
    else
        error_exit "不支持的操作系统,无法自动安装依赖 $dep,请手动安装"
    fi

    if ! command -v "$dep" &> /dev/null; then
        error_exit "依赖 $dep 安装失败,请手动安装"
    fi
}

# 检查并安装依赖
check_deps() {
    local missing=0
    local deps=()

    if ! command -v curl &> /dev/null; then
        echo "缺少依赖:curl" >&2
        deps+=("curl")
        missing=1
    fi
    if ! command -v jq &> /dev/null; then
        echo "缺少依赖:jq" >&2
        deps+=("jq")
        missing=1
    fi
    
    # 检查IPv6 ping工具
    local ping6_available=0
    if command -v ping6 &> /dev/null; then
        ping6_available=1
    elif ping -6 -c 1 -W 1 ::1 &> /dev/null; then
        ping6_available=1
    fi
    if [ $ping6_available -eq 0 ]; then
        echo "缺少依赖:IPv6 ping工具" >&2
        # 根据不同系统添加对应的iputils包
        if command -v apt-get &> /dev/null; then
            deps+=("iputils-ping")
        elif command -v yum &> /dev/null || command -v dnf &> /dev/null; then
            deps+=("iputils")
        elif command -v pacman &> /dev/null; then
            deps+=("iputils")
        fi
        missing=1
    fi

    if [ $missing -eq 1 ]; then
        log_info "开始安装缺失的依赖..."
        for dep in "${deps[@]}"; do
            install_dependency "$dep"
        done
        log_info "所有依赖安装完成"
    fi
}

# 验证响应是否为JSON(修复HTML响应问题)
is_valid_json() {
    local input="$1"
    # 检查是否包含HTML标签(常见错误情况)
    if echo "$input" | grep -qi '<html>\|<body>\|<doctype'; then
        return 1
    fi
    # 使用jq验证JSON格式
    echo "$input" | jq . >/dev/null 2>&1
    return $?
}

# IPv6网络连通性验证
verify_ipv6_connectivity() {
    log_info "正在验证IPv6网络连通性..."
    local ping_success=0
    local ping_cmd="ping6"
    if ! command -v ping6 &> /dev/null; then
        ping_cmd="ping -6"
    fi

    for node in "${IPV6_PING_NODES[@]}"; do
        log_info "正在ping IPv6节点:$node..."
        if $ping_cmd -c 1 -W 3 "$node" &> /dev/null; then
            log_info "✅ IPv6节点[$node] ping通"
            ping_success=1
            break
        fi
    done

    if [ "$ping_success" -eq 0 ]; then
        error_exit "❌ 所有IPv6节点均无法连通,取消IPv6操作"
    fi
}

# 获取本机公网IP
get_public_ips() {
    log_info "正在检测本机公网IP..."
    
    # 获取IPv4(带错误处理)
    IPV4=$(curl -4 -s --max-time 5 --retry 2 api64.ipify.org)
    if [ -z "$IPV4" ]; then
        IPV4=$(curl -4 -s --max-time 5 --retry 2 ipv4.ip.sb)
    fi

    # 获取IPv6(带错误处理)
    IPV6=$(curl -6 -s --max-time 5 --retry 2 api64.ipify.org)
    if [ -z "$IPV6" ]; then
        IPV6=$(curl -6 -s --max-time 5 --retry 2 ipv6.ip.sb)
    fi

    # 验证并输出结果
    if [ -n "$IPV6" ]; then
        verify_ipv6_connectivity
        log_info "✅ 检测到IPv6:$IPV6"
    fi
    if [ -n "$IPV4" ]; then
        log_info "✅ 检测到IPv4:$IPV4"
    fi
    if [ -z "$IPV4" ] && [ -z "$IPV6" ]; then
        error_exit "❌ 未检测到任何公网IP"
    fi
}

# 调用Cloudflare API(增强版)
call_api() {
    local method="$1"
    local endpoint="$2"
    local data="${3:-}"

    # 执行API请求(增加重试机制)
    for ((attempt=1; attempt<=3; attempt++)); do
        response=$(curl -s -w "\n%{http_code}" -X "$method" \
            -H "X-Auth-Email: $AUTH_EMAIL" \
            -H "Authorization: Bearer $AUTH_TOKEN" \
            -H "Content-Type: application/json" \
            ${data:+-d "$data"} \
            "$API_BASE$endpoint")

        http_code=$(echo "$response" | tail -n1)
        response_body=$(echo "$response" | head -n -1)

        # 检查HTTP状态码
        if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
            # 检查是否为有效JSON
            if is_valid_json "$response_body"; then
                # 检查API成功状态
                if echo "$response_body" | jq -e '.success' > /dev/null; then
                    echo "$response_body"
                    return 0
                fi
            fi
        fi

        # 重试提示
        if [ $attempt -lt 3 ]; then
            log_warn "API请求尝试$attempt失败,正在重试..."
            sleep 2
        fi
    done

    # 详细错误信息
    error_exit "API请求失败:
    方法: $method
    地址: $API_BASE$endpoint
    状态码: $http_code
    响应: $(echo "$response_body" | head -n 10)  # 显示前10行"
}

# 获取Cloudflare Zone ID
get_zone_id() {
    log_info "正在获取Zone ID($ZONE_NAME)..."
    response=$(call_api "GET" "?name=$ZONE_NAME")
    ZONE_ID=$(echo "$response" | jq -r '.result[0].id')
    
    if [ -z "$ZONE_ID" ] || [ "$ZONE_ID" = "null" ]; then
        error_exit "未找到域名$ZONE_NAME,请检查:
        1. 域名是否已添加到Cloudflare
        2. 认证信息是否正确
        3. API令牌是否有足够权限"
    fi
    log_info "✅ 成功获取Zone ID:$ZONE_ID"
}

# 获取现有DNS记录
get_existing_records() {
    log_info "正在查询现有DNS记录($RECORD_NAME)..."
    response=$(call_api "GET" "/$ZONE_ID/dns_records?name=$RECORD_NAME")
    
    local record_count=$(echo "$response" | jq '.result | length')
    if [ "$record_count" -eq 0 ]; then
        log_info "ℹ️ 未查询到任何DNS记录"
        return
    fi

    # 提取A/AAAA记录
    for ((i=0; i<record_count; i++)); do
        local type=$(echo "$response" | jq -r ".result[$i].type")
        if [ "$type" = "A" ] || [ "$type" = "AAAA" ]; then
            EXISTING_IDS+=($(echo "$response" | jq -r ".result[$i].id"))
            EXISTING_TYPES+=("$type")
            EXISTING_CONTENTS+=($(echo "$response" | jq -r ".result[$i].content"))
        fi
    done

    # 显示记录
    log_info "✅ 查询到${#EXISTING_TYPES[@]}条有效记录:"
    for ((i=0; i<${#EXISTING_TYPES[@]}; i++)); do
        echo "  - ${EXISTING_TYPES[$i]}: ${EXISTING_CONTENTS[$i]}"
    done
}

# -------------------------- DNS操作函数 --------------------------
add_record() {
    local type="$1"
    local content="$2"

    log_info "正在添加$type记录:$RECORD_NAME$content"
    local data=$(jq -n \
        --arg type "$type" \
        --arg name "$RECORD_NAME" \
        --arg content "$content" \
        --argjson ttl "$TTL" \
        --argjson proxied "$PROXIED" \
        '{type: $type, name: $name, content: $content, ttl: $ttl, proxied: $proxied}')
    
    call_api "POST" "/$ZONE_ID/dns_records" "$data"
    log_info "✅ $type记录添加成功"
}

update_record() {
    local record_id="$1"
    local type="$2"
    local old_ip="$3"
    local new_ip="$4"

    log_info "正在更新$type记录:$old_ip$new_ip"
    local data=$(jq -n \
        --arg type "$type" \
        --arg name "$RECORD_NAME" \
        --arg content "$new_ip" \
        --argjson ttl "$TTL" \
        --argjson proxied "$PROXIED" \
        '{type: $type, name: $name, content: $content, ttl: $ttl, proxied: $proxied}')
    
    call_api "PUT" "/$ZONE_ID/dns_records/$record_id" "$data"
    log_info "✅ $type记录更新成功"
}

delete_record() {
    local record_id="$1"
    local type="$2"
    local content="$3"

    log_info "正在删除$type记录:$content"
    call_api "DELETE" "/$ZONE_ID/dns_records/$record_id"
    log_info "✅ $type记录删除成功"
}

# -------------------------- 主逻辑函数 --------------------------
manage_ipv4() {
    local has_a_record=0
    local a_record_id=""
    local a_old_ip=""

    # 检查现有A记录
    for ((i=0; i<${#EXISTING_TYPES[@]}; i++)); do
        if [ "${EXISTING_TYPES[$i]}" = "A" ]; then
            has_a_record=1
            a_record_id="${EXISTING_IDS[$i]}"
            a_old_ip="${EXISTING_CONTENTS[$i]}"
            break
        fi
    done

    # 处理A记录
    if [ "$has_a_record" -eq 1 ]; then
        if [ "$a_old_ip" = "$IPV4" ]; then
            log_info "ℹ️ A记录已为最新:$IPV4"
        else
            update_record "$a_record_id" "A" "$a_old_ip" "$IPV4"
        fi
    else
        add_record "A" "$IPV4"
    fi

    # 清理残留IPv6记录
    if [ -z "$IPV6" ] && [ "$OPERATION" -ne 6 ]; then
        for ((i=0; i<${#EXISTING_TYPES[@]}; i++)); do
            if [ "${EXISTING_TYPES[$i]}" = "AAAA" ]; then
                delete_record "${EXISTING_IDS[$i]}" "AAAA" "${EXISTING_CONTENTS[$i]}"
            fi
        done
    fi
}

manage_ipv6() {
    # 二次验证IPv6连通性
    verify_ipv6_connectivity

    local has_aaaa_record=0
    local aaaa_record_id=""
    local aaaa_old_ip=""

    # 检查现有AAAA记录
    for ((i=0; i<${#EXISTING_TYPES[@]}; i++)); do
        if [ "${EXISTING_TYPES[$i]}" = "AAAA" ]; then
            has_aaaa_record=1
            aaaa_record_id="${EXISTING_IDS[$i]}"
            aaaa_old_ip="${EXISTING_CONTENTS[$i]}"
            break
        fi
    done

    # 处理AAAA记录
    if [ "$has_aaaa_record" -eq 1 ]; then
        if [ "$aaaa_old_ip" = "$IPV6" ]; then
            log_info "ℹ️ AAAA记录已为最新:$IPV6"
        else
            update_record "$aaaa_record_id" "AAAA" "$aaaa_old_ip" "$IPV6"
        fi
    else
        add_record "AAAA" "$IPV6"
    fi

    # 清理残留IPv4记录
    if [ -z "$IPV4" ] && [ "$OPERATION" -ne 4 ]; then
        for ((i=0; i<${#EXISTING_TYPES[@]}; i++)); do
            if [ "${EXISTING_TYPES[$i]}" = "A" ]; then
                delete_record "${EXISTING_IDS[$i]}" "A" "${EXISTING_CONTENTS[$i]}"
            fi
        done
    fi
}

delete_all_records() {
    if [ ${#EXISTING_TYPES[@]} -eq 0 ]; then
        log_info "ℹ️ 没有记录可删除"
        return
    fi

    log_info "正在删除所有记录(共${#EXISTING_TYPES[@]}条)..."
    for ((i=0; i<${#EXISTING_TYPES[@]}; i++)); do
        delete_record "${EXISTING_IDS[$i]}" "${EXISTING_TYPES[$i]}" "${EXISTING_CONTENTS[$i]}"
    done
}

# -------------------------- 主流程 --------------------------
main() {
    # 参数检查
    if [ -z "$RECORD_NAME" ]; then
        error_exit "请指定域名!使用方法:
  自动适配:$0 域名
  单独操作:$0 域名 [4/6/1]"
    fi

    # 环境检查
    check_deps

    # 主逻辑执行
    echo -e "\n===== Cloudflare DNS 双栈管理工具 ====="
    get_public_ips
    get_zone_id
    get_existing_records
    
    echo -e "\n----------------------------------------"
    case "$OPERATION" in
        0)  # 自动适配
            log_info "执行自动适配模式"
            [ -n "$IPV4" ] && manage_ipv4
            [ -n "$IPV6" ] && manage_ipv6
            ;;
        4)  # 仅IPv4
            [ -z "$IPV4" ] && error_exit "未检测到IPv4地址"
            log_info "执行仅IPv4模式"
            manage_ipv4
            ;;
        6)  # 仅IPv6
            [ -z "$IPV6" ] && error_exit "未检测到IPv6地址"
            log_info "执行仅IPv6模式"
            manage_ipv6
            ;;
        1)  # 删除所有
            log_info "执行删除所有记录模式"
            delete_all_records
            ;;
        *)  # 无效操作
            error_exit "无效操作类型!可选值:0=自动 4=仅IPv4 6=仅IPv6 1=删除所有"
            ;;
    esac

    echo -e "\n----------------------------------------"
    log_info "操作完成"
}

# 启动主程序
main

推荐阅读