Last active
March 19, 2022 22:42
-
-
Save DvdGiessen/040cc87ea81cbfd3340997d28f7c5636 to your computer and use it in GitHub Desktop.
Small dynamic DNS update script for a domain in TransIP
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 | |
# 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