This work was originally published as an Inferentialist blog post.
Introduction
Last week, I went looking for a solution to the following problem: I have a single Linux server with a dynamically assigned IP address and I want to host several sites on this server. My registrar is Namecheap.com, and their advice is to use a Linux tool called ddclient.
Unfortunately, namecheap.com’s example doesn’t cover multiple hosts. A Google search pointed me to thornelabs.net, where the author describes a patch that can be applied to ddclient. Ddclient is written in Perl, so patching is a possibility, but one that feels a bit unsatisfactory.
Ddclient: The Post-execution Hook
Digging into the Perl script, I discovered that it provides a post-execution hook. This suggests a non-patch strategy: simply chain subsequent ddclient calls.
Here’s my base ddclient.conf file:
# Configuration file for ddclient generated by debconf
#
# /etc/ddclient.conf
use=web, web=dynamicdns.park-your-domain.com/getip
protocol=namecheap
login=inferentialist.com
postscript=/usr/sbin/ddpost # the post-execution hook
password=CD012345678901234567890123456789
@
ddpost
ddpost is the following python script, to be edited for your particular use case.
#!/usr/bin/python
import argparse
import tempfile
import os
import subprocess
import syslog
import sys
parser = argparse.ArgumentParser(description='run ddclient on secondary hosts')
parser.add_argument('ip_addr', help='script should be passed current ip address')
args = parser.parse_args()
ip_addr = args.ip_addr
host_passwords = {
'inferentialist.com': 'AA012345678901234567890123456789',
'dlennon.org' : 'BB012345678901234567890123456789'
}
host_subdomains = {
'inferentialist.com': ['blog', 'api'],
'dlennon.org': ['@']
}
config_template = """
use=ip
ip={ip_addr}
protocol=namecheap
login={host}
password={password}
{subdomain}
"""
ddconfig_template = """ddclient -file /tmp/{host}.conf -cache /tmp/{host}.cache -quiet"""
for host in host_passwords.keys():
password = host_passwords[host]
for subdomain in host_subdomains[host]:
config_name = "/tmp/{0}.conf".format(host)
cache_name = "/tmp/{0}.cache".format(host)
config = config_template.format(**locals())
with open(config_name, "w") as f:
f.write(config)
ddconfig_cmd = ddconfig_template.format(**locals())
sys_msg = None
try:
subprocess.check_call(ddconfig_cmd.split(' '))
sys_msg = "SUCCESS: [ddclient postscript] updating {subdomain}.{host}: good: IP address set to {ip_addr}".format(**locals())
except subprocess.CalledProcessError:
sys_msg = "FAILED: [ddclient postscript] updating {subdomain}.{host}".format(**locals())
syslog.syslog(sys_msg)
for fname in [config_name, cache_name]:
try:
os.unlink(fname)
except OSError:
pass
This will dynamically update four sites. inferentialist.com is handled by the initial call to ddclient. blog.inferentialist.com, api.inferentialist.com, and dlennon.org are updated via the ddpost script.
Note that each site specified in this pipeline should have a corresponding “A” record on namecheap.com. Moreover, subdomains should use the same password as “@” (e.g. root) domains.
The python script also pushes event records to /var/log/syslog.