Home Home Grown Dynamic DNS
Post
Cancel

Home Grown Dynamic DNS

My setup to do dynamic DNS updates to Cloudflare. The package available are too heavyweight or don’t do what I want; it’s simple enough to write my own.

Create script /usr/local/bin/cloudflare-update.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#!/usr/bin/env bash

# Copyright 2022 Mike Carlton
#
# Released under terms of the MIT License:
#   http://www.opensource.org/licenses/mit-license.php

# https://api.cloudflare.com/#dns-records-for-a-zone-create-dns-record

if hash jq 2>/dev/null; then
    :           # all good
else
    echo 'Please install 'jq': https://stedolan.github.io/jq/'
    exit 1
fi

if [ -z "$CLOUDFLARE_API_TOKEN" ] ; then
    echo 'Please define 'CLOUDFLARE_API_TOKEN' environment var'
    echo 'Create token if needed: https://dash.cloudflare.com/profile/api-tokens'
    exit 1
fi

api_token="$CLOUDFLARE_API_TOKEN"
domain="carltons.us"
records=( "home.$domain" "unifi.$domain" )

ipv4_re='^([0-9]{1,3}\.){3}[0-9]{1,3}$'         # simple, but good enough
current_ip=$(curl -s -4 icanhazip.com)
if ! [[ $current_ip =~ $ipv4_re ]]; then
    echo "Unable to get current ip: got '$current_ip'"
    exit 1
fi

zone=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$domain" \
            -H "Authorization: Bearer $api_token" -H 'Content-Type:application/json' \
       | jq -r '.result[0].id')

for record in "${records[@]}" ; do
    dns=( $(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone/dns_records?type=A&name=$record" \
                 -H "Authorization: Bearer $api_token" -H 'Content-Type:application/json' \
            | jq -r '.result[0].content,.result[0].id') )
    dns_ip=${dns[0]}
    dns_id=${dns[1]}
    if ! [[ $dns_ip =~ $ipv4_re ]]; then
        echo "Unable to get DNS ip: got '$dns_ip'"
        exit 1
    fi

    if ! [[ "$dns_ip" == "$current_ip" ]]; then
        response=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone/dns_records/$dns_id" \
                        -H "Authorization: Bearer $api_token" -H 'Content-Type:application/json' \
                        --data '{ "type": "A", "name": "'${record}'", "content": "'${current_ip}'", "ttl": 1, "proxied": true }')
        if [[ $(echo $response | jq -r .success) == "true" ]]; then
            >&2 echo "Updated Cloudflare DNS record $record: $dns_ip to $current_ip"
        else
            message=$(echo $response | jq -r '.errors[0].message')         # just report the first error
            >&2 echo "Fail to update Cloudflare DNS record $record: $dns_ip to $current_ip: $message"
            exit 1
        fi
    fi
done

exit 0

Create /etc/cron.d/cloudflare-ddns with

1
2
3
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
*/5 * * * * root [ -x /usr/local/bin/cloudflare-update.sh ] && . /root/.cloudflare && /usr/local/bin/cloudflare-update.sh

Create /root/.cloudflare

1
CLOUDFLARE_API_TOKEN=<TOKEN>
This post is licensed under CC BY 4.0 by the author.