#!/usr/bin/env bash # # Copyright 2022 David Vazgenovich Shakaryan # # HETZNER_TOKEN= hetzner-ddns.sh IP_RESOLVER='https://ifconfig.co' TARGET="${1}" shopt -s extglob die() { [[ -n "${@}" ]] && echo "${@}" >&2 exit 1 } hetzcurl() { curl -sfH "Auth-API-Token: ${HETZNER_TOKEN}" \ "https://dns.hetzner.com/api/v1/${1}" \ "${@:2}" } ip="$(curl -sf4 "${IP_RESOLVER}")" || die 'IP lookup failed' zone_re="${TARGET}" while [[ "${zone_re}" =~ ^([^\\]*)\.(.*)$ ]]; do zone_re="(${BASH_REMATCH[1]}\\.)?${BASH_REMATCH[2]}" done res="$(hetzcurl "zones")" || die 'Zones lookup failed' IFS='|' read zone_id zone_name < <(jq -er --arg re "${zone_re}" \ '[.zones[] | select(.name | test("\\A" + $re + "\\z"))] | if . == [] then (null | halt_error) else . end | max_by(.name | length) | [.id, .name] | join("|")' \ <<< "${res}") || die 'Zone not found' rec_name="${TARGET%%?(.)${zone_name}}" rec_name="${rec_name:-@}" res="$(hetzcurl "records?zone_id=${zone_id}")" || die 'Records lookup failed' rec_id="$(jq -er --arg name "${rec_name}" \ 'first(.records[] | select(.type == "A" and .name == $name)) | .id' \ <<< "${res}")" || die 'Record not found' res="$(hetzcurl "records/${rec_id}")" || die 'Record lookup failed' old_ip="$(jq -r '.record.value' <<< "${res}")" if [[ "${old_ip}" == "${ip}" ]]; then echo "IP unchanged from ${ip}" exit fi data="$(jq -c --arg ip "${ip}" \ '.record | {zone_id, type, name, value: $ip }' <<< "${res}")" res="$(hetzcurl "records/${rec_id}" \ -H 'Content-Type: application/json' \ -X PUT -d "${data}")" || die 'Record update failed' new_ip="$(jq -r '.record.value' <<< "${res}")" echo "IP changed from ${old_ip} to ${new_ip}"