Skip to content

Instantly share code, notes, and snippets.

@azmankudus
Created February 14, 2019 02:27
Show Gist options
  • Save azmankudus/ca8d3da339cc009bea8a93c0e960c6c5 to your computer and use it in GitHub Desktop.
Save azmankudus/ca8d3da339cc009bea8a93c0e960c6c5 to your computer and use it in GitHub Desktop.
Cloudflare Dynamic IP script
#!/bin/bash
# Bash script to update Cloudflare DNS content with dynamic IP via CloudFlare API v4. Usually Type A and AAAA.
# Public IP (IPv4/v6) retrieved from ifconfig.co.
# Supporting program used : curl, echo, grep, egrep, head, wc, sed, tr
#
# EMAIL= your account's email
# API_KEY= your global API key
# ZONE_NAME= set target zone name. One zone per script.
# DNS_NAMES= set full DNS names (domain/subdomain) configured in the zone. Separate by space if more than one.
# Example: DNS_NAMES=foo.domain.com bar.domain.com daz.domain.com
# Can specify DNS types for each DNS name after ":".
# Example: DNS_NAMES=foo.domain.com:A:AAAA bar.domain.com daz.domain.com:A
# Where: only type A and AAA will be updated for foo.domain.com
# all types will be updated for bar.domain.com
# only type A will be updated for baz.domain.com
#
# DEBUG=Y if want to view more debug messages
# FORCE=Y if want to force update IP even though no IP change
#
# If everything working, configure crontab. Example (run every 5th minute and log into file):
# */5 * * * * /bin/bash /app/cloudflare/dynamicip.sh >> /app/cloudflare/log/dynamicip-$(date +"\%Y\%m\%d").log
EMAIL=
API_KEY=
ZONE_NAME=
DNS_NAMES=
DEBUG=
FORCE=
### Loggers ###
log () {
echo $(date +"%Y/%m/%d %H:%M:%S %z") [$(printf "%-5s" $1)] $2
}
logInfo () {
log "INFO" "$1"
}
logWarning () {
log "WARN" "$1"
}
logError () {
log "ERROR" "$1"
}
logDebug () {
if [ "$DEBUG" = "Y" ]; then
log "DEBUG" "$1"
fi
}
### Main ###
logInfo "Start"
# Log before exit
quit () {
logInfo "Finish"
exit $1
}
if [ -z "$EMAIL" ]; then
logError "No email"
quit 3
fi
if [ -z "$API_KEY" ]; then
logError "No API key"
quit 3
fi
if [ -z "$ZONE_NAME" ]; then
logError "No zone name"
quit 3
fi
if [ -z "$DNS_NAMES" ]; then
logError "No DNS name"
quit 3
fi
oldIpv6File=
oldIpv6=
oldIpv4File=
oldIpv4=
# Load old IP from file. Skip if FORCE=Y
oldIpv6File="$(pwd)/ipv6.old"
oldIpv4File="$(pwd)/ipv4.old"
if [ "$FORCE" != "Y" ]; then
oldIpv6=$(cat $oldIpv6File 2> /dev/null)
oldIpv4=$(cat $oldIpv4File 2> /dev/null)
fi
# Check cURL error
curlExitCode=0
checkCurlError () {
curlExitCode=$1
if [ "$curlExitCode" -ne "0" ]; then
local errorMessage=$(echo "$2" | grep -Po "(?<=\) )[^\n]*")
logError "$3"
logError "$errorMessage"
fi
}
# Get latest IPv6
newIpv6=$(curl -s -S -X GET "http://v6.ifconfig.co" 2>&1)
checkCurlError $? "Error while getting IPv6!" "$newIpv6"
logDebug "$newIpv6"
if [ "$curlExitCode" -gt 0 ]; then
newIpv6=
logWarning "Skip all AAAA records."
else
newIpv6="$(echo $newIpv6 | egrep '^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$')"
if [ -z "$newIpv6" ]; then
logWarning "Invalid IPv6. Skip all AAAA records."
else
logInfo "New IPv6: $newIpv6"
fi
fi
# Get latest IPv4
newIpv4=$(curl -s -S -X GET "http://v4.ifconfig.co" 2>&1)
checkCurlError $? "Error while getting IPv4!" "$newIpv4"
logDebug "$newIpv4"
if [ "$curlExitCode" -gt 0 ]; then
newIpv4=
logWarning "Skip all A records."
else
newIpv4="$(echo $newIpv4 | egrep '^([0-9]{1,3}\.){3}[0-9]{1,3}?$')"
if [ -z "$newIpv4" ]; then
logWarning "Invalid IPv4. Skip all A records."
else
logInfo "New IPv4: $newIpv4"
fi
fi
# Exit if no new IPv6 and IPv4
if [ -z "$newIpv6" ] && [ -z $"newIpv4" ]; then
logError "No new IP available. Exit."
quit 1
fi
# Exit if no new IP
if [ "$FORCE" != "Y" ] && [ "$newIpv6" == "$oldIpv6" ] && [ "$newIpv4" == "$oldIpv4" ]; then
logInfo "No IP change."
quit 0
fi
# Main API URL and headers
mainUrl="https://api.cloudflare.com/client/v4"
header1="X-Auth-Email: $EMAIL"
header2="X-Auth-Key: $API_KEY"
header3="Content-Type: application/json"
logDebug "URL = $mainUrl"
logDebug "Header-1 = $header1"
logDebug "Header-2 = $header2"
logDebug "Header-3 = $header3"
apiResponse=
apiResponseTotal=
# Call API
# 1=Action name, 2=HTTP Method, 3=URL, 4=Data
callApi() {
apiResponseTotal=
if [ "$2" = "GET" ]; then
apiResponse=$(curl -s -S -X $2 -H "$header1" -H "$header2" -H "$header3" "$3" 2>&1)
else
apiResponse=$(curl -s -S -X $2 -H "$header1" -H "$header2" -H "$header3" -d "$4" "$3" 2>&1)
fi
checkCurlError $? "$apiResponse" "$1"
if [ "$curlExitCode" -gt 0 ]; then
quit 2
fi
logDebug "URL=$3"
logDebug "RESPONSE=$apiResponse"
local success=$(echo "$apiResponse" | grep -Po "(?<=\"success\":)[^,]*" | head -1)
if [ "$success" = "true" ]; then
logInfo "SUCCESS: $1"
else
logError "ERROR: $1"
local errorCodes=$(echo "$apiResponse" | grep -Po "(?<=\"code\":)[^,]*")
local errorMessages=$(echo "$apiResponse" | grep -Po "(?<=\"message\":\")[^\"]*")
local errorIndex=1
local errorCode=$(echo "$errorCodes" | sed -n ${errorIndex}p)
while [ ! -z "$errorCode" ]; do
errorMessage=$(echo "$errorMessages" | sed -n ${errorIndex}p)
logError "Error $errorCode: $errorMessage"
((errorCode++))
errorCode=$(echo "$errorCodes" | sed -n ${errorIndex}p)
done
quit 1
fi
apiResponseTotal=$(echo "$apiResponse" | grep -Po "(?<=\"total_count\":)[^}]*" | head -1)
}
# Get zone info
callApi "Get Zone info ($ZONE_NAME)" "GET" "$mainUrl/zones?name=$ZONE_NAME"
zoneId=$(echo "$apiResponse" | grep -Po "(?<=\"id\":\")[^\"]*" | head -1)
# Update DNS record
updateDns () {
local newIp=$newIpv6
local newIpType=v6
if [ "$3" == "A" ] || [ -z "$newIpv6" ]; then
newIp=$newIpv4
newIpType=v4
fi
if ! (([ "$3" == "AAAA" ] && [ -z "$newIpv6" ]) || ([ "$3" == "A" ] && [ -z "$newIpv4" ])); then
callApi "Update new IP$newIpType ($3: $2)" "PUT" "$mainUrl/zones/$zoneId/dns_records/$1" "{\"name\":\"$2\",\"type\":\"$3\",\"proxied\":$4,\"ttl\":$5,\"content\":\"$newIp\"}"
fi
}
# Get DNS info and update with latest IP
dnsNames=$(echo "$DNS_NAMES" | tr "," " " | tr -s " ")
for dnsNameAndTypes in ${dnsNames}; do
dnsName=$(echo "$dnsNameAndTypes" | cut -d':' -f 1 | tr -s " ")
dnsTypeIndex=2
dnsType=$(echo "$dnsNameAndTypes" | cut -d':' -f $dnsTypeIndex | tr -s " ")
if [ "$dnsName" == "$dnsType" ]; then
dnsType=
fi
# DNS with predefined type(s)
if [ ! -z "$dnsType" ]; then
while [ ! -z "$dnsType" ]; do
if ! (([ "$3" == "AAAA" ] && [ -z "$newIpv6" ]) || ([ "$3" == "A" ] && [ -z "$newIpv4" ])); then
callApi "Get DNS info ($dnsType: $dnsName)" "GET" "$mainUrl/zones/$zoneId/dns_records?name=$dnsName&type=$dnsType"
if [ -z "$apiResponseTotal" ] || [ "$apiResponseTotal" -eq 0 ]; then
logWarning "DNS record does not exists. Skip. ($dnsType: $dnsName)"
else
recordId=$(echo "$apiResponse" | grep -Po "(?<=\"id\":\")[^\"]*")
recordType=$(echo "$apiResponse" | grep -Po "(?<=\"type\":\")[^\"]*")
recordProxied=$(echo "$apiResponse" | grep -Po "(?<=\"proxied\":)[^,]*")
recordTtl=$(echo "$apiResponse" | grep -Po "(?<=\"ttl\":)[^,]*")
updateDns "$recordId" "$dnsName" "$recordType" "$recordProxied" "$recordTtl"
fi
fi
((dnsTypeIndex++))
dnsType=$(echo "$dnsNameAndTypes" | cut -d':' -f $dnsTypeIndex | tr -s " ")
done
# DNS without predefined type(s). Apply to all types with same DNS name
else
callApi "Get DNS info ($dnsName)" "GET" "$mainUrl/zones/$zoneId/dns_records?name=$dnsName"
recordCount=$apiResponseTotal
if [ -z "$apiResponseTotal" ] || [ "$apiResponseTotal" -eq 0 ]; then
logWarning "DNS record does not exists. Skip. ($dnsName)"
else
recordIds=$(echo "$apiResponse" | grep -Po "(?<=\"id\":\")[^\"]*")
recordTypes=$(echo "$apiResponse" | grep -Po "(?<=\"type\":\")[^\"]*")
recordProxieds=$(echo "$apiResponse" | grep -Po "(?<=\"proxied\":)[^,]*")
recordTtls=$(echo "$apiResponse" | grep -Po "(?<=\"ttl\":)[^,]*")
for ((i=1; i<=$recordCount; i++)); do
recordId=$(echo "$recordIds" | sed -n ${i}p)
recordType=$(echo "$recordTypes" | sed -n ${i}p)
recordProxied=$(echo "$recordProxieds" | sed -n ${i}p)
recordTtl=$(echo "$recordTtls" | sed -n ${i}p)
if ([ "$recordType" == "AAAA" ] && [ -z "$newIpv6" ]) || ([ "$recordType" == "A" ] && [ -z "$newIpv4" ]); then
continue
fi
updateDns "$recordId" "$dnsName" "$recordType" "$recordProxied" "$recordTtl"
done
fi
fi
done
# save new IPv6 and IPv4
echo $newIpv6 > $oldIpv6File
echo $newIpv4 > $oldIpv4File
quit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment