#!/bin/sh # Created on 2025-12-07 12:22 UTC # Author: Dominic Reich (OE7DRT) # Get my outgoing ipv4 address via ifconfig.me and compare it with the recent ip saved in ~/.my-ipv4 # Update dns with the actual ipv4 if it changed # set default value for CHECK # override this when starting the script like `CHECK=true update-ip.sh` : "${CHECK:=false}" if [ "${CHECK}x" = "truex" ] then # currently abort if jq is not installed # might enter another routine to still display the record at the end command -v jq > /dev/null 2>&1 || { echo >&2 "jq not found"; exit 1; } # jq may be placed somewhere in /usr/local so first get the real path JQ=$(command -v jq) fi # specify file names IPFILE4="${HOME}/.my-ipv4" IPFILE6="${HOME}/.my-ipv6" # Auth token (Hetzner new dns console as of Dec 2025) API_URL="https://api.hetzner.cloud/v1/zones/" AUTH_TOKEN="" # Domain settings (zone id, rrset name, rrset type) ZONE_ID="domain.local" RR_NAME="@" RR_TYPE4="A" RR_TYPE6="AAAA" RR_COMMENT4="" RR_COMMENT6="" # remote ipv4 IPREMOTE4=$(curl -4 -sgkL https://ifconfig.me/ip 2>/dev/null) IPREMOTE6=$(curl -6 -sgkL https://ifconfig.me/ip 2>/dev/null) # echo "DBG: got ip ${IPREMOTE4}" # check if remote ips contain valid data # TODO: # get info about useful regex on ipv4 and ipv6 addresses # check if old IP4 is saved in file if [ -w "${IPFILE4}" -a -f "${IPFILE4}" ] then SAVEDIP4=$(cat ${IPFILE4}) # echo "DBG: saved ip is ${SAVEDIP4}" else # no existing ipv4 file, create one and add new ipv4 into it /bin/echo -n "${IPREMOTE4}" > ${IPFILE4} # echo "DBG: saved ip ${IPREMOTE4} to file ${IPFILE4}" fi #check for IP6 if [ -w "${IPFILE6}" -a -f "${IPFILE6}" ] then SAVEDIP6=$(cat ${IPFILE6}) # echo "DBG: saved ip is ${SAVEDIP4}" else # no existing ipv4 file, create one and add new ipv4 into it /bin/echo -n "${IPREMOTE6}" > ${IPFILE6} # echo "DBG: saved ip ${IPREMOTE4} to file ${IPFILE4}" fi # check if remote ip is identical as locally saved ip if [ "${SAVEDIP4}x" = "${IPREMOTE4}x" ] then # if remote ip is equal as locally saved (old) ip # echo "DBG: do nothing, ips are equal" echo >&2 "IPv4 identical. Nothing to do" else # ips differ, update dns ip with remote ip and save into local ip file curl -s -g -X POST -H "Authorization: Bearer ${AUTH_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"records":[{"value":"'${IPREMOTE4}'","comment":"'${RR_COMMENT4}'"}]}' \ "${API_URL}${ZONE_ID}/rrsets/${RR_NAME}/${RR_TYPE4}/actions/set_records" > /dev/null /bin/echo -n "${IPREMOTE4}" > ${IPFILE4} fi if [ "${CHECK}x" = "truex" ] then # finally check (output actual json record saved on dns server) remote ip curl -s -H "Authorization: Bearer ${AUTH_TOKEN}" \ "${API_URL}${ZONE_ID}/rrsets/${RR_NAME}/${RR_TYPE4}" | ${JQ} "[.[] | .records]" fi # and repeat for ipv6 if [ "${SAVEDIP6}x" = "${IPREMOTE6}x" ] then # if remote ip is equal as locally saved (old) ip # echo "DBG: do nothing, ips are equal" echo >&2 "IPv6 identical. Nothing to do" else # ips differ, update dns ip with remote ip and save into local ip file curl -s -g -X POST -H "Authorization: Bearer ${AUTH_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"records":[{"value":"'${IPREMOTE6}'","comment":"'${RR_COMMENT6}'"}]}' \ "${API_URL}${ZONE_ID}/rrsets/${RR_NAME}/${RR_TYPE6}/actions/set_records" > /dev/null /bin/echo -n "${IPREMOTE6}" > ${IPFILE6} fi if [ "${CHECK}x" = "truex" ] then # also check for ipv6 curl -s -H "Authorization: Bearer ${AUTH_TOKEN}" \ "${API_URL}${ZONE_ID}/rrsets/${RR_NAME}/${RR_TYPE6}" | ${JQ} "[.[] | .records]" fi