summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoe Carstairs <me@joeac.net>2025-12-15 12:03:22 +0000
committerJoe Carstairs <me@joeac.net>2025-12-15 12:03:22 +0000
commit6722b2b23ec6e69181714b52d4a95667127ea5f2 (patch)
tree3ca5f51cfff68d27775a632ead69ea111ad25d4e
parent5fd6d6996ec9f970b4c4c12cf8bcdb3064e83565 (diff)
rework
-rwxr-xr-xdyndns.bash93
-rwxr-xr-xdyndns.sh134
-rwxr-xr-xget_ip_addr.sh51
3 files changed, 185 insertions, 93 deletions
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"