Scripting LanCache DNS updates with Pi-hole

Posted October 24, 2021

Last updated January 25, 2023 | 3d72f07


9 minute read

As I was setting up uklan’s LanCache, I found myself wanting to figure out how I could keep handing out my Pi-hole to clients on multiple VLANs, yet still use the LanCache setup on a (now unfortunately discontinued) Odroid HC1. By the way, for ARM infrastructure, I’m using the docker-compose file/Docker images from jrcichra/lancache-rpi. They create their own images and use them in the docker-compose file that work on ARM, and made it very easy to get going on the HC1.

Poking and around some, uklan’s cache-domains Github project showed up, and they have already done most of the hard work for us! This repo contains text files with all of the DNS names services like Steam, Epic, Blizzard, and other publishers use to push updates to their games. These are the lists of DNS entries that the DNS LanCache Docker container uses, and we want to make sure that our Pi-hole is giving out the same DNS results that clients would get if they were using the LanCache container for DNS.

Setting up our files 🔗

Grabbing the repo 🔗

The first thing we’ll want to do is clone the git repo. I’m going to be cloning it into my home directory, but anywhere should be fine as long as you’re able to remember the location.

Making copies of the files 🔗

Now this is one of those things where there are many ways to accomplish the same thing. In essence, what we want to do is be able to script grabbing these files as they may be updated every once in a while, and we want to not make git mad and tell us we can’t get the new files because changes have occurred. What we’re going to set up may or may not make it more complicated than it needs to be, but since it’ll all be invisible to us after getting it set up, I don’t think there’s too much of a problem with setting it up this way.

In order to not affect any of the files, we can copy the .txt files from our home directory (you can change ~/cache-domains to wherever the location is that you cloned the repo to) containing the domains we’ll need to resolve to the LanCache machine from Pi-hole:

mkdir /tmp/cache-domains/ && cp `find ~/cache-domains -name *.txt -o -name cache_domains.json` /tmp/cache-domains.

Now with the text files where we need them, we can copy the script that will give us our output that dnsmasq will want, along with the config file that the script will use to generate the dnsmasq files:

mkdir /tmp/cache-domains/scripts/ && cp ~/cache-domains/scripts/create-dnsmasq.sh /tmp/cache-domains/scripts/

Setting up our config.json file 🔗

Now is the fun part of generating our config file. You can see an example of this in the “scripts” folder inside of the repo, or in the Github repo.

Instead of copying the config from our local repo into the /tmp/cache-domains folder, I think it would be easier if we made our own script that generate the file based on what we need, since the example config file has some more advanced use cases. In my case, I’m just using one machine to cache everything instead of setting up separate Blizzard, Origin, Steam, etc. servers like you’re able to do with the LanCache project.

we’ll start by making a file in my home directory of config.json. From there, we’ll copy in the following text:

{
	"ips": {
		"generic":	"10.20.30.40"
	},
	"cache_domains": {
		"default": 	"generic"
	}
}

In the above snippet, you’ll want to take note of a few things. Like I mentioned earlier, I only have the one LanCache, so I’ll define it as "generic" in the top section where we’re defining our endpoints. In the second section where we’re directing everything to go, I’m going to tell everything to go to the generic server. Instead of defining specific services, which we could do by leaving in only the certain services we want to cache, I’m going to just leave the top line, setting “default” to use the “generic” IP. You’ll also want to change your IP address from “10.20.30.40” to whatever the local IP is of your LanCache machine.

If servers are added to the LanCache environment in the future or the IPs change, this file can be edited from the home directory to reflect these changes.

Now, we’ll copy the config into the /tmp/cache-domains/scripts/ folder so it can be used to generate our dnsmasq file:

cp ~/config.json /tmp/cache-domains/scripts/

Manually generating our dnsmasq files 🔗

Now that everything is in place, we’re ready to generate and copy our files for the Pi-hole to use!

We can start by generating the files. The script uses relative paths in variable definitions, so it’ll be easiest to browse to the folder to avoid errors and empty output. After we’re in our /tmp/cache-domains/scripts/ folder, we can run the create-dnsmasq.sh file with ./create-dnsmasq.sh, and then check in the folder that will be created in /tmp/cache-domains/scripts/output/.

Copying our files for Pi-hole to use 🔗

Now, as the script says, we need to copy our .conf files that were generated to the directory /etc/dnsmasq.d/ so the Pi-hole will recognize these entries and serve them correctly. We will need root privilege for this, so either have a user in the sudo group, or use the root account. This can be done with:

sudo cp -r /tmp/cache-domains/scripts/output/dnsmasq/*.conf /etc/dnsmasq.d/

We should now see a bunch of extra files with a recent modified time in the directory. This means success!

The last thing to do would be restarting the pihole-FTL service, which will get dnsmasq to recognize the new files in the /etc/dnsmasq.d/ directory. Do this by running sudo service pihole-FTL restart. You can check the status or any errors with sudo service pihole-FTL status.

Automating the process 🔗

Since there are many ways to accomplish this task, I’ll just choose one that seems easy! We’ll be using sudo crontab -e to set up a cron job as the root user to automate these updates.

All of the rest of our one-liners have been run under the context of a normal user, so we’ll collect them and modify them to use specific paths instead of relative (~, current user’s home directory) paths.

We’ll switch to the root user and create a script in /root named lancache-dns-updates.sh. Inside this script, we’ll put our previous lines we ran and combine them:

#!/bin/bash

### Set variables, change as necessary ###
# Username of the regular user you're using
SYSTEMUSER=oct8l
# Directory the git repository is synced to
GITSYNCDIR=/home/$SYSTEMUSER/cache-domains
# Your personalized config file from "Setting up our config.json file" step
DNSMASQCONFIG=/home/$SYSTEMUSER/config.json

# Create a new, random temp directory and make sure it was created, else exit
TEMPDIR=$(mktemp -d)

  if [ ! -e $TEMPDIR ]; then
      >&2 echo "Failed to create temp directory"
      exit 1
  fi

# Switch to the git directory and pull any new data
cd $GITSYNCDIR && \
 git fetch
  HEADHASH=$(git rev-parse HEAD)
  UPSTREAMHASH=$(git rev-parse master@{upstream})
  if [ "$HEADHASH" != "$UPSTREAMHASH" ]; then
      echo "Upstream repo has changed!" && git pull
     else
      echo "No changes to upstream repo!" && exit
  fi

# Copy the .txt files and .json file to the temp directory
cp `find $GITSYNCDIR -name "*.txt" -o -name cache_domains.json` $TEMPDIR

# Copy the create-dnsmasq.sh script to our temp directory
mkdir $TEMPDIR/scripts/ && \
  cp $GITSYNCDIR/scripts/create-dnsmasq.sh $TEMPDIR/scripts/ && \
  chmod +x $TEMPDIR/scripts/create-dnsmasq.sh

# Copy the config over
cp $DNSMASQCONFIG $TEMPDIR/scripts/

# Generate the dnsmasq files with the script
cd $TEMPDIR/scripts/ && \
  bash ./create-dnsmasq.sh > /dev/null 2>&1

# Copy the dnsmasq files
cp -r $TEMPDIR/scripts/output/dnsmasq/*.conf /etc/dnsmasq.d/

# Restart pihole-FTL
sudo service pihole-FTL restart

# Delete the temp directory to clean up files
trap "exit 1"           HUP INT PIPE QUIT TERM
trap 'rm -rf "$TEMPDIR"' EXIT

After creating this script, we’ll make sure it’s executable while running (while still at the root user’s prompt) chmod +x /root/lancache-dns-updates.sh.

We should now be able to run ./lancache-dns-updates.sh from the root user’s home directory and see all sorts of new files with new timestamps in the /etc/dnsmasq.d/ directory with ls -lath /etc/dnsmasq.d/. We should also be able to see the pihole-FTL service only running for a few seconds:

Adding the script to a cron job 🔗

Now is the last step where we’ll make sure this is scheduled on a regular basis so we can always have the newest domain names directed to our LanCache.

We can run ln -s /root/lancache-dns-updates.sh /etc/cron.daily/lancache-update when we’re logged in as the root user, and that will add the script to be run at the “daily” interval on the machine. To find when the “daily” cronjobs will run on your system, you can enter grep run-parts /etc/crontab and see the output of when these will run on your system.

Since this is also restarting the DNS resolver on Pi-hole, it may be best to set a manual time that this could run without causing issues with DNS resolution for a few seconds while dnsmasq reloads. Instead, we could run crontab -e while logged in as the root user and set up jobs to run as our root user at specified times. For example, we could run it at 3:30 AM every morning (according to the time set on the Pi) by adding this line to the end of our file:

30 3 * * * /bin/bash /root/lancache-dns-updates.sh

For a neat online tool, you can use crontab.guru to get a visual on what the five different times mean in a cron job.

The end 🔗

Now we should be all set up with getting daily updates on our Pi-hole with any changes to domains that CDNs are using for game updates, and have the requests sent automatically to our LanCache. This will allow us to use our Pi-hole as a single source of DNS in our network, while still getting the LanCache benefits.

Maybe in the future I’ll come back to the script and make it so it only restarts pihole-FTL if there are any new changes that were pulled down in the git clone section of the script, but for now this is my solution.