需求
- 尽量不影响主路由(折腾全在旁路由)
- 从主路由中获取外网 IP 地址(不使用网站检测,因为我知道 IPV4/IPV6 是公网,而且也不想频繁访问网络)
- 更新成功或脚本运行错误时,能收到提示
脚本
基于上面的说明,直接贴出脚本代码(使用 CloudFlare API),通过 SSH 授权密钥访问主路由获取 IPV4/IPV6 后与本地文件对比 IP 变化判断是否有更新,自用,有需要大佬可以参考修改(此脚本在 OpenWrt 中验证通过):
#!/bin/sh
set -e
IP_FILE="/root/auto_ddns/ip.txt"
ID_FILE="/root/auto_ddns/cloudflare_id.txt"
LOG_FILE="/root/auto_ddns/ddns.log"
check_command() {
if ! command -v "$1" &> /dev/null; then
echo "Error: \"$1\" command not found"
exit 1
fi
}
check_command "curl"
CURL_BIN="/usr/bin/curl"
handle_error() {
exec >/dev/tty 2>&1
# echo "$(<&2)" >> "$LOG_FILE"
$CURL_BIN \
-T "$LOG_FILE" \
-H "Authorization: Bearer tk_zGKbk7wDA************" \ # 认证
-H "Title: NAS DDNS 更新脚本执行错误" \
-H "X-Priority: 4" \
-H "Tags: globe_with_meridians" \
-H "Filename: ddns.log" \
https://ntfy.example.com/Task # ntfy 消息服务地址
}
trap 'handle_error' ERR
echo "" > $LOG_FILE
exec > >(tee -a "$LOG_FILE") 2>&1
log() {
if [ "$1" ]; then
echo -e "[$(date +'%Y-%m-%d %H:%M:%S')] - $1" >> $LOG_FILE
fi
}
# cloudflare api
AUTH_EMAIL="your@email.com" # cloudflare 注册邮箱地址
AUTH_KEY="fi8uiKCB*************************" # found in cloudflare account settings | Zone API TOKEN
ZONE_NAME="example.com" # 域名
RECORD_NAME="ddns.example.com" # 需要更新的 DDNS 记录名
TTL=120 # 设 1 为自动
ROUTER_IP="192.168.12.1" # 主路由地址
ROUTER_PORT="22" # 主路由 SSH 端口号
log "开始执行!"
# 获取路由器 ip 地址
IPV4=$(ssh -i '/root/auto_ddns/id_ed25519' admin@${ROUTER_IP} -p ${ROUTER_PORT} ifconfig ppp0 | grep 'inet addr' | awk '{print $2}' | awk -F ':' '{print $2}')
# IPV6 地址由于都是公网的,获取 OpenWrt 自身即可
IPV6=$(ifconfig br-lan | grep inet6 | grep -v fe80:: | awk '{print $3}' | awk -F '/' '{print $1}')
# 如果有多组 IPV6 则取最后一组
IPV6=$(echo $IPV6 | awk '{print $NF}')
if [ -f $ID_FILE ] && [ $(wc -l $ID_FILE | cut -d " " -f 1) == 3 ]; then
log "发现 ID 文件,将读取内容"
zone_id=$(sed -n '1p' $ID_FILE)
v4_record_id=$(sed -n '2p' $ID_FILE)
v6_record_id=$(sed -n '3p' $ID_FILE)
else
log "未存在 ID 文件,将创建并写入内容"
# get zone id
zone_id=$($CURL_BIN -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$ZONE_NAME" -H "Authorization: Bearer $AUTH_KEY" -H "Content-Type: application/json" | sed -E "s/.+\"result\":\[\{\"id\":\"([a-f0-9]+)\".+/\1/g" )
# get record response
record_response_json=$($CURL_BIN -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records?name=$RECORD_NAME" -H "Authorization: Bearer $AUTH_KEY" -H "Content-Type: application/json")
# get both v4 and v6 record ip
v4_record_ip=$(echo $record_response_json | sed -E "s/.+\"content\":\"([a-f0-9.]+)\".+\"proxiable\".+/\1/g")
v6_record_ip=$(echo $record_response_json | sed -E "s/.+\"content\":\"([a-f0-9:]+)\".+\"proxiable\".+/\1/g")
# get both v4 and v6 record id
v4_record_id=$(echo $record_response_json | sed -E "s/.+\{\"id\":\"([a-f0-9]+)\".+\"type\":\"A\".+/\1/g")
v6_record_id=$(echo $record_response_json | sed -E "s/.+\{\"id\":\"([a-f0-9]+)\".+\"type\":\"AAAA\".+/\1/g")
# write to file
echo "$zone_id" > $ID_FILE
echo "$v4_record_id" >> $ID_FILE
echo "$v6_record_id" >> $ID_FILE
fi
if [ ! -f $IP_FILE ] || [ $(wc -l $IP_FILE | cut -d " " -f 1) != 2 ]; then
log "未存在 IP 文件,将创建并写入值"
if [ -z "$v4_record_ip" ] || [ -z "$v6_record_ip" ]; then
log "ip 记录值为空(将从 CloudFlare 中获取)"
# get record response
record_response_json=$($CURL_BIN -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records?name=$RECORD_NAME" -H "Authorization: Bearer $AUTH_KEY" -H "Content-Type: application/json")
v4_record_ip=$(echo $record_response_json | sed -E "s/.+\"content\":\"([a-f0-9.]+)\".+\"proxiable\".+/\1/g")
v6_record_ip=$(echo $record_response_json | sed -E "s/.+\"content\":\"([a-f0-9:]+)\".+\"proxiable\".+/\1/g")
fi
echo "ip 记录值将写入 IP 文件中"
echo "$v4_record_ip" > $IP_FILE
echo "$v6_record_ip" >> $IP_FILE
fi
OLD_IPV4=$(sed -n '1p' $IP_FILE)
OLD_IPV6=$(sed -n '2p' $IP_FILE)
if [ $OLD_IPV4 == $IPV4 ]; then
log "IPV4 未发生变化"
else
update_v4=$($CURL_BIN -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$v4_record_id" -H "Authorization: Bearer $AUTH_KEY" -H "Content-Type: application/json" --data "{\"id\":\"$zone_id\",\"type\":\"A\",\"name\":\"$RECORD_NAME\",\"content\":\"$IPV4\",\"ttl\":$TTL}")
success=$( echo $update_v4 | sed -E "s/.+\"success\":[ ]*([a-z]+).+/\1/g")
if [ -n $update ] && [ $success == "true" ]; then
echo "$IPV4" > $IP_FILE
log "IPV4 从 ${OLD_IPV4} -> ${IPV4} 更新成功"
message="IPV4 更新成功:\n旧:${OLD_IPV4}\n新:${IPV4}\n"
else
log "IPV4 API 更新失败,详细信息:\n$update_v4"
message="IPV4 API 更新失败,详细信息:\n$update_v4\n"
fi
fi
if [ $OLD_IPV6 == $IPV6 ]; then
log "IPV6 未发生变化"
else
update_v6=$($CURL_BIN -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$v6_record_id" -H "Authorization: Bearer $AUTH_KEY" -H "Content-Type: application/json" --data "{\"id\":\"$zone_id\",\"type\":\"AAAA\",\"name\":\"$RECORD_NAME\",\"content\":\"$IPV6\",\"ttl\":$TTL}")
success=$( echo $update_v6 | sed -E "s/.+\"success\":[ ]*([a-z]+).+/\1/g")
if [ -n $update ] && [ $success == "true" ]; then
echo "$IPV6" >> $IP_FILE
log "IPV6 从 ${OLD_IPV6} -> ${IPV6} 更新成功"
message="${message}IPV6 更新成功:\n旧:${OLD_IPV6}\n新:${IPV6}\n"
else
log "IPV6 API 更新失败,详细信息:\n$update_v6"
message="${message}IPV6 API 更新失败,详细信息:\n$update_v6\n"
fi
fi
if [ ! -z "$message" ]; then
$CURL_BIN \
-H "Authorization: Bearer tk_zGKbk7wDA************" \ # 认证
-H "Title: NAS DDNS 更新" \
-H "Tags: globe_with_meridians" \
-d "$(echo -e $message)" \
https://ntfy.example.com/Task # ntfy 消息服务地址
fi
log "执行完毕!"
保存后,赋予该脚本可执行权限:
chmod +x /root/auto_ddns/ddns.sh
然后 crontab -e
打开定时任务,每隔 5 分钟运行一次:
*/5 * * * * /root/auto_ddns/ddns.sh
感谢
参考了下面的脚本,感谢!
https://gist.github.com/0neday/04141ba4d3ac3ccf77a5b5837b104762
Comments NOTHING