Created
February 14, 2019 02:27
-
-
Save azmankudus/ca8d3da339cc009bea8a93c0e960c6c5 to your computer and use it in GitHub Desktop.
Cloudflare Dynamic IP script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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