Cloudflare+DDNS+Shell

懒的换 IP 地址了,于是研究了一下 Cloudflare 的 API,用着还可以,做成小脚本。


1、前言

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


2、准备

准备工具

Cloudflare 的 Global API

Cloudflare 解析的域名一个

前提要素

Curl Wget 已安装


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

下载地址:[ 链接 ]

#!/usr/bin/bash env
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
#
# Dynamic Domain Name Server (Cloudflare API)
#
# Author: StarryVoid <[email protected]>
# 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"

# 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\"" ;fi
    if ! [ -x "$(command -v wget)" ]; then echo "Command not found \"wget\"" ;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 ZONERECORDS=$(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 | grep -Po '(?<="id":")[^"]*' | head -1 ); fi
    if [ "${SelectAT}" = 2 ]; then ZONERECORDS=$(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 | grep -Po '(?<="id":")[^"]*' | head -1 ); fi
    if [ ! "${ZONERECORDS}" ]; then echo "Failed to get zone_records from cloudflare." >> "${OUTPUTLOG}" ; exit 1; fi
}

get_dns_records() {
    if [ "${SelectAT}" = 1 ]; then DNSRECORDS=$(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 | grep -Po '(?<="id":")[^"]*' | head -1 ); fi
    if [ "${SelectAT}" = 2 ]; then DNSRECORDS=$(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 | grep -Po '(?<="id":")[^"]*' | head -1 ); fi
    if [ ! "${DNSRECORDS}" ]; then echo "Failed to get dns_records from cloudflare." >> "${OUTPUTLOG}"; exit 1; fi
}
    
get_domain_ip() {
    if [ "${SelectAT}" = 1 ]; then DOMAINIPADD=$(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 | grep -Po '(?<="content":")[^"]*' ); fi
    if [ "${SelectAT}" = 2 ]; then DOMAINIPADD=$(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 | grep -Po '(?<="content":")[^"]*' ); fi
    if [ ! "${DOMAINIPADD}" ]; then echo "Failed to get DNS resolution address from cloudflare." >> "${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\":1,\"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\":1,\"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=" | cut -c 13-)
        DNSRECORDS=$(cat < "${OUTPUTINFO}" | grep "DNSRECORDS=" | cut -c 12-)
        DOMAINIPADD=$(cat < "${OUTPUTINFO}" | grep "DOMAINIPADD=" | cut -c 13-) if [[ ! "${ZONERECORDS}" ]]; then echo "Failed to get zone_records information from local file." >> "${OUTPUTLOG}" ; CHECKDDNSINFOFILE=0 ; fi
        if [[ ! "${DNSRECORDS}" ]]; then echo "Failed to get dns_records information from local file." >> "${OUTPUTLOG}"; CHECKDDNSINFOFILE=0 ; fi
        if [[ ! "${DOMAINIPADD}" ]]; then echo "Failed to get domain name resolution address information from local file." >> "${OUTPUTLOG}"; CHECKDDNSINFOFILE=0 ; fi
    else 
        echo "Failed to find DDNS information file." >> "${OUTPUTLOG}" && 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="[email protected]" #你的 Cloudflare 邮箱用户名
XAUTHKEY="123123123123" #你的 Cloudflare Global API Key
# Domain Name " example.com " " ddns.example.com "
ZONENAME="example.com" #你的二级域名
DOMAINNAME="ddns.example.com" #你的 DDNS 域名

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 域名

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 信息,后者是储存运行日志。

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

[[email protected] 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 三条。配置好后确认

《Cloudflare+DDNS+Shell》

最后核对好权限后再次确认,显示” *** 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 方式管理。

点赞