2489 字
12 分钟
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 # 仅IPv4bash cloudflare2026.sh 域名 6 # 仅IPv6bash cloudflare2026.sh 域名 1 # 删除所有记录特点
- 自动检测并安装缺失的依赖工具
- 对 IPv6连通性进行多重验证
- API 调用包含重试机制,提高稳定性
- 详细的日志输出,便于排查问题
- 自动清理无效记录(如仅 IPv4模式下会删除 IPv6记录)
使用前需确保已在 Cloudflare 上添加域名,并配置正确的 API 令牌(需包含 DNS
权限)。
申请 CloudFlare API
前往 https://dash.cloudflare.com/profile/api-tokens 登录后,右上角选择个人资料,点击创建令牌
为了安全,用自定义令牌,没有用全局令牌。如你非要用全局令牌,请修改脚本 Authorization 全部替换为 X-Auth-Key
编辑 cloudFlare.sh 内的参数(API 与 Email)后。运行脚本 添加/更新 域名的解析
其他问题
脚本开源
#!/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}
# 获取本机公网IPget_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 IDget_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