Skip to content

Instantly share code, notes, and snippets.

@sparanoid
Created July 28, 2019 04:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sparanoid/79f3cbbee6340bb7df5a265d0f09f329 to your computer and use it in GitHub Desktop.
Save sparanoid/79f3cbbee6340bb7df5a265d0f09f329 to your computer and use it in GitHub Desktop.
CloudFlare as a Dynamic DNS (DDNS)
#!/bin/bash
# CloudFlare as a Dynamic DNS (DDNS)
# Support CloudFlare API v4
# Author: Tunghsiao Liu (t@sparanoid.com)
# Setup envrionment
export PATH=/usr/local/bin:/usr/local/sbin:$PATH
# NOTE: You need `curl` v7.19.4 or higher to support `--noproxy` flag.
# First install jq on macOS:
# brew install jq
# Add this script to cron, run it every 10 mins:
# */10 * * * * bash ~/Documents/Mac/Scripts/server-update-cloudflare.sh
# CloudFlare Global API key
TOKEN="<api_key>"
# CloudFlare account
ACCOUNT="<email>"
# Domain name
DOMAIN="example.com"
# The record you want to update
# e.g. blog, use the bare domain if you want to update root (@) record
RECORD="myhome.example.com"
# Record TTL
TTL=120
# The DNS record type you want to update, must be UPPERCASED
RECORD_TYPE="A"
# jq location
JQ=`which jq`
# Log location, no permission for `/var/log/`, so I have to store it in `/tmp/`
LOGFILE="/tmp/update-cloudflare.log"
# CloudFlare API functions
# Example: getcf "/zones?name=nio2.com" ".result[0].id"
getcf () {
curl -sS -X GET "https://api.cloudflare.com/client/v4$1" \
-H "X-Auth-Email: $ACCOUNT" \
-H "X-Auth-Key: $TOKEN" \
-H "Content-Type: application/json" | $JQ -r "$2"
}
# Example: putcf "/zones?name=nio2.com" ".result[0].id"
putcf () {
curl -sS -X PUT "https://api.cloudflare.com/client/v4$1" \
-H "X-Auth-Email: $ACCOUNT" \
-H "X-Auth-Key: $TOKEN" \
-H "Content-Type: application/json" --data $2 | $JQ
}
CF_ZONE_ID=$(getcf "/zones?name=$DOMAIN" ".result[0].id")
echo " Retrived zone id: $CF_ZONE_ID"
IPOLD=$(getcf "/zones/$CF_ZONE_ID/dns_records" ".result[] | select(.name == \"$RECORD\") | select(.type == \"$RECORD_TYPE\") | .content")
echo " Retrived old IP: $IPOLD"
# Get current external IP address
IP=`curl --noproxy '*' -s http://whatismyip.akamai.com/`
echo "Retrived current IP: $IP"
# Check if VPN is active
IPVPN=`ifconfig | grep -o ppp`
# Validate IP
function valid_ip() {
local ip=$1
local stat=1
if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
OIFS=$IFS
IFS='.'
ip=($ip)
IFS=$OIFS
[[ ${ip[0]} -le 255 && ${ip[1]} -le 255 \
&& ${ip[2]} -le 255 && ${ip[3]} -le 255 ]]
stat=$?
fi
return $stat
}
# Compare IPs
if ! valid_ip $IP; then
echo "[`date`] ERROR: Invalid IP '$IP'" >> "$LOGFILE"
exit 1
fi
# Check if the IP has changed
if [ "$IP" == "$IPOLD" ] || [[ $IPVPN ]]; then
echo "Same IP, nothing to change."
echo "[`date`] SAME: $IP $IPVPN" >> "$LOGFILE"
exit 0
else
# Only get record ID if IP changed, this could help reduce 1 API request
CF_RECORD_ID=$(getcf "/zones/$CF_ZONE_ID/dns_records" ".result[] | select(.name == \"$RECORD\") | select(.type == \"$RECORD_TYPE\") | .id")
echo " Retrived record id: $CF_RECORD_ID"
echo " JSON output:"
putcf "/zones/$CF_ZONE_ID/dns_records/$CF_RECORD_ID" "{\"id\":\"$CF_RECORD_ID\",\"type\":\"$RECORD_TYPE\",\"name\":\"$RECORD\",\"content\":\"$IP\",\"ttl\":$TTL,\"zone_id\":\"$CF_ZONE_ID\",\"zone_name\":\"$DOMAIN\"}"
echo "[`date`] UPDATE: $IPOLD >>> $IP" >> "$LOGFILE"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment