2489 字
12 分钟
cloudflare 域名脚本
2022-05-14

这是一个用于管理 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 记录管理(添加 / 更新 / 删除)

使用方法#

Terminal window
# 自动适配模式
bash cloudflare2026.sh 域名
# 单独操作模式
bash cloudflare2026.sh 域名 4 # 仅IPv4
bash cloudflare2026.sh 域名 6 # 仅IPv6
bash 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)后。运行脚本 添加/更新 域名的解析

其他问题#

官方 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