Category Archives: Shell Scripting

CloudFlare and Dynamic DNS

Recently I changed ISPs.  This generally isn’t a big deal. It didn’t cause any difference in speed or  connection reliability, however my old ISP had, regardless of me not ordering one, given me a static IP address.  I’d taken advantage of this and assigned the IP to a DNS record to give me easy to remember access to my files when I’m away from home.  Anyway when I changed ISPs I discovered my new one changed my IP address every few days, which is an unfortunate thing to discover when you’re not at home.

I took a look at my router and found it only supported DynDns, which I then found out that, apart from to some DLink users, didn’t provide provide a free version of their service to the general public, and I don’t have a DLink router.

The next option was a free alternative to DynDns, there were several, and they all required running some kind of linux demon / cron script, which I would then have to alter my existing A record DNS entry to a CNAME to point to the new dynamic DNS record.

Before setting this up I thought I’d look into my existing DNS provider just incase there was any way of updating DNS records on the fly without having to maintain two separate DNS providers. I found that CloudFlare had an API, which I could update records with. The example provided was a Curl example, which made me wonder, surely I could just write a bash script to update the record with via a cron job. I didn’t want to have to install any major scripting language on my minimal Linux distro, excluding any php, perl etc. solution I’d found that worked with CloudFlare.

I checked CloudFlare’s API rate limiting and found it certainly wouldn’t be limiting for what I was trying to do (it’s approximately 1200 requests every 5 mins if you’re wondering). I then found MyExternalIP that could provide my external IP address with a rate limit that was easy to deal with of roughly 1/s, after which it was just a case of writing the bash script.

I worked a script out using Curl requests to get the information I needed including the specific id of the DNS record I was trying to update and then used sed to get the information I needed out each requests response. I made it compatible with the CloudFlareAPI call I was making that way any parameter passed to the call could be passed by a command line argument to the script itself.

The resultant script I added to my crontab to run every half hour.  Everything worked perfectly so I thought  I’d create a gist at github so others could take advantage of this:

#!/bin/sh
#
# CloudFlare Dynamic DNS
#
# Updates CloudFlare records with the current public IP address
#
# Takes the same basic arguments as A/CNAME updates in the CloudFlare v4 API
# https://www.cloudflare.com/docs/client-api.html#s5.2
#
# Use with cron jobs etc.
#
# e.g.
#
# manually run:
# cloudflare_dyn_dns.sh -key 404613183ab3971a2118ae5bf03d63e032f9e -email test@example.com -zone example.com -name extra
#
# cronjob entry to run every 5 minutes:
# */5 * * * * /path/to/cloudflare_dyn_dns.sh -key 404613183ab3971a2118ae5bf03d63e032f9e -email test@example.com -zone example.com -name extra >> /path/to/cloudflare_dyn_dns.log
#
# will both set the type A DNS record for extra.example.com to the current public IP address for user test@example.com with the provided API key

key=
email=
zone=
zone_id=
type=A
rec_id=
name=
content=
ttl=1
proxied=false

while [ "$1" != "" ]; do
    case $1 in
        -key )     shift
                   key=$1
                   ;;
        -email )   shift
                   email=$1
                   ;;
        -zone )    shift
                   zone=$1
                   ;;
        -zone_id ) shift
                   zone_id=$1
                   ;;
        -type )    shift
                   type=$1
                   ;;
        -rec_id )  shift
                   rec_id=$1
                   ;;
        -name )    shift
                   name=$1
                   ;;
        -content ) shift
                   content=$1
                   ;;
        -ttl )     shift
                   ttl=$1
                   ;;
        -proxied ) shift
                   proxied=$1
                   ;;
        * )        echo "unknown parameter $1"
                   exit 1
    esac
    shift
done

if [ "$content" = "" ]
then
    content=`curl -s http://myexternalip.com/raw`
    if [ "$content" = "" ]
    then
        date
        echo "No IP address to set record value with."
        exit 1
    fi
fi

if [ "$name" = "" ]
then
    echo "You must provide the name of the record you wish to change."
    exit 1
fi

if [ "$zone" = "" ]
then
    echo "You must provide the domain you wish to change."
    exit 1
fi

if [ "$name" = "$zone" ]
then
    hostname="$name"
else
    hostname="$name.$zone"
fi

command -v host > /dev/null 2>&1
if [ "$?" = "1" ]
then
    command -v nslookup > /dev/null 2>&1
    if [ "$?" = "1" ]
    then
        echo "Cannot find a way to check existing $type record for $hostname"
        exit 1
    fi
    existing_content=`nslookup -type=$type $hostname | awk -F 'Address: ' 'NR==6 { print $2 }'`
else
    existing_content=`host -t $type $hostname | sed -E 's/.+?\s+([^\s]+)$/\1/'`
fi

if [ "$content" = "$existing_content" ]
then
    echo "Existing record value $existing_content is the same as provided content $content. Exiting."
    exit
fi

if [ "$key" = "" ]
then
    echo "You must provide your user API token."
    exit 1
fi

if [ "$email" = "" ]
then
    echo "You must provide your user email."
    exit 1
fi

# Get the zone id for the entry we're trying to change if it's not provided
if [ "$zone_id" = "" ]
then
    zone_response_json=`curl -X GET "https://api.cloudflare.com/client/v4/zones?name=$zone" -H "X-Auth-Email: $email" -H "X-Auth-Key: $key" -H "Content-Type: application/json"`
    zone_id=`echo $zone_response_json | sed -E "s/.+\"result\":\[\{\"id\":\"([a-f0-9]+)\"[^\}]+$zone.+/\1/g"`
    if [ "$zone_id" = "" ]
    then
        echo "Cloudflare DNS Zone id could not be found, please make sure it exists"
        exit 1
    fi
fi

# Get the record id for the entry we're trying to change if it's not provided
if [ "$rec_id" = "" ]
then
    rec_response_json=`curl -X GET "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records?name=$hostname" -H "X-Auth-Email: $email" -H "X-Auth-Key: $key" -H "Content-Type: application/json"`
    rec_id=`echo $rec_response_json | sed -E "s/.+\"result\":\[\{\"id\":\"([a-f0-9]+)\"[^\}]+\"type\":\"$type\"[^\}]+$hostname.+/\1/g"`
    if [ "$rec_id" = "" ]
    then
        echo "Cloudflare DNS Record id could not be found, please make sure it exists"
        exit 1
    fi
fi

# Update the DNS record
update_response=`curl -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$rec_id" -H "X-Auth-Email: $email" -H "X-Auth-Key: $key" -H "Content-Type: application/json" --data "{\"id\":\"$rec_id\",\"type\":\"$type\",\"name\":\"$hostname\",\"content\":\"$content\",\"ttl\":$ttl,\"proxied\":$proxied}"`

success_val=`echo $update_response | sed -E "s/.+\"success\":(true|false).+/\1/g"`
if [ "$success_val" = "true" ]
then
    echo "Record Updated."
else
    echo "Record update failed."
    exit 1
fi

Installing MongoDB on a Linux distro using SystemD instead of inittab

While trying to get MongoDB working on a Fedora 15 test server I found that there was no way of installing the latest version with SystemD control due to a pre-existing known bug.

To Mongo’s credit there is a file provided in the source under rpm/init.d-mongod, however when it comes to this being used by SystemD, well it just errors.

This was frustrating, but as I was doing it on a VM, I did what any logical person would do. Installed Mongo from the package manager, took a copy of the required SystemD files, reverted to a snapshot, then installed the latest version of Mongo from source.

After this I adapted the mongod.service file to use the correct locations, ensured all the required directories, files and users were present on the system then started the service et voila, working latest version of MongoDB on Fedora 15 with SystemD

the two required files are:
/lib/systemd/system/mongod.service

[Unit]
Description=High-performance, schema-free document-oriented database
After=syslog.target network.target
 
[Service]
Type=forking
User=mongod
Group=mongod
PIDFile=/var/run/mongodb/mongod.pid
EnvironmentFile=/etc/sysconfig/mongod
ExecStart=/usr/local/bin/mongod $OPTIONS run
 
[Install]
WantedBy=multi-user.target

/etc/sysconfig/mongod

Or you can just use the RedHat distro based install script I created:

#!/bin/sh

# MongoDB Version
MONGODB_VER='2.2.2'

# Get all the dependencies up to date
yum -y update
yum -y install scons gcc-c++ glibc-devel

# Get the source
cd /usr/local/src/
wget http://downloads.mongodb.org/src/mongodb-src-r$MONGODB_VER.tar.gz
tar xfz mongodb-src-r$MONGODB_VER.tar.gz
cd mongodb-src-r$MONGODB_VER

# Compile and Install
scons all
scons install

# Create the SystemD dependant files
echo '[Unit]
Description=High-performance, schema-free document-oriented database
After=syslog.target network.target
 
[Service]
Type=forking
User=mongod
Group=mongod
PIDFile=/var/run/mongodb/mongod.pid
EnvironmentFile=/etc/sysconfig/mongod
ExecStart=/usr/local/bin/mongod $OPTIONS run
 
[Install]
WantedBy=multi-user.target' > /lib/systemd/system/mongod.service

echo 'OPTIONS="--quiet -f /etc/mongod.conf"' > /etc/sysconfig/mongod

# Setup the required user and group
useradd -r -U mongod

# Setup the required directories
mkdir -p /var/run/mongodb/
mkdir -p /var/log/mongo/
mkdir -p /var/lib/mongo/
chown mongod:mongod /var/run/mongodb/
chown mongod:mongod /var/log/mongo/
chown mongod:mongod /var/lib/mongo/
chmod 0755 /var/log/mongo/
chmod 0755 /var/run/mongodb/
chmod 0755 /var/lib/mongo

# Start the new service and enable it on boot
systemctl --system daemon-reload
systemctl start mongod.service
systemctl enable mongod.service

Installing the default Linux Kernel on a Linode CentOS 6 box

While creating the new web-server for my employers, to replace a Fedora 10 box which gets no security updates, I needed to compile some software from source, meaning I needed the kernel sources.

Since I couldn’t easily obtain these I needed to install the Kernel provided by the distribution rather than the more recent kernel provided by Linode themselves.

The Linode Library provided a way of doing this for CentOS 5 but not for CentOS 6, thus I adapted the provided script for v5 into one that works with CentOS 6 et voila, distro provided kernel.

Here’s the full source available as a gist on github:

### Starting from a fresh CentOS 6 or newer Linode
### Enable the native kernel to boot from pvgrub
### It will autoconfigure itself with each yum update.
### This is adapted from a previous script for CentOS 5.5 found here:
### http://www.linode.com/docs/assets/542-centos5-native-kernel-selinux-enforcing.sh
### Provided via the linode wiki
### https://www.linode.com/docs/tools-reference/custom-kernels-distros/run-a-distributionsupplied-kernel-with-pvgrub#centos-5
### Provided without warranty, although since it should only be run
### on first box build if your box gets broken simply rebuild it

mkdir /boot/grub/

DISTRO_PLATFORM=`uname -p`
AWK_VERSION_MATCH="{if(\$1==\"kernel.$DISTRO_PLATFORM\") print \$2}"
KERNEL_VERSION=`yum -q list kernel | awk "$AWK_VERSION_MATCH"`

### Write template grub.conf
cat > /boot/grub/grub.conf << EOF
# grub.conf generated by anaconda
#
# Note that you do not have to rerun grub after making changes to this file
# NOTICE:  You have a /boot partition.  This means that
#          all kernel and initramfs paths are relative to /boot/, eg.
#          root (hd0)
#          kernel /boot/vmlinuz-version ro root=/dev/xvda
#          initrd /boot/initramfs-version.img
#boot=/dev/xvda
default=0
timeout=3
title CentOS ($KERNEL_VERSION.$DISTRO_PLATFORM)
        root (hd0)
        kernel /boot/vmlinuz-$KERNEL_VERSION.$DISTRO_PLATFORM root=/dev/xvda
        initrd /boot/initramfs-$KERNEL_VERSION.$DISTRO_PLATFORM.img
EOF

ln -s /boot/grub/grub.conf /boot/grub/menu.lst
yum -y install kernel
if [ $? -ne 0 ]; then
    echo "ERROR aborting..."
    exit 1
fi