diff --git a/README.md b/README.md index 6260c3c..56f20b8 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ These roles are included: The options prefixed with `pihole_ftl_` are described in the official [Pi-hole FTL Configuration](https://docs.pi-hole.net/ftldns/configfile/) - The [`pihole_ha_mode`](inventory.yaml#L24) option is used to switch between HA or Single mode to determine the IPv4/IPv6 addresses for the Pi-hole services (bind IPs for Web/DNS, pi.hole DNS record) and is enabled by default. ⚠️ Disable this if you don't intend to deploy a HA setup with keepalived. + - The [`dhcp_server`](inventory.yaml#L28) option and other options prefixed with `dhcp_` are for configuring Pihole's optional DHCP support. This is disabled by default. If you enable the DHCP server, consider a shorter sync cron interval [here](master/roles/sync/tasks/main.yaml#L33) to catch changes in dhcp leases. ## `update-pihole.yaml` This playbook is for subsequent runs after the `bootstrap-pihole.yaml` playbook was run at least once. diff --git a/inventory.yaml b/inventory.yaml index 3ab3d62..8613e7e 100644 --- a/inventory.yaml +++ b/inventory.yaml @@ -25,3 +25,9 @@ all: pihole_vip_ipv4: "192.168.178.10/24" pihole_vip_ipv6: "fd00::10/64" sync_target: "{{ pihole_vip_ipv4.split('/')[0] }}" + dhcp_server: "false" # If enabled, consider a shorter sync interval to catch dhcp leases (see README) + dhcp_start: "192.168.178.100" + dhcp_end: "192.168.178.250" + dhcp_router: "192.168.178.1" + dhcp_domain: "local" + dhcp_lease_time: "24" # In hours \ No newline at end of file diff --git a/roles/keepalived/files/check_pihole.sh b/roles/keepalived/files/check_pihole.sh deleted file mode 100644 index e265101..0000000 --- a/roles/keepalived/files/check_pihole.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -[ "$(docker inspect -f "{{.State.Health.Status}}" pihole)" = "healthy" ] diff --git a/roles/keepalived/tasks/main.yaml b/roles/keepalived/tasks/main.yaml index efd2d1c..dd5f9ba 100644 --- a/roles/keepalived/tasks/main.yaml +++ b/roles/keepalived/tasks/main.yaml @@ -14,10 +14,10 @@ force_apt_get: yes name: keepalived -- name: Copy check_pihole.sh - copy: +- name: Configure check_pihole.sh + template: dest: /etc/keepalived/check_pihole.sh - src: check_pihole.sh + src: check_pihole.j2 mode: 0755 - name: Configure keepalived diff --git a/roles/keepalived/templates/check_pihole.j2 b/roles/keepalived/templates/check_pihole.j2 new file mode 100644 index 0000000..abe3f85 --- /dev/null +++ b/roles/keepalived/templates/check_pihole.j2 @@ -0,0 +1,38 @@ +#!/bin/bash +set -e + +## This script checks the health of the pihole container and enables/disables DHCP as needed. +PIHOLE_DIR="/home/{{ ansible_user }}/pihole" +[ "{{ dhcp_server }}" = "true" ] && DHCP_ENABLED=0 || DHCP_ENABLED=1 # enabled=0, disabled=1 +VIP="{{ pihole_vip_ipv4 }}" +DHCP_START="{{ dhcp_start }}" +DHCP_END="{{ dhcp_end }}" +DHCP_GATEWAY="{{ dhcp_router }}" +DHCP_DOMAIN="{{ dhcp_domain }}" +DHCP_LEASE_TIME="{{ dhcp_lease_time }}" + +# Check if the pihole container is healthy (0) or not (1). +[ "$(docker inspect -f "{{ '{{' }}.State.Health.Status{{ '}}' }}" pihole)" = "healthy" ] && HEALTHY=0 || HEALTHY=1 + +# If the container is not healthy, exit 1. +if [ ${HEALTHY} -eq 1 ]; then + exit ${HEALTHY} +fi + +# If DHCP is not enabled, or we don't own the virtual IP +if [ ${DHCP_ENABLED} -eq 1 ] || ! /usr/sbin/ip a |grep -q ${VIP} ; then + # Ensure DHCP is disabled. + if [ -f ${PIHOLE_DIR}/dnsmasq.d/02-pihole-dhcp.conf ]; then + /usr/bin/docker exec -d pihole /usr/local/bin/pihole -a disabledhcp + fi + # Exit with the health status of the container (0). + exit ${HEALTHY} +fi + +# Ensure DHCP is enabled. +if ! [ -f ${PIHOLE_DIR}/dnsmasq.d/02-pihole-dhcp.conf ]; then + /usr/bin/docker exec -d pihole /usr/local/bin/pihole -a enabledhcp "${DHCP_START}" "${DHCP_END}" "${DHCP_GATEWAY}" "${DHCP_LEASE_TIME}" "${DHCP_DOMAIN}" +fi + +# Exit with the health status of the container (0). +exit ${HEALTHY} diff --git a/roles/pihole/tasks/main.yaml b/roles/pihole/tasks/main.yaml index 0d2b102..69e8323 100644 --- a/roles/pihole/tasks/main.yaml +++ b/roles/pihole/tasks/main.yaml @@ -21,6 +21,27 @@ execution_mode: "HA setup with keepalived" when: pihole_ha_mode +- name: Create dnsmasq.d directory for DHCP config (HA mode) + file: + path: "/home/{{ ansible_user }}/pihole/dnsmasq.d" + owner: "root" + group: "root" + state: directory + mode: 0755 + when: + - pihole_ha_mode + - dhcp_server + +- name: Set DHCP to give out HA mode IP + copy: + dest: "/home/{{ ansible_user }}/pihole/dnsmasq.d/03-pihole-dhcp-static-dns.conf" + owner: "root" + group: "root" + content: dhcp-option=6,{{pihole_local_ipv4}} + when: + - pihole_ha_mode + - dhcp_server + - name: Determine Pi-hole host IPs (single mode) set_fact: pihole_local_ipv4: "{{ ansible_host }}" @@ -44,6 +65,11 @@ REV_SERVER_TARGET: "{{ pihole_rev_server_target }}" REV_SERVER_CIDR: "{{ pihole_rev_server_cidr }}" FTLCONF_MAXDBDAYS: "{{ pihole_ftl_max_db_days }}" + DHCP_ACTIVE: "{{ dhcp_server }}" + DHCP_START: "{{ dhcp_start }}" + DHCP_END: "{{ dhcp_end }}" + DHCP_ROUTER: "{{ dhcp_router }}" + DHCP_LEASETIME: "{{ dhcp_lease_time }}" dns_servers: - 127.0.0.1 - "{{ static_dns }}" @@ -55,6 +81,12 @@ log_options: max-size: "10m" max-file: "5" + capabilities: + - CAP_NET_BIND_SERVICE + - CAP_NET_RAW + - CAP_NET_ADMIN + - CAP_SYS_NICE + - CAP_CHOWN - name: Check pihole container uri: diff --git a/roles/sync/tasks/main.yaml b/roles/sync/tasks/main.yaml index b5d6348..b04bf07 100644 --- a/roles/sync/tasks/main.yaml +++ b/roles/sync/tasks/main.yaml @@ -32,7 +32,7 @@ - name: Schedule sync with cron ansible.builtin.cron: - hour: "2,14" - minute: "0" + hour: "*" + minute: "*/5" job: "{{ ansible_user_dir }}/{{ sync_dir.path }}/pihole_sync.sh" name: pihole-sync diff --git a/roles/sync/templates/pihole_sync.j2 b/roles/sync/templates/pihole_sync.j2 index 6bd9ee0..6657230 100644 --- a/roles/sync/templates/pihole_sync.j2 +++ b/roles/sync/templates/pihole_sync.j2 @@ -5,22 +5,30 @@ host_key_check="-o StrictHostKeyChecking=no" target="{{ ansible_user }}@{{ sync_target }}" sync_dir="{{ ansible_user_dir }}/{{ sync_dir.path }}" pihole_dir="{{ ansible_user_dir }}/pihole" +dhcp_server="{{ dhcp_server }}" +[ $(ip a | grep {{ sync_target }}) ] && am_master=0 || am_master=1 # If the sync target is on this host, we are the master (0). -if [[ $(ip a | grep {{ sync_target }}) ]]; then - nice -n 19 sqlite3 $pihole_dir/pihole/gravity.db ".backup $sync_dir/gravity.dump" -fi - -if [[ ! $(ip a | grep {{ sync_target }}) ]]; then - sleep 60 - - RSYNC_GRAVITY=$(rsync -a --info=name -e "ssh $key $host_key_check" $target:$sync_dir/gravity.dump $sync_dir) - if [ $? -eq 0 ]; then - if [ -n "$RSYNC_GRAVITY" ]; then - docker stop pihole - sudo sqlite3 $pihole_dir/pihole/gravity.db ".restore $sync_dir/gravity.dump" - docker start pihole +# If the gravity dump file is more than 12 hours old, sync it. +if [[ -f $sync_dir/gravity.dump ]] && [[ $(find $sync_dir/gravity.dump -mmin +719) ]]; then + # master generates a dump file. + if [[ $am_master -eq 0 ]]; then + nice -n 19 sqlite3 $pihole_dir/pihole/gravity.db ".backup $sync_dir/gravity.dump" + else + # not master waits for the dump, then rsyncs and imports it. + sleep 60 + RSYNC_GRAVITY=$(rsync -a --info=name -e "ssh $key $host_key_check" $target:$sync_dir/gravity.dump $sync_dir) + if [ $? -eq 0 ]; then + if [ -n "$RSYNC_GRAVITY" ]; then + docker stop pihole + sudo sqlite3 $pihole_dir/pihole/gravity.db ".restore $sync_dir/gravity.dump" + docker start pihole + fi fi fi +fi + +# not master syncs everything else. +if [[ $am_master -eq 1 ]]; then RSYNC_DNS=$(rsync -a --info=name -e "ssh $key $host_key_check" $target:$pihole_dir/pihole/custom.list $sync_dir) if [ $? -eq 0 ]; then @@ -35,4 +43,29 @@ if [[ ! $(ip a | grep {{ sync_target }}) ]]; then sudo cp --preserve=timestamps $sync_dir/05-pihole-custom-cname.conf $pihole_dir/dnsmasq.d fi fi + + RSYNC_WILDCARD=$(rsync -a --info=name -e "ssh $key $host_key_check" $target:$pihole_dir/dnsmasq.d/02-cluster.vert-wildcard.conf $sync_dir) + if [ $? -eq 0 ]; then + if [ -n "$RSYNC_WILDCARD" ]; then + sudo cp --preserve=timestamps $sync_dir/02-cluster.vert-wildcard.conf $pihole_dir/dnsmasq.d + fi + fi + + RSYNC_LEASES=$(rsync -a --info=name -e "ssh $key $host_key_check" $target:$pihole_dir/pihole/dhcp.leases $sync_dir) + if [ $? -eq 0 ]; then + if [ -n "$RSYNC_LEASES" ]; then + sudo cp --preserve=timestamps $sync_dir/dhcp.leases $pihole_dir/pihole + fi + fi + + if ! [ -d ${pihole_dir}/dnsmasq.d ]; then + mkdir -p ${pihole_dir}/dnsmasq.d + fi + + RSYNC_DNSMASQ=$(rsync -a --info=name -e "ssh $key $host_key_check" --exclude '02-pihole-dhcp.conf' $target:$pihole_dir/dnsmasq.d/* $sync_dir/dnsmasq.d/) + if [ $? -eq 0 ]; then + if [ -n "$RSYNC_DNSMASQ" ]; then + sudo cp --preserve=timestamps $sync_dir/dnsmasq.d/* $pihole_dir/dnsmasq.d + fi + fi fi