From 6722b2b23ec6e69181714b52d4a95667127ea5f2 Mon Sep 17 00:00:00 2001 From: Joe Carstairs Date: Mon, 15 Dec 2025 12:03:22 +0000 Subject: [PATCH] rework --- dyndns.bash | 93 ---------------------------------- dyndns.sh | 134 +++++++++++++++++++++++++++++++++++++++++++++++++ get_ip_addr.sh | 51 +++++++++++++++++++ 3 files changed, 185 insertions(+), 93 deletions(-) delete mode 100755 dyndns.bash create mode 100755 dyndns.sh create mode 100755 get_ip_addr.sh diff --git a/dyndns.bash b/dyndns.bash deleted file mode 100755 index ebb6ab8..0000000 --- a/dyndns.bash +++ /dev/null @@ -1,93 +0,0 @@ -#! /bin/bash - -# Work with DigitalOcean api to update "A" record for a domain. -# Store DigitalOcean API token in file : DIGITALOCEAN_TOKEN -# Store domain to be updated in file : DIGITALOCEAN_DOMAIN - -SCRIPT="${BASH_SOURCE[0]}" -SCRIPT_DIR=$(builtin cd "${SCRIPT%/*}" && pwd ) -SCRIPT_NAME=${SCRIPT##*/} - -REPEAT_CHECK_DIR=$SCRIPT_DIR -REPEAT_CHECK=$REPEAT_CHECK_DIR/REPEAT_CHECK # file to save argument in if successful - -DIGITALOCEAN_DOMAIN=`cat $SCRIPT_DIR/DIGITALOCEAN_DOMAIN` -DIGITALOCEAN_TOKEN=`cat $SCRIPT_DIR/DIGITALOCEAN_TOKEN` - -# Get IP from command line if available. Otherwise use curl to get public IP. -IP="" -if [ "$#" == "1" ]; then - IP=$1 -else - IP=`curl -4 icanhazip.com 2> /dev/null` -fi - -# If $IP does not look like a valid address then complain and exit. -# For regular expression, thank you to : https://tecadmin.net/shell-script-validate-ipv4-addresses/ -if ! [[ $IP =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then - echo Invalid IP address format: $IP - exit -fi - -get_a_record_ids() { - curl -X GET \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ - "https://api.digitalocean.com/v2/domains/$DIGITALOCEAN_DOMAIN/records" 2> /dev/null | jq '.domain_records | map(select(.type == "A" and .name == "@")) | map(.id) | .[]' -} - -add_a_record() { - curl -X POST \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ - -d "{\"type\":\"A\" \ - ,\"name\":\"@\" \ - ,\"data\":\"$IP\" \ - ,\"priority\":null \ - ,\"port\":null \ - ,\"ttl\":1800 \ - ,\"weight\":null \ - ,\"flags\":null \ - ,\"tag\":null \ - }" \ - "https://api.digitalocean.com/v2/domains/$DIGITALOCEAN_DOMAIN/records" 2> /dev/null | jq '.domain_record.id' -} - -delete_record_by_id() { - echo deleting existing A record for $DIGITALOCEAN_DOMAIN - curl -X DELETE \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ - "https://api.digitalocean.com/v2/domains/$DIGITALOCEAN_DOMAIN/records/$1" > /dev/null 2>&1 -} - -replace_a_record() { - for id in `get_a_record_ids`; do - delete_record_by_id $id - done - - new_record_id=`add_a_record` - if [ "$new_record_id" == "null" ] ; then - echo unable to add new A record for $DIGITALOCEAN_DOMAIN with value : $1 - return 1 - fi - echo added new A record for $DIGITALOCEAN_DOMAIN with value : $1 -} - - -# exit if previous successful call was with same argument -if [[ -e $REPEAT_CHECK ]]; then - PREVIOUS_IP=`cat $REPEAT_CHECK` - if [[ "$IP" == "$PREVIOUS_IP" ]]; then - echo previous update was also with argument: \"$IP\" so exiting now to avoid duplicate work. - exit - fi -fi - -# attempt to replace A record and, if successful, save arguments to check against next time -if replace_a_record $IP; then - # if successful then save arguments. - echo update worked so saving arguments \"$IP\" in $REPEAT_CHECK - mkdir -p $REPEAT_CHECK_DIR - echo "$IP" > $REPEAT_CHECK -fi diff --git a/dyndns.sh b/dyndns.sh new file mode 100755 index 0000000..4847154 --- /dev/null +++ b/dyndns.sh @@ -0,0 +1,134 @@ +#! /bin/sh + +# Work with DigitalOcean api to update "A" and "AAAA" records for a domain. +# Store DigitalOcean API token in file : DIGITALOCEAN_TOKEN +# Store domain to be updated in file : DIGITALOCEAN_DOMAIN + +ip_version="$1" +if [ -z "${ip_version:-}" ] || ([ "$ip_version" != 4 ] && [ "$ip_version" != 6 ]); then + echo "Error: no valid IP version provided" &>2 + echo "Usage: dyndns.bash <4|6>" &>2 + exit 1 +fi + +IP="$(./get_ip_addr.sh $ip_version)" +if [ -z "$IP" ]; then + echo "Error: could not find IP address" &>2 + exit 1 +fi + +SCRIPT="$0" +SCRIPT_DIR=$(cd "${SCRIPT%/*}" && pwd ) +SCRIPT_NAME=${SCRIPT##*/} + +REPEAT_CHECK_DIR=$SCRIPT_DIR +REPEAT_CHECK_A=$REPEAT_CHECK_DIR/REPEAT_CHECK_A +REPEAT_CHECK_AAAA=$REPEAT_CHECK_DIR/REPEAT_CHECK_AAAA +REPEAT_CHECK="$([ $ip_version = 4 ] && echo $REPEAT_CHECK_A || echo $REPEAT_CHECK_AAAA)" + +DIGITALOCEAN_DOMAIN=`cat $SCRIPT_DIR/DIGITALOCEAN_DOMAIN` +DIGITALOCEAN_TOKEN=`cat $SCRIPT_DIR/DIGITALOCEAN_TOKEN` + +get_a_record_ids() { + curl -X GET \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ + "https://api.digitalocean.com/v2/domains/$DIGITALOCEAN_DOMAIN/records" 2> /dev/null | jq '.domain_records | map(select(.type == "A" and .name == "@")) | map(.id) | .[]' +} + +get_aaaa_record_ids() { + curl -X GET \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ + "https://api.digitalocean.com/v2/domains/$DIGITALOCEAN_DOMAIN/records" 2> /dev/null | jq '.domain_records | map(select(.type == "AAAA" and .name == "@")) | map(.id) | .[]' +} + +add_a_record() { + curl -X POST \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ + -d "{\"type\":\"A\" \ + ,\"name\":\"@\" \ + ,\"data\":\"$IP\" \ + ,\"priority\":null \ + ,\"port\":null \ + ,\"ttl\":1800 \ + ,\"weight\":null \ + ,\"flags\":null \ + ,\"tag\":null \ + }" \ + "https://api.digitalocean.com/v2/domains/$DIGITALOCEAN_DOMAIN/records" 2> /dev/null | jq '.domain_record.id' +} + +add_aaaa_record() { + curl -X POST \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ + -d "{\"type\":\"AAAA\" \ + ,\"name\":\"@\" \ + ,\"data\":\"$IP\" \ + ,\"priority\":null \ + ,\"port\":null \ + ,\"ttl\":1800 \ + ,\"weight\":null \ + ,\"flags\":null \ + ,\"tag\":null \ + }" \ + "https://api.digitalocean.com/v2/domains/$DIGITALOCEAN_DOMAIN/records" 2> /dev/null | jq '.domain_record.id' +} + +delete_record_by_id() { + echo deleting existing A/AAAA record for $DIGITALOCEAN_DOMAIN + curl -X DELETE \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ + "https://api.digitalocean.com/v2/domains/$DIGITALOCEAN_DOMAIN/records/$1" > /dev/null 2>&1 +} + +replace_a_records() { + for id in `get_a_record_ids`; do + delete_record_by_id $id + done + + new_record_id=`add_a_record` + if [ "$new_record_id" = "null" ] ; then + echo unable to add new A record for $DIGITALOCEAN_DOMAIN with value: $IP + return 1 + fi + echo added new A record for $DIGITALOCEAN_DOMAIN with value: $IP +} + + +replace_aaaa_records() { + for id in `get_aaaa_record_ids`; do + delete_record_by_id $id + done + + new_record_id=`add_aaaa_record` + if [ "$new_record_id" = "null" ] ; then + echo unable to add new AAAA record for $DIGITALOCEAN_DOMAIN with value: $IP + return 1 + fi + echo added new AAAA record for $DIGITALOCEAN_DOMAIN with value: $IP +} + +replace_records() { + [ $ip_version = 4 ] && replace_a_records || replace_aaaa_records +} + +# exit if previous successful call was with same argument +if [ -e $REPEAT_CHECK ]; then + PREVIOUS_IP=`cat $REPEAT_CHECK` + if [ "$IP" = "$PREVIOUS_IP" ]; then + echo previous update was also with argument: \"$IP\" so exiting now to avoid duplicate work. + exit + fi +fi + +# attempt to replace records and, if successful, save arguments to check against next time +if replace_records $IP; then + # if successful then save arguments. + echo update worked so saving arguments \"$IP\" in $REPEAT_CHECK + mkdir -p $REPEAT_CHECK_DIR + echo "$IP" > $REPEAT_CHECK +fi diff --git a/get_ip_addr.sh b/get_ip_addr.sh new file mode 100755 index 0000000..27c948a --- /dev/null +++ b/get_ip_addr.sh @@ -0,0 +1,51 @@ +#!/bin/sh + +ip_version="$1" +if [ -z "${ip_version:-}" ] || ([ "$ip_version" != 4 ] && [ "$ip_version" != 6 ]); then + echo "Usage: get_ip_addr <4|6>" &>2 + exit 1 +fi + +if [ $ip_version = 4 ]; then + curl -4 http://icanhazip.com 2> /dev/null + exit 0 +fi + +set -eu +CONN_NAME=${CONN_NAME:-nevis} +conn_device_name="$(nmcli --fields NAME,DEVICE con show | grep ^$CONN_NAME | sed 's/\s\+/ /g' | cut -d' ' -f 2)" +if [ -z "${conn_device_name:-}" ]; then + echo "Could not find the connection device name for connection: \"$CONN_NAME\"" &>2 + exit 1 +fi + +device_line_no="$(ip addr | grep -En ^[0-9]+:\ $conn_device_name | cut -d':' -f 1)" +if [ -z "${device_line_no:-}" ]; then + echo "Could not find device: \"$conn_device_name\" in output of ip addr" &>2 + exit 1 +fi + +ip_addr_output_for_device="$(ip addr | tail -n +$(($device_line_no + 1)))" + +device_no="$(ip addr | tail -n +$device_line_no | head -n 1 | cut -d':' -f 1)" +next_device_no=$(($device_no + 1)) +next_device_line_no="$(ip addr | grep -En ^${next_device_no}:\ | cut -d':' -f 1)" +if [ -n "${next_device_line_no:-}" ]; then + len=$(($next_device_line_no - ($device_line_no + 1))) + ip_addr_output_for_device="$(echo $ip_addr_output_for_device | head -n $len)" +fi + +ipv4_regex="([a-z0-9]{1,3}\.){3}[a-z0-9]{1,3}" +ipv6_regex="([a-z0-9]{1,4}:){7}[a-z0-9]{1,4}" +if [ "$ip_version" = "4" ]; then + ip_regex="$ipv4_regex" +else + ip_regex="$ipv6_regex" +fi +ip="$(echo "$ip_addr_output_for_device" | grep "scope global" | grep -oE $ip_regex | head -n 1)" +if [ -z "${ip:-}" ]; then + echo "Could not find global IP in output of ip addr under device: $conn_device_name ($CONN_NAME)" + exit 1 +fi + +echo "$ip"