Bookmark

[Bash Script] Update a Cloudflare DNS Record with a Dynamic IPv4 Address

This script checks your current public IP and quickly updates the corresponding Cloudflare DNS records.

cloudflare_dynamic_ip_update.bash

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#!/usr/bin/env bash

## Author: Hyecheol (Jerry) Jang
## Shell Script that checks the current public (dynamic) IP address of a server
## and updates the Cloudflare DNS record after comparing it with the registered address.
## Basic shell scripting guide: https://blog.gaerae.com/2015/01/bash-hello-world.html
## Use dig (https://en.wikipedia.org/wiki/Dig_(command)) to get the current public IP address.
currentIP=$(dig -4 TXT +short o-o.myaddr.1.google.com @ns1.google.com)
if [ $? == 0 ] && [ ${currentIP} ]; then  ## when dig runs without an error,
    ## Keep only the IP address from the response.
    ## https://stackabuse.com/substrings-in-bash/
    currentIP=$(echo $currentIP | cut -d'"' -f 2)
    echo "current public IP address is "$currentIP
else  ## an error occurred,
    echo "Check your internet connection, or the Google DNS server may be interrupted."
    exit
fi
## Use the Cloudflare API to retrieve the existing record IP.
## https://api.cloudflare.com/
## Read configuration from a separate cloudflare_config file in the same directory.
## https://stackoverflow.com/questions/10929453/read-a-file-line-by-line-assigning-the-value-to-a-variable
## https://stackoverflow.com/questions/10586153/split-string-into-an-array-in-bash
SCRIPT_PATH=$(dirname $(realpath $0))
readResult=""  ## Store configuration.
while IFS= read -r line || [[ -n "$line" ]]; do
    readResult+=" "  ## Delimiter: space.
    readResult+="$line"
done < $SCRIPT_PATH"/cloudflare_config"  ## Use cloudflare_config.
unset IFS
unset SCRIPT_PATH
## Split the configuration string into its elements.
key=$(echo $(echo $readResult | cut -d' ' -f 1) | cut -d'=' -f 2)
email=$(echo $(echo $readResult | cut -d' ' -f 2) | cut -d'=' -f 2)
zoneID=$(echo $(echo $readResult | cut -d' ' -f 3) | cut -d'=' -f 2)
IFS=',' read -r -a updateTarget <<< "$(echo $(echo $readResult | cut -d' ' -f 4) | cut -d'=' -f 2)"
unset readResult
unset IFS
## Allocate arrays for record IP addresses, types, and names.
declare -a recordIP
declare -a recordType
declare -a recordName
declare -a dnsID
declare -a recordProxied
for string in ${updateTarget[@]}; do  ## Retrieve each record's IP address.
    content=$(
        curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zoneID/dns_records?name=${string}" \
            -H "X-Auth-Email: $email" \
            -H "X-Auth-Key: $key" \
            -H "Content-Type: application/json"
    )
    ## Parse JSON with jq.
    ## https://stackoverflow.com/questions/42427371/cloudflare-api-cut-json-response
    ## https://stedolan.github.io/jq/
    ip=$(echo $content | jq '.result | map(.content) | add' | cut -d'"' -f 2)
    rType=$(echo $content | jq '.result | map(.type) | add' | cut -d'"' -f 2)
    name=$(echo $content | jq '.result | map(.name) | add' | cut -d'"' -f 2)
    id=$(echo $content | jq '.result | map(.id) | add' | cut -d'"' -f 2)
    proxied=$(echo $content | jq '.result | map(.proxied) | add' | cut -d'"' -f 2)
    recordIP=(${recordIP[@]} $ip)
    recordType=(${recordType[@]} $rType)
    recordName=(${recordName[@]} $name)
    dnsID=(${dnsID[@]} $id)
    recordProxied=(${recordProxied[@]} $proxied)
    unset id
    unset rType
    unset name
    unset ip
    unset content
    unset string
    unset proxied
done
unset updateTarget
## Compare currentIP with each recordIP.
declare -a needUpdate  ## Store whether each record needs updating.
for string in ${recordIP[@]}; do
    if [ ${string} == ${currentIP} ]; then
        needUpdate=(${needUpdate[@]} 'False')
    else
        needUpdate=(${needUpdate[@]} 'True')
    fi
    unset string
done
unset recordIP  ## No longer needed.
## Update records that need it.
count=0
while [ $count -lt ${#needUpdate[@]} ]; do
    if [ ${needUpdate[count]} == 'True' ]; then
        echo "record IP needs to be updated for "${recordName[count]}
        success=$(
            curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zoneID/dns_records/${dnsID[count]}" \
                -H "X-Auth-Email: $email" \
                -H "X-Auth-Key: $key" \
                -H "Content-Type: application/json" \
                --data '{"type":"'"${recordType[count]}"'","name":"'"${recordName[count]}"'","content":"'"$currentIP"'","proxied":'${recordProxied[count]}'}' | \
            jq '.success' | cut -d'"' -f 2
        )
        if [ $success == true ]; then
            echo "Success: updated the record IP of "${recordName[count]}
        else
            echo "Failed to update the record IP of "${recordName[count]}"\n""Please check the result."
        fi
    else
        echo "record IP does not need to be updated for "${recordName[count]}
    fi
    count=$((${count}+1))
done
unset count
unset currentIP
unset key
unset email
unset zoneID
unset recordType
unset recordName
unset dnsID
unset recordProxied
unset needUpdate

cloudflare_config

1
2
3
4
Auth-Key=[Global API Key]
Auth-Email=[Login Mail]
Zone-ID=[Zone ID]
Update-Target=subdomain.router.io.vn

Credit: Hyecheol (Jerry) Jang
  • cloudflare_config (104bytes - 0 Download.)
  • cloudflare_dynamic_ip_update.bash (4kB - 0 Download.)
  • 0 Bình luận

    Góp Ý / Bình Luận / Đánh giá