因爲經常用到這個功能,在這裏貼三則腳本備忘。

名稱: cloudflare-api-v4-ddns
平臺:主流 Linux , 支持 v4 & v6
鏈接: yulewang/cloudflare-api-v4-ddns
兼容: 不兼容openwrt

#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail

# Automatically update your CloudFlare DNS record to the IP, Dynamic DNS
# Can retrieve cloudflare Domain id and list zone's, because, lazy

# Place at:
# curl https://raw.githubusercontent.com/yulewang/cloudflare-api-v4-ddns/master/cf-v4-ddns.sh > /usr/local/bin/cf-ddns.sh && chmod +x /usr/local/bin/cf-ddns.sh
# run `crontab -e` and add next line:
# */5 * * * * /usr/local/bin/cf-ddns.sh >/dev/null 2>&1
# or you need log:
# */5 * * * * /usr/local/bin/cf-ddns.sh >> /var/log/cf-ddns.log 2>&1


# Usage:
# cf-ddns.sh -k cloudflare-api-key \
#            -u user@example.com \
#            -h host.example.com \     # fqdn of the record you want to update
#            -z example.com \          # will show you all zones if forgot, but you need this
#            -t A|AAAA                 # specify ipv4/ipv6, default: ipv4
#            -p true|false             # specify if proxied or not, default: true

# Optional flags:
#            -f false|true \           # force dns update, disregard local stored ip

# default config

# API key, see https://www.cloudflare.com/a/account/my-account,
# incorrect api-key results in E_UNAUTH error
CFKEY=

# Username, eg: user@example.com
CFUSER=

# Zone name, eg: example.com
CFZONE_NAME=

# Hostname to update, eg: homeserver.example.com
CFRECORD_NAME=

# Record type, A(IPv4)|AAAA(IPv6), default IPv4
CFRECORD_TYPE=A

# Record Proxied by CloudFlare
CFRECORD_PROXIED=true

# Cloudflare TTL for record, between 120 and 86400 seconds
CFTTL=120

# Ignore local file, update ip anyway
FORCE=false

WANIPSITE="http://ipv4.icanhazip.com"

# Site to retrieve WAN ip, other examples are: bot.whatismyipaddress.com, https://api.ipify.org/ ...
if [ "$CFRECORD_TYPE" = "A" ]; then
  :
elif [ "$CFRECORD_TYPE" = "AAAA" ]; then
  WANIPSITE="http://ipv6.icanhazip.com"
else
  echo "$CFRECORD_TYPE specified is invalid, CFRECORD_TYPE can only be A(for IPv4)|AAAA(for IPv6)"
  exit 2
fi

# get parameter
while getopts k:u:h:z:t:p:f: opts; do
  case ${opts} in
    k) CFKEY=${OPTARG} ;;
    u) CFUSER=${OPTARG} ;;
    h) CFRECORD_NAME=${OPTARG} ;;
    z) CFZONE_NAME=${OPTARG} ;;
    t) CFRECORD_TYPE=${OPTARG} ;;
    p) CFRECORD_PROXIED=${OPTARG} ;;
    f) FORCE=${OPTARG} ;;
  esac
done

# If required settings are missing just exit
if [ "$CFKEY" = "" ]; then
  echo "Missing api-key, get at: https://www.cloudflare.com/a/account/my-account"
  echo "and save in ${0} or using the -k flag"
  exit 2
fi
if [ "$CFUSER" = "" ]; then
  echo "Missing username, probably your email-address"
  echo "and save in ${0} or using the -u flag"
  exit 2
fi
if [ "$CFRECORD_NAME" = "" ]; then 
  echo "Missing hostname, what host do you want to update?"
  echo "save in ${0} or using the -h flag"
  exit 2
fi

# If the hostname is not a FQDN
if [ "$CFRECORD_NAME" != "$CFZONE_NAME" ] && ! [ -z "${CFRECORD_NAME##*$CFZONE_NAME}" ]; then
  CFRECORD_NAME="$CFRECORD_NAME.$CFZONE_NAME"
  echo " => Hostname is not a FQDN, assuming $CFRECORD_NAME"
fi

# Get current and old WAN ip
WAN_IP=`curl -s ${WANIPSITE}`
WAN_IP_FILE=$HOME/.cf-wan_ip_$CFRECORD_NAME.txt
if [ -f $WAN_IP_FILE ]; then
  OLD_WAN_IP=`cat $WAN_IP_FILE`
else
  echo "No file, need IP"
  OLD_WAN_IP=""
fi

# If WAN IP is unchanged an not -f flag, exit here
if [ "$WAN_IP" = "$OLD_WAN_IP" ] && [ "$FORCE" = false ]; then
  echo "WAN IP Unchanged, to update anyway use flag -f true"
  exit 0
fi

# Get zone_identifier & record_identifier
ID_FILE=$HOME/.cf-id_$CFRECORD_NAME.txt
if [ -f $ID_FILE ] && [ $(wc -l $ID_FILE | cut -d " " -f 1) == 4 ] \
  && [ "$(sed -n '3,1p' "$ID_FILE")" == "$CFZONE_NAME" ] \
  && [ "$(sed -n '4,1p' "$ID_FILE")" == "$CFRECORD_NAME" ]; then
    CFZONE_ID=$(sed -n '1,1p' "$ID_FILE")
    CFRECORD_ID=$(sed -n '2,1p' "$ID_FILE")
else
    echo "Updating zone_identifier & record_identifier"
    CFZONE_ID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$CFZONE_NAME" -H "X-Auth-Email: $CFUSER" -H "X-Auth-Key: $CFKEY" -H "Content-Type: application/json" | grep -Po '(?<="id":")[^"]*' | head -1 )
    CFRECORD_ID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$CFZONE_ID/dns_records?name=$CFRECORD_NAME" -H "X-Auth-Email: $CFUSER" -H "X-Auth-Key: $CFKEY" -H "Content-Type: application/json"  | grep -Po '(?<="id":")[^"]*' | head -1 )
    echo "$CFZONE_ID" > $ID_FILE
    echo "$CFRECORD_ID" >> $ID_FILE
    echo "$CFZONE_NAME" >> $ID_FILE
    echo "$CFRECORD_NAME" >> $ID_FILE
fi

# If WAN is changed, update cloudflare
echo "Updating DNS to $WAN_IP"

RESPONSE=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$CFZONE_ID/dns_records/$CFRECORD_ID" \
  -H "X-Auth-Email: $CFUSER" \
  -H "X-Auth-Key: $CFKEY" \
  -H "Content-Type: application/json" \
  --data "{\"id\":\"$CFZONE_ID\",\"type\":\"$CFRECORD_TYPE\",\"name\":\"$CFRECORD_NAME\",\"content\":\"$WAN_IP\", \"ttl\":$CFTTL, \"proxied\":$CFRECORD_PROXIED}")

if [ "$RESPONSE" != "${RESPONSE%success*}" ] && [ "$(echo $RESPONSE | grep "\"success\":true")" != "" ]; then
  echo "Updated succesfuly!"
  echo $WAN_IP > $WAN_IP_FILE
  exit
else
  echo 'Something went wrong :('
  echo "Response: $RESPONSE"
  exit 1
fi

Openwrt

名稱: update_cloudflare_com_v4.sh 平臺:Openwrt/LEDE
鏈接: Github
鏈接: Openwrt Wiki
兼容: 兼容openwrt , 可以在 opkg 中作爲軟件包下載到

警告:
免费的顶级域名不在cf的支持之列。也就是说,.cf, .ga, .gq, .ml, .tk 这些域名统统不支持。

如果你安装 ddns-script 在添加到供应商看到了 cloudflare 那就不需要本脚本了,如果没有看到那么接着往下看。 (可能因爲爲止原因沒有這個包

先把本脚本 update_cloudflare_com_v4.sh 上传只 openwrt 的 /root 目录下面。

再从 Cloudflare 获取 Global API Key 备用(记作为 API_KEY),预先添加一个 A 记录,IP 随意写比如 127.0.0.1, 添加后点击看下面的 API 能够从 APIs 链接获取到 Zone id(记作为 API_ZONE_ID),然后需要通过 curl 获取 dns records 列表接口查看刚才新添加的 A 记录的 DNS Record id(记作为 API_DNS_RECORD_ID)

在 openwrt 的 ddns 添加出供应商选择 Custom,更新脚本填写 /root/update_cloudflare_com_v4.sh,然后填入前面设置的域名和获取到的 API Key 以及 Cloudflare 用户名。这里需要注意的是二级域名要使用@来分隔,比如說 myhome@example.com

最后点击启用并應用即可。 (溫馨提示:如果在高級設置->ip來源裏面填的是url,然後有開代理,需要在代理裏面設置白名單。)

#!/bin/sh
#
#.Distributed under the terms of the GNU General Public License (GPL) version 2.0
#
# script for sending updates to cloudflare.com
#.based on Ben Kulbertis cloudflare-update-record.sh found at http://gist.github.com/benkulbertis
#.and on George Johnson's cf-ddns.sh found at https://github.com/gstuartj/cf-ddns.sh
#.2016-2018 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
# CloudFlare API documentation at https://api.cloudflare.com/
#
# This script is parsed by dynamic_dns_functions.sh inside send_update() function
#
# using following options from /etc/config/ddns
# option username  - your cloudflare e-mail
# option password  - cloudflare api key, you can get it from cloudflare.com/my-account/
# option domain    - "hostname@yourdomain.TLD"	# syntax changed to remove split_FQDN() function and tld_names.dat.gz
#
# The proxy status would not be changed by this script. Please change it in Cloudflare dashboard manually. 
#
# variable __IP already defined with the ip-address to use for update
#

# check parameters
[ -z "$CURL" ] && [ -z "$CURL_SSL" ] && write_log 14 "Cloudflare communication require cURL with SSL support. Please install"
[ -z "$username" ] && write_log 14 "Service section not configured correctly! Missing key as 'username'"
[ -z "$password" ] && write_log 14 "Service section not configured correctly! Missing secret as 'password'"
[ $use_https -eq 0 ] && use_https=1	# force HTTPS

# used variables
local __HOST __DOMAIN __TYPE __URLBASE __PRGBASE __RUNPROG __DATA __IPV6 __ZONEID __RECID __PROXIED
local __URLBASE="https://api.cloudflare.com/client/v4"
local __TTL=120

# split __HOST __DOMAIN from $domain
# given data:
# @example.com for "domain record"
# host.sub@example.com for a "host record"
__HOST=$(printf %s "$domain" | cut -d@ -f1)
__DOMAIN=$(printf %s "$domain" | cut -d@ -f2)

# Cloudflare v4 needs:
# __DOMAIN = the base domain i.e. example.com
# __HOST   = the FQDN of record to modify
# i.e. example.com for the "domain record" or host.sub.example.com for "host record"

# handling domain record then set __HOST = __DOMAIN
[ -z "$__HOST" ] && __HOST=$__DOMAIN
# handling host record then rebuild fqdn host@domain.tld => host.domain.tld
[ "$__HOST" != "$__DOMAIN" ] && __HOST="${__HOST}.${__DOMAIN}"

# set record type
[ $use_ipv6 -eq 0 ] && __TYPE="A" || __TYPE="AAAA"

# transfer function to use for godaddy
# all needed variables are set global here
# so we can use them directly
cloudflare_transfer() {
	local __CNT=0
	local __ERR
	while : ; do
		write_log 7 "#> $__RUNPROG"
		eval "$__RUNPROG"
		__ERR=$?			# save communication error
		[ $__ERR -eq 0 ] && break	# no error break while

		write_log 3 "cURL Error: '$__ERR'"
		write_log 7 "$(cat $ERRFILE)"		# report error

		[ $VERBOSE_MODE -gt 1 ] && {
			# VERBOSE_MODE > 1 then NO retry
			write_log 4 "Transfer failed - Verbose Mode: $VERBOSE_MODE - NO retry on error"
			break
		}

		__CNT=$(( $__CNT + 1 ))	# increment error counter
		# if error count > retry_count leave here
		[ $retry_count -gt 0 -a $__CNT -gt $retry_count ] && \
			write_log 14 "Transfer failed after $retry_count retries"

		write_log 4 "Transfer failed - retry $__CNT/$retry_count in $RETRY_SECONDS seconds"
		sleep $RETRY_SECONDS &
		PID_SLEEP=$!
		wait $PID_SLEEP	# enable trap-handler
		PID_SLEEP=0
	done

	# check for error
	grep -q '"success":\s*true' $DATFILE || {
		write_log 4 "CloudFlare reported an error:"
		write_log 7 "$(cat $DATFILE)"		# report error
		return 1	# HTTP-Fehler
	}
}

# Build base command to use
__PRGBASE="$CURL -RsS -o $DATFILE --stderr $ERRFILE"
# force network/interface-device to use for communication
if [ -n "$bind_network" ]; then
	local __DEVICE
	network_get_physdev __DEVICE $bind_network || \
		write_log 13 "Can not detect local device using 'network_get_physdev $bind_network' - Error: '$?'"
	write_log 7 "Force communication via device '$__DEVICE'"
	__PRGBASE="$__PRGBASE --interface $__DEVICE"
fi
# force ip version to use
if [ $force_ipversion -eq 1 ]; then
	[ $use_ipv6 -eq 0 ] && __PRGBASE="$__PRGBASE -4" || __PRGBASE="$__PRGBASE -6"	# force IPv4/IPv6
fi
# set certificate parameters
if [ "$cacert" = "IGNORE" ]; then	# idea from Ticket #15327 to ignore server cert
	__PRGBASE="$__PRGBASE --insecure"	# but not empty better to use "IGNORE"
elif [ -f "$cacert" ]; then
	__PRGBASE="$__PRGBASE --cacert $cacert"
elif [ -d "$cacert" ]; then
	__PRGBASE="$__PRGBASE --capath $cacert"
elif [ -n "$cacert" ]; then		# it's not a file and not a directory but given
	write_log 14 "No valid certificate(s) found at '$cacert' for HTTPS communication"
fi
# disable proxy if not set (there might be .wgetrc or .curlrc or wrong environment set)
# or check if libcurl compiled with proxy support
if [ -z "$proxy" ]; then
	__PRGBASE="$__PRGBASE --noproxy '*'"
elif [ -z "$CURL_PROXY" ]; then
	# if libcurl has no proxy support and proxy should be used then force ERROR
	write_log 13 "cURL: libcurl compiled without Proxy support"
fi
# set headers
__PRGBASE="$__PRGBASE --header 'X-Auth-Email: $username' "
__PRGBASE="$__PRGBASE --header 'X-Auth-Key: $password' "
__PRGBASE="$__PRGBASE --header 'Content-Type: application/json' "

# read zone id for registered domain.TLD
__RUNPROG="$__PRGBASE --request GET '$__URLBASE/zones?name=$__DOMAIN'"
cloudflare_transfer || return 1
# extract zone id
__ZONEID=$(grep -o '"id":\s*"[^"]*' $DATFILE | grep -o '[^"]*$' | head -1)
[ -z "$__ZONEID" ] && {
	write_log 4 "Could not detect 'zone id' for domain.tld: '$__DOMAIN'"
	return 127
}

# read record id for A or AAAA record of host.domain.TLD
__RUNPROG="$__PRGBASE --request GET '$__URLBASE/zones/$__ZONEID/dns_records?name=$__HOST&type=$__TYPE'"
cloudflare_transfer || return 1
# extract record id
__RECID=$(grep -o '"id":\s*"[^"]*' $DATFILE | grep -o '[^"]*$' | head -1)
[ -z "$__RECID" ] && {
	write_log 4 "Could not detect 'record id' for host.domain.tld: '$__HOST'"
	return 127
}

# extract current stored IP
__DATA=$(grep -o '"content":\s*"[^"]*' $DATFILE | grep -o '[^"]*$' | head -1)

# check data
[ $use_ipv6 -eq 0 ] \
	&& __DATA=$(printf "%s" "$__DATA" | grep -m 1 -o "$IPV4_REGEX") \
	|| __DATA=$(printf "%s" "$__DATA" | grep -m 1 -o "$IPV6_REGEX")

# we got data so verify
[ -n "$__DATA" ] && {
	# expand IPv6 for compare
	if [ $use_ipv6 -eq 1 ]; then
		expand_ipv6 $__IP __IPV6
		expand_ipv6 $__DATA __DATA
		[ "$__DATA" = "$__IPV6" ] && {		# IPv6 no update needed
			write_log 7 "IPv6 at CloudFlare.com already up to date"
			return 0
		}
	else
		[ "$__DATA" = "$__IP" ] && {		# IPv4 no update needed
			write_log 7 "IPv4 at CloudFlare.com already up to date"
			return 0
		}
	fi
}

# update is needed
# let's build data to send
# set proxied parameter
__PROXIED=$(grep -o '"proxied":\s*[^",]*' $DATFILE | grep -o '[^:]*$')

# use file to work around " needed for json
cat > $DATFILE << EOF
{"id":"$__ZONEID","type":"$__TYPE","name":"$__HOST","content":"$__IP","ttl":$__TTL,"proxied":$__PROXIED}
EOF

# let's complete transfer command
__RUNPROG="$__PRGBASE --request PUT --data @$DATFILE '$__URLBASE/zones/$__ZONEID/dns_records/$__RECID'"
cloudflare_transfer || return 1

return 0

名稱: cloudflare-api-v4-ddns
平臺:Openwrt/LEDE
鏈接: gist.github.com/icyleaf/8fc867003cb3c868bfae855e722ce392
兼容: 兼容openwrt , 但是 不推薦使用

警告:
免费的顶级域名不在cf的支持之列。也就是说,.cf, .ga, .gq, .ml, .tk 这些域名统统不支持。

如果你安装 ddns-script 在添加到供应商看到了 cloudflare 那就不需要本脚本了,如果没有看到那么接着往下看。

先把本脚本 cloudflare_ddns_update.sh 上传只 openwrt 的 /root 目录下面。

再从 Cloudflare 获取 Global API Key 备用(记作为 API_KEY),预先添加一个 A 记录,IP 随意写比如 127.0.0.1, 添加后点击看下面的 API 能够从 APIs 链接获取到 Zone id(记作为 API_ZONE_ID),然后需要通过 curl 获取 dns records 列表接口查看刚才新添加的 A 记录的 DNS Record id(记作为 API_DNS_RECORD_ID)

在 openwrt 的 ddns 添加出供应商选择 Custom,更新脚本填写 /root/cloudflare_ddns_update.sh, 域名填写你的域名,用户名填写你的注册邮箱,密码填写 API_KEY,由于下面额外的参数没有搞懂怎么用, API_ZONE_ID 和 API_DNS_RECORD_ID 获取的值需要填写在脚本对应的变量后面。填写完成后点击保存并应用。

最后点击启用并启动即可。

#!/bin/sh

# 参考资料:
# https://github.com/openwrt/packages/blob/master/net/ddns-scripts/samples/update_sample.sh
# https://github.com/anjianshi/cloudxns-ddns/blob/master/cloudxns.sh
#
# Requirements:
# curl

[ -z "$domain" ]   && write_log 14 "Service section not configured correctly! Missing 'domain'"
[ -z "$username" ] && write_log 14 "Service section not configured correctly! Missing 'username'"
[ -z "$password" ] && write_log 14 "Service section not configured correctly! Missing 'password'"

if test ! $(which curl); then
    write_log 14 "Service missing curl dependency"
fi

API_URL="https://api.cloudflare.com/client/v4/"
API_DOMAIN=$domain
API_EMAIL=$username
API_KEY=$password
API_ZONE_ID="{zone_id}"
API_DNS_RECORD_ID="{dns_record_id}"

RESULT=$(curl -X PUT "${API_URL}/zones/${API_ZONE_ID}/dns_records/${API_DNS_RECORD_ID}" \
              -H "Content-Type: application/json" \
              -H "X-Auth-Email: ${API_EMAIL}" \
              -H "X-Auth-Key: ${API_KEY}" \
              --data '{"type":"A","name":"'${API_DOMAIN}'","content":"'${__IP}'","ttl":120,"proxied":false}')

write_log 7 "answered:\n$RESULT"

if [ $(echo -n "$RESULT"|grep -o "\"success\":true"|wc -l) = 1 ];then
    return 0
else
    return 1
fi