Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Automatically update your CloudFlare DNS record to the IP, Dynamic DNS for Cloudflare
#!/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:
# /usr/local/bin/cf-ddns.sh
# run `crontab -e` and add next line:
# 0 * * * * /usr/local/bin/cf-ddns.sh >/dev/null 2>&1
# if you're lazy (like me) copy/paste the command BETWEEN the EOT
: <<'EOT'
curl https://gist.githubusercontent.com/larrybolt/6295160/raw > /usr/local/bin/cf-ddns.sh && chmod +x /usr/local/bin/cf-ddns.sh
(crontab -l 2>/dev/null; echo "0 * * * * /usr/local/bin/cf-ddns.sh >/dev/null 2>&1") | crontab -
$EDITOR /usr/local/bin/cf-ddns.sh
/usr/local/bin/cf-ddns.sh
EOT
# run /usr/local/bin/cf-ddns.sh in terminal to check all settings are valid
# 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
# Optional flags:
# -i cloudflare-record-id \ # script will show this
# -a true|false \ # auto get zone list and record id
# -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=
# Zone name, will list all possible if missing, eg: example.com
CFZONE=
# Domain id, will retrieve itself by default
CFID=
# Username, eg: user@example.com
CFUSER=
# Hostname to update, eg: homeserver.example.com
CFHOST=
# Cloudflare TTL for record, between 120 and 86400 seconds
CFTTL=3600
# Get domain ID from Cloudflare using awk/sed and python json.tool
GETID=true
# Ignore local file, update ip anyway
FORCE=false
# Site to retrieve WAN ip, other examples are: bot.whatismyipaddress.com, https://api.ipify.org/ ...
WANIPSITE="http://icanhazip.com"
# get parameter
while getopts a:k:i:u:h:z:f: opts; do
case ${opts} in
a) GETID=${OPTARG} ;;
k) CFKEY=${OPTARG} ;;
i) CFID=${OPTARG} ;;
u) CFUSER=${OPTARG} ;;
h) CFHOST=${OPTARG} ;;
z) CFZONE=${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 [ "$CFHOST" = "" ]; 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 [ "$CFHOST" != "$CFZONE" ] && ! [ -z "${CFHOST##*$CFZONE}" ]; then
CFHOST="$CFHOST.$CFZONE"
echo " => Hostname is not a FQDN, assuming $CFHOST"
fi
# If CFZONE is missing, retrieve them all from CF
if [ "$CFZONE" = "" ]; then
echo "Missing zone"
if ! [ "$GETID" == true ]; then exit 2; fi
echo "listing all zones: (if api-key is valid)"
curl -s https://www.cloudflare.com/api_json.html \
-d a=zone_load_multi \
-d tkn=$CFKEY \
-d email=$CFUSER \
| grep -Eo '"zone_name":"([^"]+)"' \
| cut -d':' -f2 \
| awk '{gsub("\"","");print "* "$1}'
echo "Please specify the matching zone in ${0} or specify using the -z flag"
exit 2
fi
# Get current and old WAN ip
WAN_IP=`curl -s ${WANIPSITE}`
if [ -f $HOME/.wan_ip-cf.txt ]; then
OLD_WAN_IP=`cat $HOME/.wan_ip-cf.txt`
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
# If CFID is missing retrieve and use it
if [ "$CFID" = "" ]; then
echo "Missing DNS record ID"
if ! [ "$GETID" == true ]; then exit 2; fi
echo "fetching from Cloudflare..."
if ! CFID=$(
curl -s https://www.cloudflare.com/api_json.html \
-d a=rec_load_all \
-d tkn=$CFKEY \
-d email=$CFUSER \
-d z=$CFZONE \
| grep -Eo '"(rec_id|name|type)":"([^"]+)"' \
| cut -d':' -f2 \
| awk 'NR%3{gsub("\"","");printf $0" ";next;}1' \
| grep -E "${CFHOST//./\\.}" \
| grep -e '"A"' \
| grep -Eo "(^|\s)(\d+)(\s|$)"
); then
echo " => Incorrect zone, or zone doesn't contain the A-record ${CFHOST}!"
echo "listing all records for zone ${CFZONE}:"
(printf "ID RECORD TYPE\n";
curl -s https://www.cloudflare.com/api_json.html \
-d a=rec_load_all \
-d tkn=$CFKEY \
-d email=$CFUSER \
-d z=$CFZONE \
| grep -Eo '"(rec_id|name|type)":"([^"]+)"' \
| cut -d':' -f2 \
| awk 'NR%3{gsub("\"","");printf $0" ";next;}1'
)| column -t
exit 2
fi
echo " => Found CFID=${CFID}, advising to save this to ${0} or set it using the -i flag"
fi
# If WAN is changed, update cloudflare
echo "Updating DNS to $WAN_IP"
RESPONSE=$(
curl -s https://www.cloudflare.com/api_json.html \
-d a=rec_edit \
-d tkn=$CFKEY \
-d email=$CFUSER \
-d z=$CFZONE \
-d id=$CFID \
-d ttl=$CFTTL \
-d type=A \
-d name=$CFHOST \
-d "content=$WAN_IP"
)
if [ "$RESPONSE" != "${RESPONSE%success*}" ]; then
echo "Updated succesfuly!"
echo $WAN_IP > $HOME/.wan_ip-cf.txt
exit
else
echo 'Something went wrong :('
echo "Response: $RESPONSE"
exit 1
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment