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 。
最后核对好权限后再次确认,显示” *** 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 的返回结果有变化,额外增加判定