[HowTo] Free .tk domain name that always points to your Pi

I setup something similar with my own custom domain, using a free Cloudflare account to get the DDNS functionality. I looked at the packages available for dynamic DNS functionality but ended up modifying code I found online and putting together a shell script that does everything for me - this just gets run with cron. I think I might have needed to install a package to get the dig command to work (can’t recall).

YMMV, the code is not well documented, probably breaks all proper conventions, and if this breaks your setup I take no responsibility, but for what it’s worth, my shell script looks like this:

#!/bin/bash

# Step 1: Fill in EMAIL, TOKEN, ZONE_NAME (domain) and REC_NAME (subdomain). Your token is here: https://www.cloudflare.com/my-account
# Step 2: Create an A record on Cloudflare with the subdomain you chose
# Step 3: Run "./ddns.sh -l" to get the ZONE_ID and REC_ID for the domain and subdomain. 
#         Fill in REC_ID and ZONE_ID below
# Step 4: Run "./ddns.sh". It should tell you that record was updated or that it didn't need updating.
# Step 5: Run regularly with cron.
#         */5 * * * * /path/to/ddns.sh > /path/to/log/file

# Cloudflare parameters
EMAIL='youremailaddress'
TOKEN='yourCFtoken'
ZONE_NAME='yourtopleveldomain'
ZONE_ID='yourCFzoneid'
REC_NAME='yourCFsubdomain'
REC_ID='yourCFsubdomainrecordID'

# If specified, obtain and print records then exit
if [ "$1" == "-l" ]; then
  ZONE_ID=$( curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$ZONE_NAME" \
    -H "X-Auth-Email: $EMAIL" \
    -H "X-Auth-Key: $TOKEN" \
    -H "Content-Type: application/json" \
    | grep -Po '(?<="id":")[^"]*' \
    | head -1 )
  REC_ID=$( curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?name=$REC_NAME" \
    -H "X-Auth-Email: $EMAIL" \
    -H "X-Auth-Key: $TOKEN" \
    -H "Content-Type: application/json" \
    | grep -Po '(?<="id":")[^"]*' )
  echo Zone Name: $ZONE_NAME
  echo Zone ID: $ZONE_ID
  echo Record Name: $REC_NAME
  echo Record ID: $REC_ID
  exit 0
fi

# If specified, obtain and print A record then exit
if [ "$1" == "-a" ]; then
  CFIP=$( curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?type=A&name=$REC_NAME&match=all" \
    -H "X-Auth-Email: $EMAIL" \
    -H "X-Auth-Key: $TOKEN" \
    -H "Content-Type: application/json" \
    | grep -Po '(?<="content":")[^"]*' )
  echo Record Name: $REC_NAME
  echo A Record: $CFIP
  exit 0
fi

# Resolve current IP address via DNS
IP=$(dig +short myip.opendns.com @resolver1.opendns.com)
if ! [[ $IP =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
  [ "$1" != "-s" ] && echo "Cannot determine public IP address, exiting"
  exit 1
fi

# Resolve domain to IP address
DNSIP=$(host $REC_NAME)

# Exit if domain cannot be resolved
if [ $? -eq 1 ]; then
  [ "$1" != "-s" ] && echo "Cannot resolve $REC_NAME, exiting"
  exit 1
fi

DNSIP=$(echo $DNSIP | awk '{ print $NF }')

# No action required if current IP matches resolved IP
if [ "$IP" == "$DNSIP" ]; then
  [ "$1" != "-s" ] && echo "$REC_NAME resolves to public IP ($IP), exiting"
  exit 0
fi

# At this point public IP and resolved IP records exist and do not match
[ "$1" != "-s" ] && echo "Public IP ($IP) does not match DNS record ($DNSIP), checking Cloudflare"

# Retrieve the Cloudflare A record IP address
CFIP=$( curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?type=A&name=$REC_NAME&match=all" \
  -H "X-Auth-Email: $EMAIL" \
  -H "X-Auth-Key: $TOKEN" \
  -H "Content-Type: application/json" \
  | grep -Po '(?<="content":")[^"]*' )

# If Cloudflare IP record matches public IP then there must be a DNS issue, no update required
if [ "$CFIP" == "$IP" ]; then
  [ "$1" != "-s" ] && echo "Cloudflare IP ($CFIP) matches public IP ($IP), exiting"
  exit 0
fi

# At this point the public IP does not match the IP address stored in Cloudflare
[ "$1" != "-s" ] && echo "Cloudflare IP ($CFIP) does not match public IP ($IP), update required"
[ "$1" != "-s" ] && echo "Setting Cloudflare DNS record to public IP"

# Set Cloudflare DNS record
RESULT=$( curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$REC_ID" \
  -H "X-Auth-Email: $EMAIL" \
  -H "X-Auth-Key: $TOKEN" \
  -H "Content-Type: application/json" \
  --data "{\"id\":\"$ZONE_ID\",\"type\":\"A\",\"name\":\"$REC_NAME\",\"content\":\"$IP\"}")

if [[ $RESULT == *"\"success\":false"* ]]; then
    echo "API update failed. Dumping results:"
    echo -e $RESULT
    /home/osmc/push.sh "Cloudflare API update failed, check log for details"
    exit 1
else
    echo "API update successful"
    /home/osmc/push.sh "DNS records have been updated, public IP is $IP"
    exit 0
fi

It might not be pretty, but I have had this script running every 5 minutes for the past 6 months and it’s worked perfectly, including the basic error catching that I built into it.

Each time the script updates my DDNS records it also pushes a notification to my phone - details on how that was set up is in my HowTo.