Skip to content

Instantly share code, notes, and snippets.

@movd
Created March 11, 2021 10:44
Show Gist options
  • Save movd/03febb3ab6435db46452fb55f3a44d65 to your computer and use it in GitHub Desktop.
Save movd/03febb3ab6435db46452fb55f3a44d65 to your computer and use it in GitHub Desktop.
Run as a cronjob to restore the `cloud.` DNS record that dynv6.com currently keeps on deleting because of a bug in the system.
#!/usr/bin/env bash
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
me="$(basename "$(test -L "$0" && readlink "$0" || echo "$0")")"
DYNV6_APIBASE="https://dynv6.com/api/v2/zones"
DYNV6_TOKEN=${DYNV6_TOKEN}
DYNV6_CNAME_RECORD=${DYNV6_CNAME_RECORD}
dynv6_zone_name=""
check_dependencies() {
# look for required binaries (taken straight from dehydrated)
for binary in curl jq dig ts; do
bin_path="$(command -v "${binary}" 2>/dev/null)" || _exiterr "This script requires ${binary}."
[[ -x "${bin_path}" ]] || _exiterr "${binary} found in PATH but it's not executable"
done
}
_echo() {
echo "${me}: ${1}" |ts
}
_exiterr() {
echo "ERROR: ${1}" >&2
exit 1
}
check_config() {
if [ -z "${DYNV6_TOKEN}" ] || [ -z "${DYNV6_ZONEID}" ]; then
# if env variables not set source .env file as config
# load .env https://stackoverflow.com/a/30969768 and check
set -o allexport
# shellcheck disable=SC1090
source "${DIR}/${ENV}.env" 2>/dev/null || _exiterr "${DIR}/${ENV}.env is not available!" &&
set +o allexport
[[ -n "${DYNV6_TOKEN}" ]] || _exiterr "Environment variable DYNV6_TOKEN is empty!"
[[ -n "${DYNV6_ZONEID}" ]] || _exiterr "Environment variable DYNV6_ZONEID is empty!"
[[ -n "${DYNV6_CNAME_RECORD}" ]] || _exiterr "Environment variable DYNV6_CNAME_RECORD is empty!"
fi
}
check_dynv6_connection() {
# test connection to dynv6 api
check_connection_status_code=$(curl -s -o /dev/null -I -w "%{http_code}" -X GET -H "Authorization: Bearer ${DYNV6_TOKEN}" ${DYNV6_APIBASE}/"${DYNV6_ZONEID}"/records)
if ! [[ $check_connection_status_code = "200" ]]; then
[[ "$check_connection_status_code" == "401" ]] && _exiterr "HTTP code 401: Unauthorized"
[[ "$check_connection_status_code" == "403" ]] && _exiterr "HTTP code 403: Forbidden. Check your DYNV6_TOKEN"
[[ "$check_connection_status_code" == "404" ]] && _exiterr "HTTP code 404: Zone not found. Check your DYNV6_ZONEID"
_exiterr "Could not connect to dynv6 API. returned HTTP code: ${check_connection_status_code}"
fi
}
get_zone_name() {
# get zone name from zone id
all_zones=$(curl -s -X GET -H "Accept: application/json" -H "Authorization: Bearer ${DYNV6_TOKEN}" ${DYNV6_APIBASE} )
dynv6_zone_name=$(echo "${all_zones}" | jq -r --arg arg "$DYNV6_ZONEID" '
map(
select(
(
.id == ( $arg | tonumber )
)
)
) | .[].name' # Just extract the zone name
)
}
create_new_cname_record() {
json_payload='{"name":"'"$DYNV6_CNAME_RECORD"'","data":"'$dynv6_zone_name.'","type":"CNAME"}'
_echo "Sending the following payload..."
_echo "${json_payload}"
res=$(curl -s -X POST -H "Content-Type: application/json" -H "Authorization: Bearer ${DYNV6_TOKEN}" -d "$json_payload" ${DYNV6_APIBASE}/"${DYNV6_ZONEID}"/records)
record_id=$(echo "${res}"| jq -r '.id')
re='^[0-9]+$'
if ! [[ $record_id =~ $re ]]; then
_exiterr "Something is really wrong. I did not receive a numeric ID from the dynv6.com API"
else
_echo "Created CNAME record with id: ${record_id}. The API responded with:"
_echo "${res}"
_echo "Waiting for DNS propagation..."
i=1
# cloud.example.com must answer with example.com
while ! dig CNAME +trace +noall +answer "${DYNV6_CNAME_RECORD}.${dynv6_zone_name}" | grep -q "${dynv6_zone_name}."; do
# abort propagation check after 30 seconds
sleep 1
((i++))
if ((i > 29)); then
break
fi
done
fi
}
run_main() {
all_records=$(curl -s -X GET -H "Accept: application/json" -H "Authorization: Bearer ${DYNV6_TOKEN}" ${DYNV6_APIBASE}/"${DYNV6_ZONEID}"/records)
found_record_id=$(echo "${all_records}" | jq -r --arg arg "$DYNV6_CNAME_RECORD" 'map(select((.name == $arg ))) | .[].id')
# If found_record_id is not NaN it does not exist
re='^[0-9]+$'
if ! [[ $found_record_id =~ $re ]]; then
_echo "No record for '${DYNV6_CNAME_RECORD}.${dynv6_zone_name}' found! I will create a new one..."
create_new_cname_record || exit 1
#else
#_echo "Record '${DYNV6_CNAME_RECORD}.${dynv6_zone_name}' already exists. I will do nothing."
fi
}
check_dependencies || exit 1
check_config || exit 1
check_dynv6_connection || exit 1
get_zone_name || exit 1
run_main || exit 1
@movd
Copy link
Author

movd commented Mar 11, 2021

Run this script like so:

$ DYNV6_TOKEN=your_api_token DYNV6_ZONEID=1234567 DYNV6_CNAME_RECORD=cloud ./dynv6-record-fixer.sh
Mar 11 11:39:47 dynv6-record-fixer.sh: No record for 'cloud.example.com' found! I will create a new one...
Mar 11 11:39:47 dynv6-record-fixer.sh: Sending the following payload...
Mar 11 11:39:47 dynv6-record-fixer.sh: {"name":"cloud","data":"example.com.","type":"CNAME"}
Mar 11 11:39:48 dynv6-record-fixer.sh: Created CNAME record with id: 9876543. The API responded with:
Mar 11 11:39:48 dynv6-record-fixer.sh: {"type":"CNAME","name":"cloud","data":"example.com.","priority":null,"flags":null,"tag":null,"weight":null,"port":null,"id":9876543,"zoneID":1234567}
Mar 11 11:39:48 dynv6-record-fixer.sh: Waiting for DNS propagation...

The following environment variables are required:

  • DYNV6_TOKEN
  • DYNV6_ZONEID
  • DYNV6_CNAME_RECORD = The subdomain that gets wrongfully deleted

Environment variables can be set in a .env file in the same folder as the script or passed via the shell.

Get DYNV6_TOKEN Key:

  • Go to https://dynv6.com/keys and log in with your credentials
    View your default token or create a new one: "🔍 Details"

Get DYNV6_ZONEID ID:

  • Click "My Zones" -> example.com
  • You can now get the zone from the URL. For example, the ID of the domain https://dynv6.com/zones/123456 is 123456

Example crontab entry:

*/5 * * * * /home/ubuntu/dynv6-record-fixer.sh >> /home/ubuntu/dynv6-record-fixer.log

Limitations:

  • Your delegated domain example.com must be set up as a single-zone.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment