-
-
Save zsimic/c39dd9686c6d6b0d149a67ff23286b99 to your computer and use it in GitHub Desktop.
| #!/bin/bash | |
| # See https://gist.github.com/zsimic/c39dd9686c6d6b0d149a67ff23286b99 for docs on how to use | |
| # Note: you can invoke with LOGFILE= for troubleshooting | |
| [ -z "$LOGFILE" ] && LOGFILE=/var/log/messages | |
| if [ -z "$1" -a -f $LOGFILE ]; then # Pass any command line arg to avoid the logging redirect | |
| exec $0 run 1>> $LOGFILE 2>&1 | |
| fi | |
| function do_log { | |
| echo "$(date +'%h %e %T') $(hostname) linode-ddns: $@" | |
| } | |
| function usage { | |
| do_log "Usage error: $@" | |
| exit 1 | |
| } | |
| # Note: you can invoke with CFGFILE= for troubleshooting | |
| [ -z "$CFGFILE" ] && CFGFILE=~/.ssh/linode-ddns-cfg.sh | |
| CFGFOLDER=$(dirname $CFGFILE) | |
| IPFILE="$CFGFOLDER/.last-ip" # Allows to do nothing when IP didn't change, remove file to force re-run | |
| [ ! -f $CFGFILE ] && usage "Config file $CFGFILE does not exist" | |
| source $CFGFILE | |
| [ -z "$TOKEN" ] && usage "TOKEN not defined in $CFGFILE" | |
| [ -z "$RECORDS" ] && usage "RECORDS not defined in $CFGFILE" | |
| URL="https://api.linode.com/v4" | |
| H1="Content-type: application/json" | |
| H2="Authorization: Bearer $TOKEN" | |
| CURRENT_IP=$(ip address show eth0 | grep -Eo 'inet [0-9\.]+' | cut -d " " -f2) | |
| LAST_IP=$([ -f $IPFILE ] && head -1 $IPFILE) | |
| [[ $LAST_IP == $CURRENT_IP ]] && exit 0 | |
| DBG="" | |
| [ ! -d /config/scripts ] && DBG="echo" # Useful to debug, allows to perform actual REST call only on router | |
| for record in $RECORDS; do | |
| OUTPUT=$($DBG curl -s -H"$H1" -H"$H2" "$@" -XPUT $URL/domains/$record -d "{\"target\": \"$CURRENT_IP\"}") | |
| if [ $? -ne 0 ]; then | |
| do_log "Updating record $record failed: $OUTPUT" | |
| exit 1 | |
| fi | |
| done | |
| do_log "Home IP updated to $CURRENT_IP" | |
| [ ! -f $IPFILE ] && touch $IPFILE && chmod 0600 $IPFILE # chmod just because domainId is logged there... | |
| echo $CURRENT_IP > $IPFILE | |
| echo "# Updated on $(date) for $RECORDS" >> $IPFILE |
I recently upgraded to a UCG-MAX to get 2.5GB internet and had to make a few tweaks to get the script to work on it. As Linode is not supported on these 'Dream' machines as a DDNS provider. The big tweaks are WAN_PORT & SCRIPT_DIR variables since the edge router paths are not there. Also note that the path to the cfg file had to be absolute. /root/.ssh/linode-ddns-cfg.sh.
A critical tweak was adding a space after the -H for the curl headers: curl -s -H "$H1" -H "$H2" "$@" ... not sure why this was needed but this was part of way got it all working.
Verified on UGC-MAX running UNIFI OS 4.4.9
#!/bin/bash
# See https://gist.github.com/zsimic/c39dd9686c6d6b0d149a67ff23286b99 for docs on how to use
#2026-01-02 update variable for WAN port bc some dream devices have flexible wans with default of last port instead of port zero
# other updates for dream devices as tested on ucg-max
WAN_PORT='eth4';
SCRIPT_DIR='/opt/scripts/linode-ddns';
# edge routers usually used port zero for WAN
# uncomment these as needed
#WAN_PORT='eth0';
#SCRIPT_DIR='/config/scripts';
# Note: you can invoke with LOGFILE= for troubleshooting
[ -z "$LOGFILE" ] && LOGFILE=/var/log/messages
if [ -z "$1" -a -f $LOGFILE ]; then # Pass any command line arg to avoid the logging redirect
exec $0 run 1>> $LOGFILE 2>&1
fi
function do_log {
echo "$(date +'%h %e %T') $(hostname) linode-ddns: $@"
}
function usage {
do_log "Usage error: $@"
exit 1
}
# Note: you can invoke with CFGFILE= for troubleshooting
[ -z "$CFGFILE" ] && CFGFILE=/root/.ssh/linode-ddns-cfg.sh
CFGFOLDER=$(dirname $CFGFILE)
IPFILE="$CFGFOLDER/.last-ip" # Allows to do nothing when IP didn't change, remove file to force re-run
IPFILE6="$CFGFOLDER/.last-ip6" # Allows to do nothing when IP didn't change, remove file to force re-run
[ ! -f $CFGFILE ] && usage "Config file $CFGFILE does not exist"
source $CFGFILE
[ -z "$TOKEN" ] && usage "TOKEN not defined in $CFGFILE"
[ -z "$RECORDS" ] && usage "RECORDS not defined in $CFGFILE"
[ -z "$RECORDS6" ] && usage "RECORDS6 not defined in $CFGFILE"
URL="https://api.linode.com/v4"
H1="Content-type: application/json"
H2="Authorization: Bearer $TOKEN"
CURRENT_IP=$(ip address show "$WAN_PORT" | grep -Eo 'inet [0-9\.]+' | cut -d " " -f2)
CURRENT_IP6=$(ip address show "$WAN_PORT" | grep -Eo 'inet6 [0-9a-f\:]+' | cut -d " " -f2 | head -1)
LAST_IP=$([ -f $IPFILE ] && head -1 $IPFILE)
LAST_IP6=$([ -f $IPFILE6 ] && head -1 $IPFILE6)
[[ $LAST_IP == $CURRENT_IP && $LAST_IP6 == $CURRENT_IP6 ]] && exit 0
DBG=""
[ ! -d "$SCRIPT_DIR" ] && DBG="echo" # Useful to debug, allows to perform actual REST call only on router
if [[ $LAST_IP != $CURRENT_IP ]]; then
for record in $RECORDS; do
OUTPUT=$($DBG curl -s -H "$H1" -H "$H2" "$@" -XPUT $URL/domains/$record -d "{\"target\": \"$CURRENT_IP\"}")
if [ $? -ne 0 ]; then
do_log "Updating record $record failed: $OUTPUT"
exit 1
fi
done
fi
if [[ $LAST_IP6 != $CURRENT_IP6 ]]; then
for record in $RECORDS6; do
OUTPUT=$($DBG curl -s -H "$H1" -H "$H2" "$@" -XPUT $URL/domains/$record -d "{\"target\": \"$CURRENT_IP6\"}")
if [ $? -ne 0 ]; then
do_log "Updating record $record failed: $OUTPUT"
exit 1
fi
done
fi
do_log "Home IP updated to $CURRENT_IP $CURRENT_IP6"
[ ! -f $IPFILE ] && touch $IPFILE && chmod 0600 $IPFILE # chmod just because domainId is logged there...
echo $CURRENT_IP > $IPFILE
echo "# Updated on $(date) for $RECORDS" >> $IPFILE
#IP6 Version
[ ! -f $IPFILE6 ] && touch $IPFILE6 && chmod 0600 $IPFILE6 # chmod just because domainId is logged there...
echo $CURRENT_IP6 > $IPFILE6
echo "# Updated on $(date) for $RECORDS6" >> $IPFILE6
I've moved away from ubiquiti edge routers 😊 I'm using their "dream machine" now and don't want to publicize my IP anymore (reverse tunnel is way better😬)
interesting. Do you have any tutorials you can point me towards?
Yes, like https://www.youtube.com/watch?v=nmE28_BA83w or https://www.youtube.com/watch?v=B4pnGyvj2sM or https://www.youtube.com/watch?v=Cs8yOmTJNYQ - all very good channels, Techno Tim (that last one) probably explains things more in depth
Awesome! Thank you :)
I'm not super familiar with ipv6 yet, I thought this wasn't needed with ipv6 :)
Ie, addresses are so plentiful that they can be trivially static...
Routers may be intentionally configured to change their ipv6 every now and then, but that's more to avoid tracking (ie: for privacy reasons), so this addition could play well into that.