1. 首页
  2. 技术
  3. Linux

cloudflare最新ddns脚本 2020.5.2号 解决老版本脚本无法更新ip

引用自:https://blog.starryvoid.com/archives/313.html

1、前言

服务器 IP 总是变,没事就会变个新的,这时候就需要一个 Dynamic Domain Name Server 来保证实时的 DNS 更换。
当然首先这个需要你的 DNS 解析商做配合,本文则采用 Cloudflare+DDNS+Shell


2、准备

准备工具

Cloudflare 的 Global API

Cloudflare 解析的域名一个

前提要素

Curl Wget 已安装


3、DDNS 获取新 IP 地址 Shell 脚本

下载地址:[ 链接 ]

#!/usr/bin/env bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
#
# Dynamic Domain Name Server (Cloudflare API)
#
# Author: StarryVoid <stars@starryvoid.com>
# Intro:  https://blog.starryvoid.com/archives/313.html
#

# Select API(1) Or Token(2)
SelectAT="1"

# CloudFlare API " X-Auth-Email: *** " " X-Auth-Key: *** "
XAUTHEMAIL="YOUREMAILADDRESS"
XAUTHKEY="YOURCLOUDFLAREAPIKEY"

# CloudFlare Token " Authorization: Bearer *** "
AuthorizationToken="YOURTOKEN"

# Domain Name " example.com " " ddns.example.com "
ZONENAME="DOMAIN"
DOMAINNAME="DOMAINNAME"
DOMAINTTL="1"

# Output
OUTPUTINFO="$(pwd)/ddns_output.info"
OUTPUTLOG="$(pwd)/ddns_shell.log"

# Time
DATETIME=$(date +%Y-%m-%d_%H:%M:%S)

# ------------ Start ------------

check_environment () {
    if ! [ -x "$(command -v curl)" ]; then echo "Command not found \"curl\"" >> "${OUTPUTLOG}" ; exit 1 ; fi
    if ! [ -x "$(command -v wget)" ]; then echo "Command not found \"wget\"" >> "${OUTPUTLOG}" ; exit 1 ; fi
}

check_selectAT () {
    if [[ ! "${SelectAT}" = 1 && ! "${SelectAT}" = 2 ]]; then echo "Failed to Select API(1) Or Token(2)" >> "${OUTPUTLOG}"; exit 1; fi
}

get_zone_records() {
    if [ "${SelectAT}" = 1 ]; then ZONERECORDSLOG=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=${ZONENAME}" -H "X-Auth-Email: ${XAUTHEMAIL}" -H "X-Auth-Key: ${XAUTHKEY}" -H "Content-Type: application/json" --connect-timeout 30 -m 10 ); fi
    if [ "${SelectAT}" = 2 ]; then ZONERECORDSLOG=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=${ZONENAME}" -H "Authorization: Bearer ${AuthorizationToken}" -H "Content-Type: application/json" --connect-timeout 30 -m 10 ); fi
    ZONERECORDS=$(echo "${ZONERECORDSLOG}" | awk BEGIN{RS=EOF}'{gsub(/\n/," ");print}' | sed 's/ //g' | grep -Po '(?<="id":")[^"]*' | head -1 )
    if [ ! "${ZONERECORDS}" ]; then echo "Failed to get zone_records from cloudflare." >> "${OUTPUTLOG}" ; echo "---log---" >> "${OUTPUTLOG}" ; echo "${ZONERECORDSLOG}" >> "${OUTPUTLOG}" ; echo "---log---" >> "${OUTPUTLOG}" ; exit 1; fi
}

get_dns_records() {
    if [ "${SelectAT}" = 1 ]; then DNSRECORDSLOG=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${ZONERECORDS}/dns_records?type=A&name=${DOMAINNAME}" -H "X-Auth-Email: ${XAUTHEMAIL}" -H "X-Auth-Key: ${XAUTHKEY}" -H "Content-Type: application/json" --connect-timeout 30 -m 10 ); fi
    if [ "${SelectAT}" = 2 ]; then DNSRECORDSLOG=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${ZONERECORDS}/dns_records?type=A&name=${DOMAINNAME}" -H "Authorization: Bearer ${AuthorizationToken}" -H "Content-Type: application/json" --connect-timeout 30 -m 10 ); fi
    DNSRECORDS=$(echo "${DNSRECORDSLOG}" | awk BEGIN{RS=EOF}'{gsub(/\n/," ");print}' | sed 's/ //g' | grep -Po '(?<="id":")[^"]*' | head -1 )
    if [ ! "${DNSRECORDS}" ]; then echo "Failed to get dns_records from cloudflare." >> "${OUTPUTLOG}"; echo "---log---" >> "${OUTPUTLOG}" ; echo "${DNSRECORDSLOG}" >> "${OUTPUTLOG}" ; echo "---log---" >> "${OUTPUTLOG}" ; exit 1; fi
}
    
get_domain_ip() {
    if [ "${SelectAT}" = 1 ]; then DOMAINIPADDLOG=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${ZONERECORDS}/dns_records/${DNSRECORDS}" -H "X-Auth-Email: ${XAUTHEMAIL}" -H "X-Auth-Key: ${XAUTHKEY}" -H "Content-Type: application/json" --connect-timeout 30 -m 10 ); fi
    if [ "${SelectAT}" = 2 ]; then DOMAINIPADDLOG=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${ZONERECORDS}/dns_records/${DNSRECORDS}" -H "Authorization: Bearer ${AuthorizationToken}" -H "Content-Type: application/json" --connect-timeout 30 -m 10 ); fi
    DOMAINIPADD=$(echo "${DOMAINIPADDLOG}" | awk BEGIN{RS=EOF}'{gsub(/\n/," ");print}' | sed 's/ //g' | grep -Po '(?<="content":")[^"]*' | head -1 )
    if [ ! "${DOMAINIPADD}" ]; then echo "Failed to get DNS resolution address from cloudflare." >> "${OUTPUTLOG}"; echo "---log---" >> "${OUTPUTLOG}" ; echo "${DOMAINIPADDLOG}" >> "${OUTPUTLOG}" ; echo "---log---" >> "${OUTPUTLOG}" ; exit 1; fi
}

update_new_ipaddress() {
    if [ "${SelectAT}" = 1 ]; then curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/${ZONERECORDS}/dns_records/${DNSRECORDS}" -H "X-Auth-Email: ${XAUTHEMAIL}" -H "X-Auth-Key: ${XAUTHKEY}" -H "Content-Type: application/json"  --data "{\"type\":\"A\",\"name\":\"${DOMAINNAME}\",\"content\":\"${NEWIPADD}\",\"ttl\":"${DOMAINTTL}",\"proxied\":false}" --connect-timeout 30 -m 10 > /dev/null 2>&1 && UPDATENEWIPADDRESS=1 || UPDATENEWIPADDRESS=0; fi
    if [ "${SelectAT}" = 2 ]; then curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/${ZONERECORDS}/dns_records/${DNSRECORDS}" -H "Authorization: Bearer ${AuthorizationToken}" -H "Content-Type: application/json"  --data "{\"type\":\"A\",\"name\":\"${DOMAINNAME}\",\"content\":\"${NEWIPADD}\",\"ttl\":"${DOMAINTTL}",\"proxied\":false}" --connect-timeout 30 -m 10 > /dev/null 2>&1 && UPDATENEWIPADDRESS=1 || UPDATENEWIPADDRESS=0; fi
    if [ "${UPDATENEWIPADDRESS}" = 1 ] ; then echo "Successfully updated domain name resolution address." >> "${OUTPUTLOG}" ; fi
    if [ "${UPDATENEWIPADDRESS}" = 0 ] ; then echo "Failed to updated domain name resolution address." >> "${OUTPUTLOG}" ; exit 1 ; fi
}

get_server_new_ip() {
    [ -z "${NEWIPADD}" ] && NEWIPADD=$( wget -qO- -t1 -T2 https://ipv4.icanhazip.com )
    [ -z "${NEWIPADD}" ] && NEWIPADD=$( wget -qO- -t1 -T2 https://api.ipify.org )
    [ -z "${NEWIPADD}" ] && NEWIPADD=$( wget -qO- -t1 -T2 ipv4.icanhazip.com )
    [ -z "${NEWIPADD}" ] && NEWIPADD=$( wget -qO- -t1 -T2 api.ipify.org )
    [ -z "${NEWIPADD}" ] && NEWIPADD=$( wget -qO- -t1 -T2 ipinfo.io/ip )
    if [[ ! "${NEWIPADD}" ]]; then echo "Failed to get server public network address from internet." >> "${OUTPUTLOG}"; exit 1; fi
}

check_ddns_info_file() {
    if [ -f "${OUTPUTINFO}" ]; then
        CHECKDDNSINFOFILE=1
        ZONERECORDS=$(cat < "${OUTPUTINFO}" | grep "ZONERECORDS=" | awk -F "=" '{print $2}' | sed 's/\"//g' | sed "s/\'//g" )
        DNSRECORDS=$(cat < "${OUTPUTINFO}" | grep "DNSRECORDS=" | awk -F "=" '{print $2}' | sed 's/\"//g' | sed "s/\'//g" )
        DOMAINIPADD=$(cat < "${OUTPUTINFO}" | grep "DOMAINIPADD=" | awk -F "=" '{print $2}' | sed 's/\"//g' | sed "s/\'//g" )
        if [[ ! "${ZONERECORDS}" ]]; then CHECKDDNSINFOFILE=0 ; fi
        if [[ ! "${DNSRECORDS}" ]]; then CHECKDDNSINFOFILE=0 ; fi
        if [[ ! "${DOMAINIPADD}" ]]; then CHECKDDNSINFOFILE=0 ; fi
    else 
        CHECKDDNSINFOFILE=0
    fi
    if [ "${CHECKDDNSINFOFILE}" = 0 ] ; then echo "Failed to check DDNS information file." >> "${OUTPUTLOG}" ; rm -f "${OUTPUTINFO}" ; exit 1 ; fi
}

make_ddns_info_file() {
    if [ -f "${OUTPUTINFO}" ] ; then rm -f "${OUTPUTINFO}" ; fi
    touch "${OUTPUTINFO}"
    echo Update Time is "${DATETIME}" >> "${OUTPUTINFO}"
    get_zone_records
    get_dns_records
    get_domain_ip
    echo -e "ZONERECORDS=\"${ZONERECORDS}\"\nDNSRECORDS=\"${DNSRECORDS}\"\nDOMAINIPADD=\"${DOMAINIPADD}\"" >> "${OUTPUTINFO}"
    echo "Successfully generated DDNS information." >> "${OUTPUTLOG}"
}

main() {
    check_environment
    check_selectAT
    echo "Running Time is ${DATETIME}" >> "${OUTPUTLOG}"
    get_server_new_ip
    if [ -f "${OUTPUTINFO}" ]; then
        check_ddns_info_file
    else
        make_ddns_info_file
    fi
    if [[ "${NEWIPADD}" == "${DOMAINIPADD}" ]]; then 
        echo "IP address has not changed." >> "${OUTPUTLOG}"
        exit 0
    else 
        update_new_ipaddress
        sleep 10s
        make_ddns_info_file
        if [[ "${NEWIPADD}" == "${DOMAINIPADD}" ]]; then 
            echo "IP address has been modified to \"${NEWIPADD}\"." >> "${OUTPUTLOG}"
            exit 0
        else
            echo "IP address modification failed." >> "${OUTPUTLOG}"
            exit 1
        fi
    fi
}

# ------------ End ------------

main


4、讲解

首先本文制作过程中参考过秋水逸冰的脚本,在此表示感谢


4.1、脚本配置 Global API 版

我们需要将所需的内容(Cloudflare API  和 DDNS 域名)填入对应位置

# Select API(1) Or Token(2)
SelectAT="1"
# CloudFlare API " X-Auth-Email: *** " " X-Auth-Key: *** "
XAUTHEMAIL="mail@example.com" #你的 Cloudflare 邮箱用户名
XAUTHKEY="123123123123" #你的 Cloudflare Global API Key
# Domain Name " example.com " " ddns.example.com "
ZONENAME="example.com" #你的二级域名
DOMAINNAME="ddns.example.com" #你的 DDNS 域名
DOMAINTTL="1" #你的域名 TTL 时间,默认 1 为 auto

4.2、脚本配置 Token 版

我们需要将所需的内容(Cloudflare Token 和 DDNS 域名)填入对应位置

# Select API(1) Or Token(2)
SelectAT="2"
# CloudFlare Token " Authorization: Bearer *** "
AuthorizationToken="YOURTOKEN"
# Domain Name " example.com " " ddns.example.com "
ZONENAME="example.com" #你的二级域名
DOMAINNAME="ddns.example.com" #你的 DDNS 域名
DOMAINTTL="1" #你的域名 TTL 时间,默认 1 为 auto

4.3、定时运行

如果需要定时运行,可以编辑/etc/crontab  实现定期运行,下例为 5min 运行一次
先 cd  进入目录是为了隔离不同 DDNS 脚本生成的数据和日志文件。否则默认放/root 目录下。

# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed

*/5 * * * * root cd /autoshell && bash cloudflare-ddns.sh

4.4、脚本输出

默认会在用户所在目录下生成两个文件 ddnsrun.data  和 ddnsrun.log ,前者是储存获取的 API 信息,后者是储存运行日志。

[root@linux auto]# cat ddnsrun.data
Update Time 2018-12-02_11:43:01
ZONERECORDS=*
DNSRECORDS=*
OldIPAddress=*.*.*.*

[root@linux auto]# cat ddnsrun.log
Running Time is 2018-12-02_11:43:01
IP address has been changed to *.*.*.*
Running Time is 2018-12-02_11:44:01
The IP address is the same
Running Time is 2018-12-02_11:45:01
The IP address is the same
Running Time is 2018-12-02_11:46:01
The IP address is the same

如果出现问题,可以在日志中查看问题原因。


4.5、获取 Cloudflare 的 API Tokens

首先进入 Cloudflare 的个人配置页面 [链接]

找到下面的 API Tokens  (Manage access and permissions for your accounts, sites, and products.)

然后点击右侧的 Create Token 创建新的 Token ( 相对 Global API 约束访问权限 )

新的 Token 需要配置权限,本次 DDNS 需要的权限分别为 Account.Dns Firewall.Read 和 Zone.Zone.Read 和 Zone.DNS.Edit 三条。配置好后确认

*** 提醒:2020/03/05 发现 Cloudflare 如果 Token 配置了 Zone Resources 限制区域,会导致无法获取 Zone Record 。
*** 现象:执行命令返回 “message”:”Actor ‘com.cloudflare.api.token.***’ requires permission ‘com.cloudflare.api.account.zone.list’ to list zones”
*** 暂时解决方式:配置为 All zones 。

Cloudflare_API_Token-300x234-1

最后核对好权限后再次确认,显示” *** API token was successfully updated “,此时你可以查看你的 Token ,并点击 Copy 即可复制。


4.6、获取 Cloudflare 的 Global API

首先进入 Cloudflare 的个人配置页面 [链接]

找到下面的 API Keys  (Keys used to access Cloudflare APIs.)

然后在 Global API Key 一行点击右侧的 View  查看你的 Global API Key

最后额外注意,Global API 需要搭配你的邮箱账户名才可以使用


5、后期修订

2019/11/05、经由 Sion 提醒,换行符在发表文章时丢失,现已提供下载地址。 同时支持 Token 方式管理。

2020/03/05、发现 Cloudflare 关于 Token 配置疑似 Bug ,已增加额外提醒。

2020/04/26、发现 Cloudflare 关于 List DNS Records 的返回结果有变化,额外增加判定

原创文章,作者:admin,如若转载,请注明出处:https://www.huiyingwu.com/3693/

发表评论

电子邮件地址不会被公开。 必填项已用*标注