Adventures in Rolling Your Own Router: Part VII

In my previous post, I set up an Ansible playbook to automate the configuration of my Debian router / firewall. In this post, I’ll update the playbook to include the installation of Pi-hole for DNS and DHCP.

Pi-hole Installer

At the time of writing this post, Pi-hole is at commit cbfb58f7. I’ll link to line numbers at this commit, but be aware the installer script will change over time.

The Pi-hole installer is meant to be piped directly from curl to bash.

curl -sSL https://install.pi-hole.net | bash

The installer then runs through a set of interactive prompts using whiptail, which allow you to choose an interface, set a static IP, enable or disable the web UI, etc.

The most concerning thing (for this particular router) the installer does is install dhcpcd5 which could potentially conflict with systemd-networkd. It also will attempt to set a static IP address on the interface you choose using a variety of methods, none of which are systemd-networkd.

Luckily for us, we can bypass the canned interface probing and the modification of network configuration using a “secret” argument. If we pass the –unattended argument to the installer, the conditional in the main function lets us bypass all the stuff we don’t want.

The only catch is that we need to set up a file which contains variables so that Pi-hole can configure itself. These variables would normally be determined by the interactive portion of the installer.

Installing Pi-hole with Ansible

The first step in automating the installation is codifying the variable file. This will be yet another template. The password hash is a dummy value; I’ll reset the password before use.

% cat templates/pihole.setupvars.j2
BLOCKING_ENABLED=true
DHCP_ACTIVE=true
DHCP_START={{ dhcp_range_start }}
DHCP_END={{ dhcp_range_end }}
DHCP_ROUTER={{ dhcp_router }}
DHCP_IPv6=
DHCP_rapid_commit=
PIHOLE_DOMAIN={{ local_domain_name }}
DHCP_LEASETIME=24
WEBPASSWORD=ea54268507ae0dac3da1d3057dbc5e870c6223caff753da02032bbc8f82c76b8
PIHOLE_INTERFACE={{ internal_ifname }}
IPV4_ADDRESS={{ ipv4_address_with_netmask }}
IPV6_ADDRESS=
PIHOLE_DNS_1=9.9.9.9
PIHOLE_DNS_2=149.112.112.112
QUERY_LOGGING=true
INSTALL_WEB_SERVER=true
INSTALL_WEB_INTERFACE=true
LIGHTTPD_ENABLED=true
CACHE_SIZE=10000

I have a couple of extra lines of configuration for dnsmasq, so I will add one additional template file for those.

% cat templates/dnsmasq.extras.j2
local=/{{ local_domain_name }}/
expand-hosts
read-ethers

Now, we can add some tasks to the playbook to install these template files.

    - name: Ensure Pi-hole configuration directory exists
      ansible.builtin.file:
        path: /etc/pihole
        state: directory
        owner: root
        group: root
        mode: '0755'

    - name: Configure Pi-hole installation variables
      ansible.builtin.template:
        src: templates/pihole.setupvars.j2
        dest: /etc/pihole/setupVars.conf
        owner: root
        group: root
        mode: '0644'
      vars:
        internal_ifname: "intern0"
        ipv4_address_with_netmask: "{{ internal_ip }}/{{ internal_netmask }}"
        local_domain_name: "{{ internal_domain }}"
        dhcp_range_start: "{{ dhcp_start }}"
        dhcp_range_end: "{{ dhcp_end}}"
        dhcp_router: "{{ internal_ip }}"

    - name: Ensure dnsmasq configuration directory exists
      ansible.builtin.file:
        path: /etc/dnsmasq.d
        state: directory
        owner: root
        group: root
        mode: '0755'

    - name: Add extra dnsmasq configuration
      ansible.builtin.template:
        src: templates/dnsmasq.extras.j2
        dest: /etc/dnsmasq.d/10-extra.conf
        owner: root
        group: root
        mode: '0644'
      vars:
        local_domain_name: "{{ internal_domain }}"

Now, we need to download the Pi-hole installer. I’ve decided to just grab the latest version of the installer rather than pin a particular commit. I’m hoping that the setupVars.conf format won’t change anytime soon. Because the installer script is available over HTTPS, I need to install the ca-certificates package before I can download the installer script with the get_url module.

    - name: Install CA certs
      ansible.builtin.apt:
        name: ca-certificates
        state: latest

    - name: Download Pi-hole installer
      get_url:
        url: https://install.pi-hole.net
        dest: /etc/pihole/basic-install.sh
        owner: root
        group: root
        mode: '0700'

Finally, I can install Pi-hole. I will also disable and mask the dhcpcd service that Pi-hole installed for me as we don’t want this to ever be inadvertently enabled.

    - name: Install Pi-hole
      command: /etc/pihole/basic-install.sh --unattended

    - name: Completely disable dhcpcd.service
      ansible.builtin.systemd:
        name: dhcpcd.service
        state: stopped
        enabled: no
        masked: yes

Two additional nftables rules need to be added to allow the use of DHCP and HTTP from the LAN interface.

        # allow DHCP from the LAN
        iifname {{ internal_ifname }} udp dport { 67,68 } accept

        # allow HTTP from the LAN for Pi-hole admin console
        iifname {{ internal_ifname }} tcp dport http accept

I’ll need to supply my /etc/ethers and /etc/hosts files, but besides that the router and it’s IaC are complete!

For reference, you can find the entire playbook on GitHub.