Skip to content

Instantly share code, notes, and snippets.

@DvdGiessen
Last active March 19, 2022 22:42
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DvdGiessen/040cc87ea81cbfd3340997d28f7c5636 to your computer and use it in GitHub Desktop.
Save DvdGiessen/040cc87ea81cbfd3340997d28f7c5636 to your computer and use it in GitHub Desktop.
Small dynamic DNS update script for a domain in TransIP
#!/bin/bash
# Script for automatically updating DNS records using the TransIP API.
# Supports IPv4 and IPv6 and can update both the primary domain as well as subdomains.
# Requires you to have curl, dig, and openssl installed.
#
# By Daniël van de Giessen, 2021-04-17
# https://www.dvdgiessen.nl
# Configuration
DOMAIN_PRIMARY="example.com"
DOMAIN_SUB="subdomain"
DOMAIN_TTL="300"
TRANSIP_USERNAME="example"
TRANSIP_API_PRIVATEKEY="-----BEGIN PRIVATE KEY-----
CrEaTe-A-nEw-KeYpAiR-aNd-MaKe-SuRe-It-DoEs-NoT-rEqUiRe-A-wHiTeLiStEd-Ip-AdDrEsS
-----END PRIVATE KEY-----"
# Check which emoji's to use
EMOJI_ERROR=":("
EMOJI_SUCCESS=":)"
if tput -V >/dev/null 2>&1 && [[ -t 1 && -n "$(tput colors)" && "$(tput colors)" -ge 8 ]] ; then
EMOJI_ERROR="$(tput setaf 1)${EMOJI_ERROR}$(tput sgr0)"
EMOJI_SUCCESS="$(tput setaf 2)${EMOJI_SUCCESS}$(tput sgr0)"
fi
# Check if required tools are installed
if ! curl --version >/dev/null 2>&1 ; then
echo "Could not find 'curl' which is needed for this script. ${EMOJI_ERROR}" >&2
exit 1
fi
if ! dig -v >/dev/null 2>&1 ; then
echo "Could not find 'dig' which is needed for this script. ${EMOJI_ERROR}" >&2
exit 1
fi
if ! openssl version >/dev/null 2>&1 ; then
echo "Could not find 'openssl' which is needed for this script. ${EMOJI_ERROR}" >&2
exit 1
fi
# Check configuration
if [[ -z "${DOMAIN_PRIMARY}" ]] ; then
echo "Incorrect configuration: missing DOMAIN_PRIMARY. ${EMOJI_ERROR}" >&2
exit 1
fi
if [[ -z "${DOMAIN_TTL}" ]] ; then
echo "Incorrect configuration: missing DOMAIN_TTL. ${EMOJI_ERROR}" >&2
exit 1
fi
if [[ -z "${TRANSIP_USERNAME}" ]] ; then
echo "Incorrect configuration: missing TRANSIP_USERNAME. ${EMOJI_ERROR}" >&2
exit 1
fi
if [[ -z "${TRANSIP_API_PRIVATEKEY}" ]] ; then
echo "Incorrect configuration: missing TRANSIP_API_PRIVATEKEY. ${EMOJI_ERROR}" >&2
exit 1
fi
# Determine domains to operate on
DOMAIN_PRIMARY="$(echo "${DOMAIN_PRIMARY}" | tr '[:upper:]' '[:lower:]')"
DOMAIN_SUB="$(echo "${DOMAIN_SUB}" | tr '[:upper:]' '[:lower:]')"
DOMAIN_FULL="${DOMAIN_SUB}.${DOMAIN_PRIMARY}"
if [[ -z "${DOMAIN_SUB}" || "${DOMAIN_SUB}" == "@" || "${DOMAIN_SUB}" == "${DOMAIN_PRIMARY}" ]] ; then
DOMAIN_FULL="${DOMAIN_PRIMARY}"
DOMAIN_SUB="@"
fi
# Look up the authoritive nameserver
DOMAIN_NAMESERVER="$(dig +short "${DOMAIN_PRIMARY}" NS | head -n1)"
if [[ -z "${DOMAIN_NAMESERVER}" ]] ; then
echo "Failed to get nameserver for ${DOMAIN_PRIMARY}! ${EMOJI_ERROR}" >&2
exit 1
fi
# Get the currently configured IP record from the authoritive nameserver
RECORD_IPV4="$(dig +noall +answer "${DOMAIN_FULL}" A "@${DOMAIN_NAMESERVER}" 2>/dev/null)"
RECORD_IPV6="$(dig +noall +answer "${DOMAIN_FULL}" AAAA "@${DOMAIN_NAMESERVER}" 2>/dev/null)"
# Get the TTL
TTL_IPV4="$(echo "${RECORD_IPV4}" | awk '{ print $2 }')"
TTL_IPV6="$(echo "${RECORD_IPV6}" | awk '{ print $2 }')"
# Get the IP address
CONFIGURED_IPV4="$(echo "${RECORD_IPV4}" | awk '{ print $5 }')"
CONFIGURED_IPV6="$(echo "${RECORD_IPV6}" | awk '{ print $5 }')"
# Get current external IP addresses of this machine
CURRENT_IPV4="$(curl -sS -4 ifconfig.co 2>/dev/null)"
if [[ -z "${CURRENT_IPV4}" ]] ; then
CURRENT_IPV4="$(curl -sS -4 icanhazip.com 2>/dev/null)"
fi
if [[ -z "${CURRENT_IPV4}" ]] ; then
CURRENT_IPV4="$(curl -sS -4 api4.ipify.org 2>/dev/null)"
fi
CURRENT_IPV6="$(curl -sS -6 ifconfig.co 2>/dev/null)"
if [[ -z "${CURRENT_IPV6}" ]] ; then
CURRENT_IPV6="$(curl -sS -6 icanhazip.com 2>/dev/null)"
fi
if [[ -z "${CURRENT_IPV6}" ]] ; then
CURRENT_IPV6="$(curl -sS -6 api6.ipify.org 2>/dev/null)"
fi
# Check if the IP needs to be updated
if [[ "${CONFIGURED_IPV4}" == "${CURRENT_IPV4}" && "${CONFIGURED_IPV6}" == "${CURRENT_IPV6}" && "${TTL_IPV4}" == "${DOMAIN_TTL}" && "${TTL_IPV6}" == "${DOMAIN_TTL}" ]] ; then
echo "No need to update IPv4/IPv6 address for ${DOMAIN_FULL}. ${EMOJI_SUCCESS}"
exit 0
fi
# Authenticate using a request signed with the private key
TRANSIP_AUTH_PAYLOAD="{
\"login\": \"${TRANSIP_USERNAME}\",
\"nonce\": \"$(openssl rand -hex 16)\",
\"read_only\": false,
\"expiration_time\": \"5 minutes\",
\"label\": \"$(basename "$0")@$(hostname):$(openssl rand -hex 8)\",
\"global_key\": true
}"
TRANSIP_AUTH_PRIVATEKEY_FILE="$(mktemp)"
echo "${TRANSIP_API_PRIVATEKEY}" > "${TRANSIP_AUTH_PRIVATEKEY_FILE}"
TRANSIP_AUTH_SIGNATURE="$(echo -n "${TRANSIP_AUTH_PAYLOAD}" | openssl sha512 -sign "${TRANSIP_AUTH_PRIVATEKEY_FILE}" | openssl base64 | tr -d $'\n')"
rm -f "${TRANSIP_AUTH_PRIVATEKEY_FILE}"
if [[ -z "${TRANSIP_AUTH_SIGNATURE}" ]] ; then
echo "Failed to sign authentication request using the configured private key! ${EMOJI_ERROR}" >&2
exit 1
fi
TRANSIP_AUTH_JWT_JSON="$(curl -sS -X POST -H 'Content-Type: application/json' -H "Signature: ${TRANSIP_AUTH_SIGNATURE}" -d "${TRANSIP_AUTH_PAYLOAD}" https://api.transip.nl/v6/auth)"
# Check if authentication succeeded
if [[ -z "${TRANSIP_AUTH_JWT_JSON}" ]] || echo "${TRANSIP_AUTH_JWT_JSON}" | grep -q error ; then
echo "Failed to authenticate with TransIP API! ${EMOJI_ERROR}" >&2
echo "Result from TransIP /v6/auth API: ${TRANSIP_AUTH_JWT_JSON}" >&2
exit 1
fi
# Extract the token from the JSON
TRANSIP_AUTH_JWT="$(echo "${TRANSIP_AUTH_JWT_JSON}" | cut -d: -f2 | tr -d '"}')"
# Exit status
EXITSTATUS=0
# Change the IPv4 through the TransIP API
if [[ "${CONFIGURED_IPV4}" != "${CURRENT_IPV4}" ]] ; then
METHOD_IPV4="PATCH"
CONTENT_IPV4="${CURRENT_IPV4}"
EXPIRE_IPV4="${DOMAIN_TTL}"
if [[ -z "${CONFIGURED_IPV4}" ]] ; then
METHOD_IPV4="POST"
elif [[ -z "${CURRENT_IPV4}" ]] ; then
METHOD_IPV4="DELETE"
CONTENT_IPV4="${CONFIGURED_IPV4}"
EXPIRE_IPV4="${TTL_IPV4}"
elif [[ "${TTL_IPV4}" != "${DOMAIN_TTL}" ]] ; then
DELETE_TTL_PAYLOAD_IPV4="{
\"dnsEntry\": {
\"name\": \"${DOMAIN_SUB}\",
\"expire\": ${TTL_IPV4},
\"type\": \"A\",
\"content\": \"${CONFIGURED_IPV4}\"
}
}"
DELETE_TTL_RESULT_IPV4="$(curl -sS -X DELETE -H 'Content-Type: application/json' -H "Authorization: Bearer ${TRANSIP_AUTH_JWT}" -d "${DELETE_TTL_PAYLOAD_IPV4}" "https://api.transip.nl/v6/domains/${DOMAIN_PRIMARY}/dns")"
if [[ -z "${DELETE_TTL_RESULT_IPV4}" ]] || echo "${DELETE_TTL_RESULT_IPV4}" | grep -q error ; then
echo "Failed to update remove IPv4 address for ${DOMAIN_FULL} from with incorrect TTL of ${TTL_IPV4}! ${EMOJI_ERROR}" >&2
echo "Result from TransIP /v6/domains/${DOMAIN_PRIMARY}/dns API: ${DELETE_TTL_RESULT_IPV4}" >&2
fi
METHOD_IPV4="POST"
fi
PAYLOAD_IPV4="{
\"dnsEntry\": {
\"name\": \"${DOMAIN_SUB}\",
\"expire\": ${EXPIRE_IPV4},
\"type\": \"A\",
\"content\": \"${CONTENT_IPV4}\"
}
}"
RESULT_IPV4="$(curl -sS -X "${METHOD_IPV4}" -H 'Content-Type: application/json' -H "Authorization: Bearer ${TRANSIP_AUTH_JWT}" -d "${PAYLOAD_IPV4}" "https://api.transip.nl/v6/domains/${DOMAIN_PRIMARY}/dns")"
if [[ -n "${RESULT_IPV4}" ]] && ! echo "${RESULT_IPV4}" | grep -q error ; then
if [[ -z "${CONFIGURED_IPV4}" ]] ; then
echo "Added IPv4 address for ${DOMAIN_FULL} as ${CURRENT_IPV4}! ${EMOJI_SUCCESS}"
elif [[ -z "${CURRENT_IPV4}" ]] ; then
echo "Removed IPv4 address for ${DOMAIN_FULL}! ${EMOJI_SUCCESS}"
else
echo "Updated IPv4 address for ${DOMAIN_FULL} from ${CONFIGURED_IPV4} to ${CURRENT_IPV4}! ${EMOJI_SUCCESS}"
fi
else
if [[ -z "${CONFIGURED_IPV4}" ]] ; then
echo "Failed to add IPv4 address for ${DOMAIN_FULL} as ${CURRENT_IPV4}! ${EMOJI_ERROR}" >&2
elif [[ -z "${CURRENT_IPV4}" ]] ; then
echo "Failed to remove IPv4 address for ${DOMAIN_FULL}! ${EMOJI_ERROR}" >&2
else
echo "Failed to update IPv4 address for ${DOMAIN_FULL} from ${CONFIGURED_IPV4} to ${CURRENT_IPV4}! ${EMOJI_ERROR}" >&2
fi
echo "Result from TransIP /v6/domains/${DOMAIN_PRIMARY}/dns API: ${RESULT_IPV4}" >&2
EXITSTATUS=1
fi
else
echo "No need to update IPv4 address for ${DOMAIN_FULL}. ${EMOJI_SUCCESS}"
fi
# Change the IPv6 through the TransIP API
if [[ "${CONFIGURED_IPV6}" != "${CURRENT_IPV6}" ]] ; then
METHOD_IPV6="PATCH"
CONTENT_IPV6="${CURRENT_IPV6}"
EXPIRE_IPV6="${DOMAIN_TTL}"
if [[ -z "${CONFIGURED_IPV6}" ]] ; then
METHOD_IPV6="POST"
elif [[ -z "${CURRENT_IPV6}" ]] ; then
METHOD_IPV6="DELETE"
CONTENT_IPV6="${CONFIGURED_IPV6}"
EXPIRE_IPV6="${TTL_IPV6}"
elif [[ "${TTL_IPV6}" != "${DOMAIN_TTL}" ]] ; then
DELETE_TTL_PAYLOAD_IPV6="{
\"dnsEntry\": {
\"name\": \"${DOMAIN_SUB}\",
\"expire\": ${TTL_IPV6},
\"type\": \"A\",
\"content\": \"${CONFIGURED_IPV6}\"
}
}"
DELETE_TTL_RESULT_IPV6="$(curl -sS -X DELETE -H 'Content-Type: application/json' -H "Authorization: Bearer ${TRANSIP_AUTH_JWT}" -d "${DELETE_TTL_PAYLOAD_IPV6}" "https://api.transip.nl/v6/domains/${DOMAIN_PRIMARY}/dns")"
if [[ -z "${DELETE_TTL_RESULT_IPV6}" ]] || echo "${DELETE_TTL_RESULT_IPV6}" | grep -q error ; then
echo "Failed to update remove IPv6 address for ${DOMAIN_FULL} from with incorrect TTL of ${TTL_IPV6}! ${EMOJI_ERROR}" >&2
echo "Result from TransIP /v6/domains/${DOMAIN_PRIMARY}/dns API: ${DELETE_TTL_RESULT_IPV6}" >&2
fi
METHOD_IPV6="POST"
fi
PAYLOAD_IPV6="{
\"dnsEntry\": {
\"name\": \"${DOMAIN_SUB}\",
\"expire\": ${EXPIRE_IPV6},
\"type\": \"AAAA\",
\"content\": \"${CONTENT_IPV6}\"
}
}"
RESULT_IPV6="$(curl -sS -X "${METHOD_IPV6}" -H 'Content-Type: application/json' -H "Authorization: Bearer ${TRANSIP_AUTH_JWT}" -d "${PAYLOAD_IPV6}" "https://api.transip.nl/v6/domains/${DOMAIN_PRIMARY}/dns")"
if [[ -n "${RESULT_IPV6}" ]] && ! echo "${RESULT_IPV6}" | grep -q error ; then
if [[ -z "${CONFIGURED_IPV6}" ]] ; then
echo "Added IPv6 address for ${DOMAIN_FULL} as ${CURRENT_IPV6}! ${EMOJI_SUCCESS}"
elif [[ -z "${CURRENT_IPV6}" ]] ; then
echo "Removed IPv6 address for ${DOMAIN_FULL}! ${EMOJI_SUCCESS}"
else
echo "Updated IPv6 address for ${DOMAIN_FULL} from ${CONFIGURED_IPV6} to ${CURRENT_IPV6}! ${EMOJI_SUCCESS}"
fi
else
if [[ -z "${CONFIGURED_IPV6}" ]] ; then
echo "Failed to add IPv6 address for ${DOMAIN_FULL} as ${CURRENT_IPV6}! ${EMOJI_ERROR}" >&2
elif [[ -z "${CURRENT_IPV6}" ]] ; then
echo "Failed to remove IPv6 address for ${DOMAIN_FULL}! ${EMOJI_ERROR}" >&2
else
echo "Failed to update IPv6 address for ${DOMAIN_FULL} from ${CONFIGURED_IPV6} to ${CURRENT_IPV6}! ${EMOJI_ERROR}" >&2
fi
echo "Result from TransIP /v6/domains/${DOMAIN_PRIMARY}/dns API: ${RESULT_IPV6}" >&2
EXITSTATUS=1
fi
else
echo "No need to update IPv6 address for ${DOMAIN_FULL}. ${EMOJI_SUCCESS}"
fi
exit $EXITSTATUS
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment