Skip to content

Instantly share code, notes, and snippets.

@cvn
Last active Oct 15, 2021
Embed
What would you like to do?
A script for updating dynamic DNS using Opalstack's API
#!/usr/bin/env python3
# update_ip.py - A script for updating dynamic DNS using Opalstack's API
# by Chad von Nau
#
# * Python 2 and 3 compatible.
# * Supports IPv4 and IPv6 addresses.
# * Records the last IP in a JSON file and only calls the API if the IP has changed.
#
# Instructions:
#
# 1. Create a new DNS Record on Opalstack, with the domain you want to use.
# 2. Create an Opalstack API token, if you don't already have one.
# 3. Create a PHP app on Opalstack, with this as the index.php:
# <?php echo $_SERVER['REMOTE_ADDR']; ?>
# 4. Set the variables below.
# 5. Run this script, pass the domain as an argument, ex:
# python update_ip.py my-domain.com
# 6. Optionally, automate this to run periodically. I run it every hour using cron.
# I also comment out `else: print('IP not updated...` at the end, to avoid excess logging.
# Variables
token = 'YOUR_OPALSTACK_API_TOKEN'
ip_service = 'http://YOUR_PHP_APP_URL' # expect service to return IP address as string
# Imports
import os
import sys
import json
try:
# python 3
from urllib.request import Request, urlopen
except ImportError:
# python 2
from urllib2 import Request, urlopen
# Get domain name from stdin or interactively
if len(sys.argv) > 1:
domain = sys.argv[1]
else:
domain = raw_input("domain name: ")
# Get IP address from remote service
try:
current_ip = urlopen(ip_service).read().decode('utf-8')
except:
exit('Could not determine IP. Not updating.')
if not current_ip:
exit('No IP returned by "{}". Not updating.'.format(ip_service))
# Get file path of info file
filename = 'info-{}.json'.format(domain)
path = os.path.dirname(os.path.realpath(__file__))
# strip out any nasty characters
# http://stackoverflow.com/questions/7406102/create-sane-safe-filename-from-any-unsafe-string
keep_characters = ('-','.','_')
filename = ''.join(c for c in filename if c.isalnum() or c in keep_characters).rstrip()
info_file = '{}/{}'.format(path, filename)
# Load data from info file
if os.path.isfile(info_file):
with open(info_file, 'r') as f:
info = json.load(f)
else:
info = {}
# Define util function
def api_request(url, data=None):
headers = {
'Authorization': "Token {}".format(token),
'Content-Type': 'application/json',
}
data = json.dumps(data).encode() if data else None
req = Request(url, data, headers=headers)
resp = urlopen(req).read()
return json.loads(resp)
# Update record if necessary
if info.get('content') != current_ip:
dns_uuid = info.get('id')
domain_uuid = info.get('domain')
# Get UUIDs if necessary
if not (dns_uuid and domain_uuid):
dns_list = api_request('https://my.opalstack.com/api/v1/dnsrecord/list/?embed=domain')
item = next((item for item in dns_list if item.get('domain').get('name') == domain), None)
if not item:
exit('No DNS record found for "{}"'.format(domain))
dns_uuid = item.get('id')
domain_uuid = item.get('domain').get('id')
# Set the IP address on Opalstack.
update_data = {
"id" : dns_uuid,
"domain" : domain_uuid,
"type" : "AAAA" if ':' in current_ip else "A",
"content" : current_ip,
"priority" : 10,
"ttl" : 3600,
}
dns_update = api_request('https://my.opalstack.com/api/v1/dnsrecord/update/', [update_data])
# Write response to file
with open(info_file, 'w') as f:
json.dump(dns_update[0], f, indent=2)
print('IP updated to %s' % current_ip)
else:
print('IP not updated (%s)' % current_ip)
@mightyohm

This comment has been minimized.

Copy link

@mightyohm mightyohm commented Oct 12, 2021

Looks like this script is broken as of this week (the v0 API is now returning 404).

@cvn

This comment has been minimized.

Copy link
Owner Author

@cvn cvn commented Oct 13, 2021

I updated the script to use the v1 API. Thanks for the heads up, @mightyohm.

@mightyohm

This comment has been minimized.

Copy link

@mightyohm mightyohm commented Oct 13, 2021

The new script appears to be working. Thanks for updating it!

@elromanos

This comment has been minimized.

Copy link

@elromanos elromanos commented Oct 14, 2021

You should consider lowering the TTL value to the lowest possible Opalstack allows, especially if you are using this to self-host services in your home.

The lower the TTL, the faster the change will propagate across the internet.

@cvn

This comment has been minimized.

Copy link
Owner Author

@cvn cvn commented Oct 15, 2021

That’s not a bad idea, @elromanos. The current value fits my use case, and it’s easy enough to change, so I’m going to leave the script as-is for now.

Another interesting alternative is to get the TTL value from the existing DNS record, instead of setting it explicitly in the script. This would allow you to have different TTL values for each record, and manage them all in the Opalstack UI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment