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

使用方法

1
2
3
4
5
6
7
# 自动适配模式
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文档

脚本开源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
#!/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