功能介绍
这是一个自动更新 Cloudflare DNS 记录的脚本,支持 IPv4 (A记录) 和 IPv6 (AAAA记录) 的动态DNS更新。
文件结构
ddns.sh # 主脚本文件
ddns.conf # 配置文件
配置文件格式
创建 ddns.conf
文件,内容如下:
# Cloudflare API Token
CF_API_TOKEN="你的API Token"
# 域名配置
DOMAIN="example.com" # 主域名
SUBDOMAIN="home" # 子域名
# 记录类型: A 或 AAAA
RECORD_TYPE="A"
获取 Cloudflare API Token
- 登录 Cloudflare 控制台
- 进入
我的个人资料
→API 令牌
- 点击
创建令牌
- 选择
自定义令牌
模板 - 设置权限:
- 区域 –
Zone:Read
- DNS –
Zone:Edit
- 区域 –
- 区域资源选择你的域名
- 创建并复制 Token
使用方法
基本使用
chmod +x ddns.sh
./ddns.sh ddns.conf
定时任务
编辑 crontab:
crontab -e
添加定时任务(每5分钟检查一次):
*/5 * * * * /path/to/ddns.sh /path/to/ddns.conf >> /var/log/ddns.log 2>&1
支持的系统
- Debian/Ubuntu (使用 apt)
- CentOS/RHEL (使用 yum)
- OpenWrt (使用 opkg)
依赖包
脚本会自动安装所需依赖:
curl
– HTTP请求jq
– JSON解析
输出示例
成功更新
正在获取当前出口IP...
当前出口IP: 192.168.1.100
正在查询DNS记录: home.example.com (A)
通过dig获取到DNS IP: 192.168.1.99
IP不一致,需要更新: DNS=192.168.1.99, 当前=192.168.1.100
正在获取Cloudflare Zone ID...
Zone ID: abc123...
正在查询现有DNS记录...
记录存在,Record ID: def456...,更新中...
更新成功: home.example.com -> 192.168.1.100
无需更新
正在获取当前出口IP...
当前出口IP: 192.168.1.100
正在查询DNS记录: home.example.com (A)
通过dig获取到DNS IP: 192.168.1.100
出口 IP 与 DNS 一致,无需更新 (192.168.1.100)
配置说明
参数 | 说明 | 示例 |
---|---|---|
CF_API_TOKEN |
Cloudflare API Token | abc123def456... |
DOMAIN |
主域名 | example.com |
SUBDOMAIN |
子域名 | home |
RECORD_TYPE |
记录类型 | A 或 AAAA |
最终更新的域名为:SUBDOMAIN.DOMAIN
常见问题
1. 获取IP失败
- 检查网络连接
- 确认可以访问
https://v4.ipgg.cn/ip
或https://v6.ipgg.cn/ip
2. API调用失败
- 检查
CF_API_TOKEN
是否正确 - 确认 Token 权限包含域名的 Zone:Read 和 Zone:Edit
3. DNS查询失败
- 确认域名存在
- 检查
dig
或nslookup
命令是否可用
4. 权限错误
- 确保脚本有执行权限:
chmod +x ddns.sh
- 检查配置文件路径是否正确
日志记录
在 crontab 中设置日志:
*/5 * * * * /path/to/ddns.sh /path/to/ddns.conf >> /var/log/ddns.log 2>&1
DDNS脚本内容
#!/bin/bash
CONFIG_FILE="$1"
[ -z "$CONFIG_FILE" ] && echo "使用方法: $0 <配置文件路径>" && exit 1
[ ! -f "$CONFIG_FILE" ] && echo "配置文件不存在: $CONFIG_FILE" && exit 1
source "$CONFIG_FILE"
need_cmd() {
command -v "$1" >/dev/null 2>&1 || {
echo "缺少命令: $1,尝试安装..."
install_pkg "$1" || exit 1
}
}
install_pkg() {
if [ -f /etc/debian_version ]; then
apt update -qq && apt install -y -qq "$1"
elif [ -f /etc/redhat-release ]; then
yum install -y "$1"
elif command -v opkg >/dev/null 2>&1; then
opkg update && opkg install "$1"
else
return 1
fi
}
get_dns_ip() {
local name="$1"
local type="$2"
echo "正在查询DNS记录: $name ($type)" >&2
if command -v dig >/dev/null 2>&1; then
local ip=$(dig +short "$name" "$type" | head -1)
if [[ "$ip" =~ ^[0-9a-fA-F\.:]+$ ]]; then
echo "通过dig获取到DNS IP: $ip" >&2
echo "$ip"
return
fi
fi
if command -v nslookup >/dev/null 2>&1; then
local ip
if [ "$type" = "A" ]; then
ip=$(nslookup "$name" 2>/dev/null | grep "Address:" | tail -1 | awk '{print $2}')
else
ip=$(nslookup -type=AAAA "$name" 2>/dev/null | grep "Address:" | tail -1 | awk '{print $2}')
fi
if [[ "$ip" =~ ^[0-9a-fA-F\.:]+$ ]]; then
echo "通过nslookup获取到DNS IP: $ip" >&2
echo "$ip"
return
fi
fi
echo "DNS查询失败,记录可能不存在" >&2
echo ""
}
need_cmd curl
need_cmd jq
[ -z "$CF_API_TOKEN" ] && echo "CF_API_TOKEN 未设置" && exit 1
[ -z "$DOMAIN" ] && echo "DOMAIN 未设置" && exit 1
[ -z "$SUBDOMAIN" ] && echo "SUBDOMAIN 未设置" && exit 1
[ -z "$RECORD_TYPE" ] && echo "RECORD_TYPE 未设置" && exit 1
case "$RECORD_TYPE" in
A) IP_API_URL="https://v4.ipgg.cn/ip"; DNS_TYPE="A" ;;
AAAA) IP_API_URL="https://v6.ipgg.cn/ip"; DNS_TYPE="AAAA" ;;
*) echo "RECORD_TYPE 必须为 A 或 AAAA" && exit 1 ;;
esac
echo "正在获取当前出口IP..."
CURRENT_IP=$(curl -s --max-time 10 "$IP_API_URL" | jq -r '.ip')
[[ ! "$CURRENT_IP" =~ ^[0-9a-fA-F\.:]+$ ]] && echo "获取出口 IP 失败: $CURRENT_IP" && exit 1
echo "当前出口IP: $CURRENT_IP"
record_name="$SUBDOMAIN.$DOMAIN"
DNS_IP=$(get_dns_ip "$record_name" "$DNS_TYPE")
if [ -n "$DNS_IP" ]; then
if [ "$CURRENT_IP" == "$DNS_IP" ]; then
echo "出口 IP 与 DNS 一致,无需更新 ($CURRENT_IP)"
exit 0
else
echo "IP不一致,需要更新: DNS=$DNS_IP, 当前=$CURRENT_IP"
fi
else
echo "DNS记录不存在或查询失败,将创建新记录"
fi
echo "正在获取Cloudflare Zone ID..."
zone_json=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$DOMAIN" \
-H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json")
ZONE_ID=$(echo "$zone_json" | jq -r '.result[0].id')
[ -z "$ZONE_ID" ] || [ "$ZONE_ID" == "null" ] && echo "无法获取 Zone ID" && exit 1
echo "Zone ID: $ZONE_ID"
echo "正在查询现有DNS记录..."
record_json=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?name=$record_name&type=$RECORD_TYPE" \
-H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json")
RECORD_ID=$(echo "$record_json" | jq -r '.result[0].id')
if [ -z "$RECORD_ID" ] || [ "$RECORD_ID" == "null" ]; then
echo "记录不存在,创建中..."
response=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \
-H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json" \
--data "{\"type\":\"$RECORD_TYPE\",\"name\":\"$record_name\",\"content\":\"$CURRENT_IP\",\"ttl\":600,\"proxied\":false}")
else
echo "记录存在,Record ID: $RECORD_ID,更新中..."
response=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \
-H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json" \
--data "{\"type\":\"$RECORD_TYPE\",\"name\":\"$record_name\",\"content\":\"$CURRENT_IP\",\"ttl\":600,\"proxied\":false}")
fi
echo "$response" | grep -q '"success":true' && echo "更新成功: $record_name -> $CURRENT_IP" || {
echo "更新失败"
echo "$response"
exit 1
}
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END