Did you know that you can navigate the posts by swiping left and right?
Update CloudFlare DDNS with systemd timers
13 Jan 2016
. configuration . Comments
CloudFlare As a Dyanmic DNS provider
CloudFlare has some great features for Web Applications (caching, SSL termination, Reports), but you can also use it
as a Dyanmic DNS server thanks to its great API. suppose you have a webserver, or VPN/SSH endpoint on your internet connection,
But, your provider doesnt give you a static IP. How do you manage the DNS record?
Well you could sign up a DDNS service or manually update the record…
Well, you could also use CF for DNS hosting and use their API to update your record.
To keep our record up-to-date, we need to periodically update the record, incase our external IP address changes.
in the past, we used cron jobs to achieve such tasks, a simple one line to call a command on predefined intervals.
Instead of cron, we can use the power of systemds’ timers to achieve the same result. If your OS supports systemd, I
would recommend this over a cron job.
Why Systemd
systemd is the replacement to the older SysVInit system, systemd adds much more functionality, specifically,
it has the ability to replace cron jobs, which is what we are interested in here.
My Current server OS (Debian) supports systemd out of the box, and any OS with an update in the past year should
also ship with systemd (most popular distros appear to, or will shortly).
I chose not to go down the ddclient patching path (which is widely blogged and documented) as its messy and mucks with core packages, meaning security and version updates might break the patch.
Lastly, I had a python script inside cron which did the DNS updates, but this is broken (not sure why) and my python isnt great so I cant be bothered to debug it.
How does it work
Before I go into the full step by step guide, i’ll briefly go over the process and what I want to archeve by this process.
We have a simple bash script which will update our CloudFlare DNS A record with our external IP address. This script will be
executed every 5mins after boot using a systemd timer.
The bash script will use a HTTP POST to the cloudflare API containing our record ID, API key, DOMAIN, Record type, Record Name and IP Address.
Setup and Installation
WARNING: Please carefully read ALL commands and scripts posted before running them, Please DO NOT blindly copy and past. I will not be responsible for data loss or OS issues.
Requirements
There are a few requirements, to get this going. You will need the following:
- A CloudFlare account (free is fine)
- Your DNS hosted with CloudFlare
- Your CloudFlare API Key
- systemd
- An exsisting DNS record which will recieve the updates (can have a dummy IP)
- sudo access
If you have all that… carry on.
The bash script
There are two parts to geting this working, the first, is getting your exsisting record details with an API call, then we need to use of those
details to fill in the bash script.
Lets get the information we need from CloudFlare, run the following curl
command to see all your DNS information.
curl https://www.cloudflare.com/api_json.html -d 'a=rec_load_all' \
-d 'tkn=YOUR_CloudFlare_API_Key' \
-d 'email=YOUR_CloudFlare_Email_Address' \
-d 'z=YOUR_DOMAINNAME (eg example.com)'
This command will return JSON, so use a JSON viewer to make it easy to read. We are interested in rec\_id
and type
fields for the record.
In this example, my rec_id is: 123456 and record type is A and the name is www.example.com
Lets create a bash script to make the update, create /usr/local/bin/update-cfddns.sh
with the following content:
#!/bin/sh
NEW_IP=`wget -O - -q http://ifconfig.me/ip`
CURRENT_IP=`cat /var/tmp/current_ip.txt`
if [ "$NEW_IP" = "$CURRENT_IP" ]
then
echo "No Change in IP Adddress"
else
curl https://www.cloudflare.com/api_json.html \
-d 'a=rec_edit' \
-d 'tkn=YOUR_API_KEY' \
-d '[email protected]' \
-d 'z=example.com' \
-d 'id=123456' \
-d 'type=A' \
-d 'name=www.example.com' \
-d 'ttl=1' \
-d "content=$NEW_IP"
echo $NEW_IP > /var/tmp/current_ip.txt
fi
Dont forget to chmod +x
the file.
Systemd timers
Systemd use two units to create a timer, Think of a unit as a action (or file), one unit defines the timer and its intervals, the
second is the service (or action) we want the timer to trigger. The service will have the bash script referenced.
There are two ways to this, you could create a generic timer with a target, then service units can listen for that target and run. Alternatively, we can
write a specfic timer and call the service from that. Im going to the latter of the two.
Setup Units
Create A timer file into /etc/systemd/system/CfDDNS-timer.timer
with the following:
[Unit]
Description=CfDDNS DNS Update Timer
[Timer]
OnBootSec=5min
OnUnitActiveSec=5min
Unit=CfDDNS.service
[Install]
WantedBy=basic.target
What do these lines do?
-
Description: A short desc about what the timer does.
-
OnBootSec: How long after boot to run the timer for the first time.
-
OnUnitActiveSec: Interval between each run (in this case, run the timer every 5mins).
-
Unit: What to do when the timer runs.
-
WantedBy: Dependencies to help with service ordering. (theres no point running this without network)
Next, create a service into /etc/systemd/system/CfDDNS.service
with the following:
[Unit]
Description=Update CfDDNS DNS Record
Wants=CfDDNS-timer.timer
[Service]
ExecStart=/usr/local/bin/update-cfddns.sh
What have we set in this service?
-
Description: A short desc about what the service does.
-
Wants: A Dependency that requires our timer to be active.
-
ExecStart: What to run when this unit is executed. (this is our actual script from before)
Setup systemd
Now we have our timer and our code to execute, systemd wont just automatically run this timer, we need to enable it and start it.
To do that, we just system systemctl
:
sudo systemctl enable CfDDNS-timer.timer
sudo systemctl start CfDDNS-timer.timer
Testing and Verification
Hopefully the systemd configuration and units where setup without an issue, we can verify their status using systemctl
commands.
We are going to check the status two ways, one is list out the timers, useful to check we are infact running every 5mins, and that we call
the CfDDNS.service.
The Second one will provide more verbose output using the status param.
1: View the systemd timers
sudo systemctl list-timers
NEXT LEFT LAST PASSED UNIT ACTIVATES
Fri 2016-01-15 09:10:47 NZDT 3min 12s left Fri 2016-01-15 09:05:47 NZDT 1min 47s ago CfDDNS-timer.timer CfDDNS.service
n/a n/a Tue 2016-01-12 06:45:52 NZDT 3 days ago ureadahead-stop.timer ureadahead-stop.service
2: view the timer unit status:
sudo systemctl status CfDDNS-timer.timer
● CfDDNS-timer.timer - CfDDNS DNS Update Timer
Loaded: loaded (/etc/systemd/system/CfDDNS-timer.timer; enabled; vendor preset: enabled)
Active: active (waiting) since Tue 2016-01-12 09:45:15 NZDT; 2 days ago
Jan 12 09:45:15 elmo systemd[1]: Started CfDDNS DNS Update Timer.
Jan 12 09:45:30 elmo systemd[1]: Started CfDDNS DNS Update Timer.
Hopefully this has been useful and a good example on how to write systemd timers and units, these are also available on github in my dotfiles repo if you want
the up-to-date version (incase I made changes and didnt update my blog post).