#!/usr/bin/env bash # Refactored by: Patrick Huber (22phuber@gmail.com) # Description: Digs the current external Ipv4 and compares it to a Ipv4 locally stored in a file. # If the ips differ, aws cli is used to update a route 53 dns record with the new Ipv4. # This script only supports Ipv4. # Original script source: # - https://willwarren.com/2014/07/03/roll-dynamic-dns-service-using-amazon-route53/ # Dependencies: # - awscli # install AWS cli: https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html # - dig # - mktemp # - grep # set -e set -o pipefail # INITIALIZE { echo "[$(date)] Starting \"${BASH_SOURCE[0]}\"" \ && trap finish EXIT \ && trap 'Error in line: ${LINENO}' ERR \ && cd "$(dirname "${BASH_SOURCE[0]}")"; } || exit 1 # VARIABLES # AWS profile aws_profile='default' # AWS cli path aws_binary_path='/usr/local/bin' # set new PATH for AWS binary if [[ ! ${PATH} == *"${aws_binary_path}"* ]]; then PATH=${PATH}:${aws_binary_path} fi # ipfile (caches last runs ip) declare -r IPFILE="update-route53.ip" # Hosted Zone ID e.g. BJBK35SKMM9OE declare -r ZONEID="" # The recordset you want to update e.g. example.com or www.example.com declare -r RECORDSET="" # The Time-To-Live of this recordset declare -r TTL=300 # Use type A when using IPv4 address declare -r TYPE="A" # Change this if you want COMMENT="Auto updating @ $(date)" # FUNCTIONS # desc: prints the date and "script finished" text # usage: checkAwsCli function finish () { echo -e "[$(date)] Script finished\n" } # desc: check if aws cli is present # usage: checkAwsCli # returns: # 0 => if aws cli exists # 1 => if aws cli could not be found function checkAwsCli () { if [[ -x "$(command -v aws)" ]]; then # True if file exists and is executable. return 0 else echo "Function [${FUNCNAME[0]}]: AWS cli is missing." return 1 fi } # desc: simple validation for a given ipv4 address # checks: # - correct ip parts # - part values between 0 and 255 (inclusive) # usage: validate_ipv4 "" # returns: # 0 => if validation was successful # 1 => if validation has failed function validate_ipv4() { if [[ $# -lt 1 ]] ; then echo "Function [${FUNCNAME[0]}]: Wrong argument count. Nothing validated" return 1 fi local __ip="${1}" declare -a __ip_arr=${__ip//./ } if [[ ${__ip} =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then for ip_part in ${__ip_arr[@]}; do if [[ ! ${ip_part} -le 255 ]]; then return 1; fi done return 0 fi return 1 } # desc: chcek if given string is present in the given file # usage: find_string_in_file "" file # returns: # 0 => if string has been found # 1 => if string has NOT been found function find_string_in_file() { if [[ $# -lt 2 ]] ; then echo "Function [${FUNCNAME[0]}]: Wrong argument count. Nothing done" return 1 fi local __string_to_find="${1}" shift 1 local __file="${1}" grep -Fxq "${__string_to_find}" "${__file}" && return 0 return 1 } # desc: caches given ipv4 in given file # usage: cache_ipv4 "" file # 0 => if caching was successful # 1 => if caching failed function cache_ipv4 () { if [[ $# -lt 2 ]] ; then echo "Function [${FUNCNAME[0]}]: Wrong argument count. Nothing cached" return 1 fi local __ip="${1}" shift 1 local __file="${1}" validate_ipv4 "${__ip}" \ && echo "${__ip}" > "${__file}" \ && find_string_in_file "${__ip}" "${__file}" \ && return 0 return 1 } # desc: update route 53 record # usage: update_route53_record "" "" "" "" "" "" "" # 0 => if route 53 record update was successful # 1 => if route 53 record update failed function update_route53_record () { if [[ $# -lt 7 ]] ; then echo "Function [${FUNCNAME[0]}]: Wrong argument count. Nothing updated" return 1 fi local __zoneid="${1}"; shift local __ip="${1}"; shift local __recordset="${1}"; shift local __type="${1}"; shift local __ttl="${1}"; shift local __comment="${1}"; shift local __aws_profile="${1}"; local __tmpFile; __tmpFile=$(mktemp /var/tmp/aws_route53_change-resource-record-set.json.XXXXXXXX) # trap temp file deletion on RETURN signal trap 'rm -rf "${__tmpFile}"' RETURN # heredoc section cat << EOF > "${__tmpFile}" { "Comment":"${__comment}", "Changes":[ { "Action":"UPSERT", "ResourceRecordSet":{ "ResourceRecords":[ { "Value":"${__ip}" } ], "Name":"${__recordset}", "Type":"${__type}", "TTL":${__ttl} } } ] } EOF # Update the route 53 record aws --profile "${__aws_profile}" route53 change-resource-record-sets \ --hosted-zone-id "${__zoneid}" \ --change-batch "file://${__tmpFile}" \ && return 0 return 1 } # MAIN checkAwsCli || exit 1 # Get the external IP address # https://unix.stackexchange.com/questions/22615/how-can-i-get-my-external-ip-address-in-a-shell-script # example1: dig @resolver1.opendns.com A myip.opendns.com +short -4 # example2: dig @ns1-1.akamaitech.net ANY whoami.akamai.net +short -4 # example3: dig @ns1.google.com TXT o-o.myaddr.l.google.com +short -4 | sed -e 's#"##g' IPv4=$(dig @resolver1.opendns.com A myip.opendns.com +short -4) { # validate ipv4 validate_ipv4 "${IPv4}" \ && touch "${IPFILE}"; # touching ipfile (creates it, if it does not exist) } || exit 1 # Check current ip with previously save ip from the ipfile if (find_string_in_file "${IPv4}" "${IPFILE}" ); then echo "External IPv4 is still the same." exit 0 else echo "External Ipv4 has changed to ${IPv4}" # Update the record when the ip has changed if (update_route53_record "${ZONEID}" "${IPv4}" "${RECORDSET}" "${TYPE}" "${TTL}" "${COMMENT}" "${aws_profile}" ); then echo "AWS Route 53 record \"${RECORDSET}\" with type \"${TYPE}\" has been updated with value \"${IPv4}\" in zone ${ZONEID}" cache_ipv4 "${IPv4}" "${IPFILE}" exit 0 else echo "Failed to update AWS Route 53 record: ${RECORDSET}" exit 1 fi fi exit 0