Skip to content

Instantly share code, notes, and snippets.

@troubleshooter
Created August 16, 2025 18:52
Show Gist options
  • Save troubleshooter/f47e999ea469820b8d12dec0ea280c1c to your computer and use it in GitHub Desktop.
Save troubleshooter/f47e999ea469820b8d12dec0ea280c1c to your computer and use it in GitHub Desktop.
Dynamic DNS updater script for use with Linode.com's DNS API
#!/bin/sh
# A shell script to update Linode's DNS with WAN IPs
#
# For use with version 4 of Linode's DNS API
# Requires:
# * dig
# * nc
# * notify-send
# * rsyslog/syslog
# * touch
# * tr
# * wc
# * logger
# * Linode (https://linode.com) API Key, domain ID and resource IDs
# Will create needed files .wan_ip4.txt and .wan_ip6.txt in
# home directory on first run
# Sends notifications via notify-send for failed/problem
# events/changes.
# Run as often as you'd like via cron. Cron job must have environment variables
# exported for notify-send to work properly. Example:
# */15 * * * * export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus; export XDG_RUNTIME_DIR=/run/user/1000; /home/user/scripts/dns-updater-v4.sh
# Logs to syslog.
# The only modifications you need to make are the Linode-specific
# ones directly below, the proper environment variables for your
# system and script path for the cron job and the paths for the various
# commands to suit your system.
# Script defaults to check for both IPv4 and IPv6. Change if
# necessary. See CHECK_IP4 and CHECK_IP6 below.
# Fixed to work with Linode API V4 - 2023/05/08
# Updated to work with Linux Mint 22.1 Xia 2025/08/01
# Copyright (C) 2017-2025 Terry Roy
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. http://www.gnu.org/licenses/.
## LINODE-SPECIFIC SETTINGS
# Linode DNS API variables
# You need to add your information here.
TOKEN=
DOMAIN_ID=
RECORD_ID4=
RECORD_ID6=
# If an error occurs updating the DNS records, the error(s) are returned
# in an array that starts with "errors". Note this is Linode-specific.
ERROR_STR="errors"
# Turn checks on/off 1 = on, 0 = off
# This is handy for travelling. Some WANs won't have an IPv6
# address assigned. In that case, turn off IPv6 checks here instead of
# getting error notifications every time the cron job is run.
CHECK_IP4=1
CHECK_IP6=1
## LOGGING
# Log error messages to syslog as "error". The actual error message is appended
# to this so keep the space in at the end before the quotation mark.
ERR_MSG="/usr/bin/logger -i -t DNS-Updater -p user.err "
# Log other messages to syslog as "notice". The actual message is
# appended to this so keep the space in at the end before the quotation mark.
NOTICE_MSG="/usr/bin/logger -i -t DNS-Updater -p user.notice "
# Current date and time for notification message
LOGTIME=$(date "+%H:%M:%S")
# Shared options for notify-send
NS="/usr/bin/notify-send "
NSHDR="DNS-Update $LOGTIME"
# Paths to commands used
TCH="/usr/bin/touch"
# Use a function to generate the json for curl
generate_json()
{
cat <<EOF
{
"target": "$1"
}
EOF
}
## DATA FILES
# Check for existence and if not present, create.
if [ ! -f "$HOME"/.wan_ip4.txt ] && [ "$CHECK_IP4" -eq 1 ]; then
$TCH "$HOME"/.wan_ip4.txt
$NOTICE_MSG "No IPv4 file, created $HOME/.wan_ip4.txt"
$NS "$NSHDR" "No IPv4 file found. Created $HOME/.wan_ip4.txt."
fi
if [ ! -f "$HOME"/.wan_ip6.txt ] && [ "$CHECK_IP6" -eq 1 ]; then
$TCH "$HOME"/.wan_ip6.txt
$NOTICE_MSG "No IPv6 file found, created $HOME/.wan_ip6.txt"
$NS "$NSHDR" "No IPv6 file found - Created $HOME/.wan_ip6.txt"
fi
# Put in checks for commands
#echo "Debug One"
## RETRIEVE IPS
# Retrieve WAN IPv4
if [ "$CHECK_IP4" -eq 1 ] ; then
# Test for connecticvity to nameserver
# Returns '0' if successful and '1' if unsuccessful.
/usr/bin/nc -z resolver1.opendns.com 53 >/dev/null 2>&1
ONLINE4=$?
if [ "$ONLINE4" -ne 0 ] ; then
$ERR_MSG "No IPv4 connection"
$NS "$NSHDR" "ERROR IPv4 - No connection"
else
WAN_IP4=$(/usr/bin/dig +short -4 myip.opendns.com a @resolver1.opendns.com)
# Quick pseudo-validation
VALID4=$(echo "$WAN_IP4" | /usr/bin/tr -dc \'.\' | /usr/bin/wc -c)
if [ "$VALID4" -eq 3 ]; then
OLD_WAN_IP4=$(cat "$HOME"/.wan_ip4.txt)
### Comparison to previous IP
if [ "$WAN_IP4" != "$OLD_WAN_IP4" ]; then
UPDATE4=$(curl -s -H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-X PUT --data \
"$(generate_json $WAN_IP4)" \
https://api.linode.com/v4/domains/$DOMAIN_ID/records/$RECORD_ID4)
# Check there's no error message in the array
case "$UPDATE4" in
*$ERROR_STR* )
$ERR_MSG "ERROR: Problem updating IPv4: Error returned. Old IPv4: $OLD_WAN_IP4 New IPv4: $WAN_IP4 Error: $UPDATE4"
$NS "$NSHDR" "ERROR - Error returned in IPv4 update Old IPv4: $OLD_WAN_IP4 New IPv4: $WAN_IP4 Error: $UPDATE4"
;;
"")
$ERR_MSG "ERROR: Problem updating IPv4: Empty String. Old IPv4: $OLD_WAN_IP4 New IPv4: $WAN_IP4"
$NS "$NSHDR" "ERROR - Empty string returned during IPv4 update"
;;
* )
echo "$WAN_IP4" > "$HOME"/.wan_ip4.txt
$NOTICE_MSG "\"Updated IPv4 from $OLD_WAN_IP4 to $WAN_IP4. Result:$UPDATE4\""
$NS "$NSHDR" "IPv4 changed - Updated DNS record. Result:$UPDATE4"
;;
esac
else
$NOTICE_MSG "No IPv4 change"
$NS "$NSHDR" "No IPv4 change $WAN_IP4"
fi
else
# Notify if problem
$ERR_MSG "\"ERROR: IPv4 resolver invalid response. Response: $WAN_IP4\""
$NS "$NSHDR" "ERROR - IPv4 resolver invalid response. Response: $WAN_IP4"
fi
fi
fi
# Retrieve WAN IPv6
if [ "$CHECK_IP6" -eq 1 ] ; then
# Test for connecticvity to nameserver
# If successful, commands returns '0' and '1' if unsuccessful.
/usr/bin/nc -z resolver1.ipv6-sandbox.opendns.com 53 >/dev/null 2>&1
ONLINE6=$?
if [ "$ONLINE6" -ne 0 ] ; then
$ERR_MSG "No IPv6 connection"
$NS "$NSHDR" "ERROR IPv6 - No connection"
else
WAN_IP6=$(/usr/bin/dig +short -6 myip.opendns.com aaaa @resolver1.ipv6-sandbox.opendns.com)
# Quick pseudo-validation
VALID6=$(echo "$WAN_IP6" | /usr/bin/tr -dc \':\' | /usr/bin/wc -c)
if [ "$VALID6" -gt 3 ]; then
OLD_WAN_IP6=$(cat "$HOME"/.wan_ip6.txt)
# Update DNS record
if [ "$WAN_IP6" != "$OLD_WAN_IP6" ]; then
UPDATE6=$(curl -s -H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-X PUT --data \
"$(generate_json $WAN_IP6)" \
https://api.linode.com/v4/domains/$DOMAIN_ID/records/$RECORD_ID6)
# Check there's no error message in array
case "$UPDATE6" in
*$ERROR_STR* )
$ERR_MSG "Problem updating IPv6 Old IPv6: $OLD_WAN_IP6 New IPv6: $WAN_IP6 $UPDATE6"
$NS "$NSHDR" "ERROR - Error in IPv6 record update. Old IPv6: $OLD_WAN_IP6 New IPv6: $WAN_IP6 $UPDATE6"
;;
"")
$ERR_MSG "ERROR: Problem updating IPv6: Empty String. Old IPv6: $OLD_WAN_IP6 New IPv6: $WAN_IP6"
$NS "$NSHDR" "ERROR - Empty string returned during IPv6 update"
;;
* )
echo "$WAN_IP6" > "$HOME"/.wan_ip6.txt
$NOTICE_MSG "Updated IPv6 from $OLD_WAN_IP6 to $WAN_IP6. Result:$UPDATE6"
$NS "$NSHDR" "IPv6 changed - Updated DNS record. Result:$UPDATE6"
;;
esac
else
$NOTICE_MSG "No IPv6 change"
$NS "$NSHDR" "No IPv6 change $WAN_IP6"
fi
else
# Notify if problem
$ERR_MSG "IPv6 invalid response. Response: $WAN_IP6"
$NS "$NSHDR" "ERROR - IPv6: invalid response. Response: $WAN_IP6"
fi
fi
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment