diff --git a/.editorconfig b/.editorconfig
index e5626a07f4b354a1ef3d040cc75f6ba2d54d486e..ee415d1f82f884b6201ae9191972fb77329acb78 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -9,7 +9,7 @@ end_of_line = lf
 insert_final_newline = true
 indent_style = space
 indent_size = tab
-tab_width = 2
+tab_width = 4
 charset = utf-8
 trim_trailing_whitespace = true
 
diff --git a/.github/dco.yml b/.github/dco.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0c4b142e9a768208a9935f9553d927695a55e851
--- /dev/null
+++ b/.github/dco.yml
@@ -0,0 +1,2 @@
+require:
+  members: false
diff --git a/.gitignore b/.gitignore
index 73f14ae39154d4e2d2880088cd679c86910daee3..1e80dfb876777245206d643d171387f70c65089a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,11 @@
 *.swp
 __pycache__
 .cache
+.pytest_cache
+.tox
+.eggs
+*.egg-info
+
 
 # Created by https://www.gitignore.io/api/jetbrains+iml
 
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 7704aeaa08e702ef49bab8525158b9d10fcc05fa..79a710fd9881155c5201743f69ddd3bc3711d7b8 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -1,11 +1,5 @@
 <component name="ProjectCodeStyleConfiguration">
   <code_scheme name="Project" version="173">
-    <option name="OTHER_INDENT_OPTIONS">
-      <value>
-        <option name="INDENT_SIZE" value="2" />
-        <option name="TAB_SIZE" value="2" />
-      </value>
-    </option>
     <MarkdownNavigatorCodeStyleSettings>
       <option name="RIGHT_MARGIN" value="72" />
     </MarkdownNavigatorCodeStyleSettings>
diff --git a/.stickler.yml b/.stickler.yml
index b96fc2e7dd9c98f8868c8b5e604d82aa4f64c0c8..0eaae8cb226ebf2e036511b398437179e87dbdde 100644
--- a/.stickler.yml
+++ b/.stickler.yml
@@ -1,3 +1,6 @@
 linters:
   shellcheck:
     shell: bash
+  phpcs:
+  csslint:
+  flake8:
diff --git a/.travis.yml b/.travis.yml
index 2ca3b2d231f9463c1d6d87bf1136daf7fc62486d..fa525e01c6c1da9aebacaf7d78c1f8b3ed6ef830 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,4 +7,6 @@ python:
 install:
   - pip install -r requirements.txt
 
-script: py.test -vv
+script:
+  # tox.ini handles setup, ordering of docker build first, and then run tests
+  - tox
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 2b7fae5d2d3054d5ea7a0171a968c9978024fd90..e32b500e2ee8395d9f1c03020c8d67420ac863ca 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,5 +1,3 @@
-_This template was created based on the work of [`udemy-dl`](https://github.com/nishad/udemy-dl/blob/master/LICENSE)._
-
 # Contributors Guide
 
 Please read and understand the contribution guide before creating an issue or pull request.
diff --git a/README.md b/README.md
index b7f4f2498867cbfc25e60bdf211c324dd43bd9f7..77f259e600fbe982394482d95ed1caface7416ed 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
 <b>Network-wide ad blocking via your own Linux hardware</b><br/>
 </p>
 
-The Pi-hole is a [DNS sinkhole](https://en.wikipedia.org/wiki/DNS_Sinkhole) that protects your devices from unwanted content, without installing any client-side software.
+The Pi-hole[®](https://pi-hole.net/trademark-rules-and-brand-guidelines/) is a [DNS sinkhole](https://en.wikipedia.org/wiki/DNS_Sinkhole) that protects your devices from unwanted content, without installing any client-side software.
 
 - **Easy-to-install**: our versatile installer walks you through the process, and [takes less than ten minutes](https://www.youtube.com/watch?v=vKWjx1AQYgs)
 - **Resolute**: content is blocked in _non-browser locations_, such as ad-laden mobile apps and smart TVs
@@ -27,7 +27,7 @@ Those who want to get started quickly and conveniently, may install Pi-hole usin
 #### `curl -sSL https://install.pi-hole.net | bash`
 
 ## Alternative Install Methods
-[Piping to `bash` is controversial](https://pi-hole.net/2016/07/25/curling-and-piping-to-bash), as it prevents you from [reading code that is about to run](https://github.com/pi-hole/pi-hole/blob/master/automated%20install/basic-install.sh) on your system. Therefore, we provide these alternative installation methods which allow code review before installation: 
+[Piping to `bash` is controversial](https://pi-hole.net/2016/07/25/curling-and-piping-to-bash), as it prevents you from [reading code that is about to run](https://github.com/pi-hole/pi-hole/blob/master/automated%20install/basic-install.sh) on your system. Therefore, we provide these alternative installation methods which allow code review before installation:
 
 ### Method 1: Clone our repository and run
 ```
@@ -60,16 +60,21 @@ Make no mistake: **your support is absolutely vital to help keep us innovating!*
 ### Donations
 Sending a donation using our links below is **extremely helpful** in offsetting a portion of our monthly expenses:
 
-&nbsp;<img src="https://pi-hole.github.io/graphics/Badges/paypal-badge-black.svg" width="24" height="24" alt="PP"/> <a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=3J2L3Z4DHW9UY">Donate via PayPal</a><br/>
-&nbsp;<img src="https://pi-hole.github.io/graphics/Badges/bitcoin-badge-black.svg" width="24" height="24" alt="BTC"/> Bitcoin Address: <code>1GKnevUnVaQM2pQieMyeHkpr8DXfkpfAtL</code>
+- <img src="https://pi-hole.github.io/graphics/Badges/paypal-badge-black.svg" width="24" height="24" alt="PP"/> <a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=3J2L3Z4DHW9UY">Donate via PayPal</a><br/>
+- <img src="https://pi-hole.github.io/graphics/Badges/bitcoin-badge-black.svg" width="24" height="24" alt="BTC"/> [Bitcoin](https://commerce.coinbase.com/checkout/fb7facaf-bebd-46be-bb77-b358f4546763): <code>
+3MDPzjXu2hjw5sGLJvKUi1uXbvQPzVrbpF</code></br>
+- <img src="https://pi-hole.github.io/graphics/Badges/bitcoin-badge-black.svg" width="24" height="24" alt="BTC"/> [Bitcoin Cash](https://commerce.coinbase.com/checkout/fb7facaf-bebd-46be-bb77-b358f4546763): <code>qzqsz4aju2eecc6uhs7tus4vlwhhela24sdruf4qp5</code></br>
+- <img src="https://pi-hole.github.io/graphics/Badges/ethereum-badge-black.svg" width="24" height="24" alt="BTC"/> [Ethereum](https://commerce.coinbase.com/checkout/fb7facaf-bebd-46be-bb77-b358f4546763): <code>0x79d4e90A4a0C732819526c93e21A3F1356A2FAe1</code>
 
 ### Alternative support
-If you'd rather not donate (_which is okay!_), there are other ways you can help support us:
-
-- [Digital Ocean](http://www.digitalocean.com/?refcode=344d234950e1) affiliate link
-- [Vultr](http://www.vultr.com/?ref=7190426) affiliate link
-- [UNIXstickers.com](http://unixstickers.refr.cc/jacobs) affiliate link
-- [Pi-hole Swag Store](https://pi-hole.net/shop/)
+If you'd rather not [donate](https://pi-hole.net/donate/) (_which is okay!_), there are other ways you can help support us:
+- [Patreon](https://patreon.com/pihole) _Become a patron for rewards_
+- [Digital Ocean](http://www.digitalocean.com/?refcode=344d234950e1) _affiliate link_
+- [UNIXstickers.com](http://unixstickers.refr.cc/jacobs) _save $5 when you spend $9 using our affiliate link_
+- [Pi-hole Swag Store](https://pi-hole.net/shop/) _affiliate link_
+- [Amazon](http://www.amazon.com/exec/obidos/redirect-home/pihole09-20) _affiliate link_
+- [DNS Made Easy](https://cp.dnsmadeeasy.com/u/133706) _affiliate link_
+- [Vultr](http://www.vultr.com/?ref=7190426) _affiliate link_
 - Spreading the word about our software, and how you have benefited from it
 
 ### Contributing via GitHub
@@ -93,9 +98,6 @@ While we are primarily reachable on our <a href="https://discourse.pi-hole.net/"
   <li><a href="https://discourse.pi-hole.net/c/faqs">Frequently Asked Questions</a></li>
   <li><a href="https://github.com/pi-hole/pi-hole/wiki">Pi-hole Wiki</a></li>
   <li><a href="https://discourse.pi-hole.net/c/feature-requests?order=votes">Feature Requests</a></li>
-</ul>
-<br/>
-<ul>
   <li><a href="https://discourse.pi-hole.net/">Discourse User Forum</a></li>
   <li><a href="https://www.reddit.com/r/pihole/">Reddit</a></li>
   <li><a href="https://gitter.im/pi-hole/pi-hole">Gitter</a> (Real-time chat)</li>
@@ -127,7 +129,7 @@ You can read our [Core Feature Breakdown](https://github.com/pi-hole/pi-hole/wik
 ### The Web Interface Dashboard
 This [optional dashboard](https://github.com/pi-hole/AdminLTE) allows you to view stats, change settings, and configure your Pi-hole. It's the power of the Command Line Interface, with none of the learning curve!
 
-<a href="https://pi-hole.github.io/graphics/Screenshots/dashboard.png"><img src="https://pi-hole.github.io/graphics/Screenshots/dashboard.png" width="888" height="522" alt="Pi-hole Dashboard"/></a>
+<img src="https://pi-hole.github.io/graphics/Screenshots/pihole-dashboard.png"  alt="Pi-hole Dashboard"/></a>
 
 Some notable features include:
 * Mobile friendly interface
@@ -142,11 +144,11 @@ Some notable features include:
 There are several ways to [access the dashboard](https://discourse.pi-hole.net/t/how-do-i-access-pi-holes-dashboard-admin-interface/3168):
 
 1. `http://<IP_ADDPRESS_OF_YOUR_PI_HOLE>/admin/`
-2. `http:/pi.hole/admin/` (when using Pi-hole as your DNS server)
+2. `http://pi.hole/admin/` (when using Pi-hole as your DNS server)
 3. `http://pi.hole/` (when using Pi-hole as your DNS server)
 
-## The Faster-Than-Light Engine
-The [FTL Engine](https://github.com/pi-hole/FTL) is a lightweight, purpose-built daemon used to provide statistics needed for the Web Interface, and its API can be easily integrated into your own projects. As the name implies, FTL does this all *very quickly*!
+## Faster-than-light Engine
+FTLDNS[â„¢](https://pi-hole.net/trademark-rules-and-brand-guidelines/) is a lightweight, purpose-built daemon used to provide statistics needed for the Web Interface, and its API can be easily integrated into your own projects. As the name implies, FTLDNS does this all *very quickly*!
 
 Some of the statistics you can integrate include:
 * Total number of domains being blocked
@@ -172,31 +174,13 @@ Pi-hole being a **advertising-aware DNS/Web server**, makes use of the following
 * [AdminLTE Dashboard](https://github.com/almasaeed2010/AdminLTE) - premium admin control panel based on Bootstrap 3.x
 
 While quite outdated at this point, [this original blog post about Pi-hole](https://jacobsalmela.com/2015/06/16/block-millions-ads-network-wide-with-a-raspberry-pi-hole-2-0/) goes into **great detail** about how Pi-hole was originally setup and how it works. Syntactically, it's no longer accurate, but the same basic principles and logic still apply to Pi-hole's current state.
-
------
-
-## Pi-hole Projects
-- [The Big Blocklist Collection](https://wally3k.github.io)
-- [Docker Pi-hole container (x86 and ARM)](https://hub.docker.com/r/diginc/pi-hole/)
-- [Pi-Hole in the cloud](http://blog.codybunch.com/2015/07/28/Pi-Hole-in-the-cloud/)
-- [Pie in the Sky-Hole [A Pi-Hole in the cloud for ad-blocking via DNS]](https://dlaa.me/blog/post/skyhole)
-- [Pi-hole Enable/Disable Button](http://thetimmy.silvernight.org/pages/endisbutton/)
-- [Minibian Pi-hole](https://munkjensen.net/wiki/index.php/See_my_Pi-Hole#Minibian_Pi-hole)
-- [CHiP-hole: Network-wide Ad-blocker](https://www.hackster.io/jacobsalmela/chip-hole-network-wide-ad-blocker-98e037)
-- [Chrome Extension: Pi-Hole List Editor](https://chrome.google.com/webstore/detail/pi-hole-list-editor/hlnoeoejkllgkjbnnnhfolapllcnaglh) ([Source Code](https://github.com/packtloss/pihole-extension))
-- [Splunk: Pi-hole Visualiser](https://splunkbase.splunk.com/app/3023/)
-- [Adblocking with Pi-hole and Ubuntu 14.04 on VirtualBox](https://hbalagtas.blogspot.com.au/2016/02/adblocking-with-pi-hole-and-ubuntu-1404.html)
-- [Pi-hole stats in your Mac's menu bar](https://getbitbar.com/plugins/Network/pi-hole.1m.py)
-- [Pi-hole unRAID Template](https://forums.lime-technology.com/topic/36810-support-spants-nodered-mqtt-dashing-couchdb/)
-- [Copernicus: Windows Tray Application](https://github.com/goldbattle/copernicus)
-- [Let your blink1 device blink when Pi-hole filters ads](https://gist.github.com/elpatron68/ec0b4c582e5abf604885ac1e068d233f)
-- [Pi-hole metrics](https://github.com/nlamirault/pihole_exporter) exporter for [Prometheus](https://prometheus.io/)
-- [Magic Mirror with DNS Filtering](https://zonksec.com/blog/magic-mirror-dns-filtering/#dnssoftware)
-- [Pi-hole Droid: Android client](https://github.com/friimaind/pi-hole-droid)
-- [Windows DNS Swapper](https://github.com/roots84/DNS-Swapper), see [#1400](https://github.com/pi-hole/pi-hole/issues/1400)
 -----
 
 ## Coverage
+- [Software Engineering Daily: Interview with the creator of Pi-hole](https://softwareengineeringdaily.com/2018/05/29/pi-hole-ad-blocker-hardware-with-jacob-salmela/)
+- [Bloomberg Business Week: Brotherhood of the Ad blockers](https://www.bloomberg.com/news/features/2018-05-10/inside-the-brotherhood-of-pi-hole-ad-blockers)
+- [Securing DNS across all of my devices with Pi-Hole + DNS-over-HTTPS + 1.1.1.1](https://scotthelme.co.uk/securing-dns-across-all-of-my-devices-with-pihole-dns-over-https-1-1-1-1/)
+- [Adafruit: installing Pi-hole on a Pi Zero W](https://learn.adafruit.com/pi-hole-ad-blocker-with-pi-zero-w/install-pi-hole)
 - [Lifehacker: Turn A Raspberry Pi Into An Ad Blocker With A Single Command](https://www.lifehacker.com.au/2015/02/turn-a-raspberry-pi-into-an-ad-blocker-with-a-single-command/)
 - [MakeUseOf: Adblock Everywhere: The Raspberry Pi-Hole Way](http://www.makeuseof.com/tag/adblock-everywhere-raspberry-pi-hole-way/)
 - [Catchpoint: Ad-Blocking on Apple iOS9: Valuing the End User Experience](http://blog.catchpoint.com/2015/09/14/ad-blocking-apple/)
@@ -215,3 +199,12 @@ While quite outdated at this point, [this original blog post about Pi-hole](http
 - [CryptoAUSTRALIA: How We Tried 5 Privacy Focused Raspberry Pi Projects](https://blog.cryptoaustralia.org.au/2017/10/05/5-privacy-focused-raspberry-pi-projects/)
 - [CryptoAUSTRALIA: Pi-hole Workshop](https://blog.cryptoaustralia.org.au/2017/11/02/pi-hole-network-wide-ad-blocker/)
 - [Know How 355: Killing ads with a Raspberry Pi-Hole!](https://www.twit.tv/shows/know-how/episodes/355)
+
+-----
+
+## Pi-hole Projects
+- [The Big Blocklist Collection](https://wally3k.github.io)
+- [Pie in the Sky-Hole](https://dlaa.me/blog/post/skyhole)
+- [Copernicus: Windows Tray Application](https://github.com/goldbattle/copernicus)
+- [Magic Mirror with DNS Filtering](https://zonksec.com/blog/magic-mirror-dns-filtering/#dnssoftware)
+- [Windows DNS Swapper](https://github.com/roots84/DNS-Swapper)
diff --git a/adlists.default b/adlists.default
deleted file mode 100644
index cbc1bfb37014c00c53799853b6e47970c028520e..0000000000000000000000000000000000000000
--- a/adlists.default
+++ /dev/null
@@ -1,23 +0,0 @@
-# The below list amalgamates several lists we used previously.
-# See `https://github.com/StevenBlack/hosts` for details
-##StevenBlack's list
-https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
-
-##MalwareDomains
-https://mirror1.malwaredomains.com/files/justdomains
-
-##Cameleon
-http://sysctl.org/cameleon/hosts
-
-##Zeustracker
-https://zeustracker.abuse.ch/blocklist.php?download=domainblocklist
-
-##Disconnect.me Tracking
-https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt
-
-##Disconnect.me Ads
-https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt
-
-##Hosts-file.net
-https://hosts-file.net/ad_servers.txt
-
diff --git a/advanced/Scripts/chronometer.sh b/advanced/Scripts/chronometer.sh
index 13d743a83a020f8c081b2e147ac97f7f8f95752a..046a98c494ffc6ebce8b59d5a8367069a5a4a97e 100755
--- a/advanced/Scripts/chronometer.sh
+++ b/advanced/Scripts/chronometer.sh
@@ -12,539 +12,537 @@ LC_NUMERIC=C
 
 # Retrieve stats from FTL engine
 pihole-FTL() {
-  ftl_port=$(cat /var/run/pihole-FTL.port 2> /dev/null)
-  if [[ -n "$ftl_port" ]]; then
-    # Open connection to FTL
-    exec 3<>"/dev/tcp/127.0.0.1/$ftl_port"
-
-    # Test if connection is open
-    if { "true" >&3; } 2> /dev/null; then
-      # Send command to FTL
-      echo -e ">$1" >&3
-
-      # Read input
-      read -r -t 1 LINE <&3
-      until [[ ! $? ]] || [[ "$LINE" == *"EOM"* ]]; do
-         echo "$LINE" >&1
-         read -r -t 1 LINE <&3
-      done
-
-      # Close connection
-      exec 3>&-
-      exec 3<&-
-   fi
-  else
-    echo "0"
-  fi
+    ftl_port=$(cat /var/run/pihole-FTL.port 2> /dev/null)
+    if [[ -n "$ftl_port" ]]; then
+        # Open connection to FTL
+        exec 3<>"/dev/tcp/127.0.0.1/$ftl_port"
+
+        # Test if connection is open
+        if { "true" >&3; } 2> /dev/null; then
+            # Send command to FTL
+            echo -e ">$1" >&3
+
+            # Read input
+            read -r -t 1 LINE <&3
+            until [[ ! $? ]] || [[ "$LINE" == *"EOM"* ]]; do
+                echo "$LINE" >&1
+                read -r -t 1 LINE <&3
+            done
+
+            # Close connection
+            exec 3>&-
+            exec 3<&-
+        fi
+    else
+        echo "0"
+    fi
 }
 
 # Print spaces to align right-side additional text
 printFunc() {
-  local text_last
+    local text_last
 
-  title="$1"
-  title_len="${#title}"
+    title="$1"
+    title_len="${#title}"
 
-  text_main="$2"
-  text_main_nocol="$text_main"
-  if [[ "${text_main:0:1}" == "" ]]; then
-    text_main_nocol=$(sed 's/\[[0-9;]\{1,5\}m//g' <<< "$text_main")
-  fi
-  text_main_len="${#text_main_nocol}"
+    text_main="$2"
+    text_main_nocol="$text_main"
+    if [[ "${text_main:0:1}" == "" ]]; then
+        text_main_nocol=$(sed 's/\[[0-9;]\{1,5\}m//g' <<< "$text_main")
+    fi
+    text_main_len="${#text_main_nocol}"
 
-  text_addn="$3"
-  if [[ "$text_addn" == "last" ]]; then
-    text_addn=""
-    text_last="true"
-  fi
+    text_addn="$3"
+    if [[ "$text_addn" == "last" ]]; then
+        text_addn=""
+        text_last="true"
+    fi
 
-  # If there is additional text, define max length of text_main
-  if [[ -n "$text_addn" ]]; then
-    case "$scr_cols" in
-      [0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-4]) text_main_max_len="9";;
-      4[5-9]) text_main_max_len="14";;
-      *) text_main_max_len="19";;
-    esac
- fi
+    # If there is additional text, define max length of text_main
+    if [[ -n "$text_addn" ]]; then
+        case "$scr_cols" in
+            [0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-4]) text_main_max_len="9";;
+            4[5-9]) text_main_max_len="14";;
+            *) text_main_max_len="19";;
+        esac
+    fi
 
-  [[ -z "$text_addn" ]] && text_main_max_len="$(( scr_cols - title_len ))"
+    [[ -z "$text_addn" ]] && text_main_max_len="$(( scr_cols - title_len ))"
 
-  # Remove excess characters from main text
-  if [[ "$text_main_len" -gt "$text_main_max_len" ]]; then
-    # Trim text without colours
-    text_main_trim="${text_main_nocol:0:$text_main_max_len}"
-    # Replace with trimmed text
-    text_main="${text_main/$text_main_nocol/$text_main_trim}"
-  fi
+    # Remove excess characters from main text
+    if [[ "$text_main_len" -gt "$text_main_max_len" ]]; then
+        # Trim text without colours
+        text_main_trim="${text_main_nocol:0:$text_main_max_len}"
+        # Replace with trimmed text
+        text_main="${text_main/$text_main_nocol/$text_main_trim}"
+    fi
 
-  # Determine amount of spaces for each line
-  if [[ -n "$text_last" ]]; then
-    # Move cursor to end of screen
-    spc_num=$(( scr_cols - ( title_len + text_main_len ) ))
-  else
-    spc_num=$(( text_main_max_len - text_main_len ))
-  fi
+    # Determine amount of spaces for each line
+    if [[ -n "$text_last" ]]; then
+        # Move cursor to end of screen
+        spc_num=$(( scr_cols - ( title_len + text_main_len ) ))
+    else
+        spc_num=$(( text_main_max_len - text_main_len ))
+    fi
 
-  [[ "$spc_num" -le 0 ]] && spc_num="0"
-  spc=$(printf "%${spc_num}s")
-  #spc="${spc// /.}" # Debug: Visualise spaces
+    [[ "$spc_num" -le 0 ]] && spc_num="0"
+    spc=$(printf "%${spc_num}s")
+    #spc="${spc// /.}" # Debug: Visualise spaces
 
-  printf "%s%s$spc" "$title" "$text_main"
+    printf "%s%s$spc" "$title" "$text_main"
 
-  if [[ -n "$text_addn" ]]; then
-    printf "%s(%s)%s\\n" "$COL_NC$COL_DARK_GRAY" "$text_addn" "$COL_NC"
-  else
-    # Do not print trailing newline on final line
-    [[ -z "$text_last" ]] && printf "%s\\n" "$COL_NC"
-  fi
+    if [[ -n "$text_addn" ]]; then
+        printf "%s(%s)%s\\n" "$COL_NC$COL_DARK_GRAY" "$text_addn" "$COL_NC"
+    else
+        # Do not print trailing newline on final line
+        [[ -z "$text_last" ]] && printf "%s\\n" "$COL_NC"
+    fi
 }
 
 # Perform on first Chrono run (not for JSON formatted string)
 get_init_stats() {
-  calcFunc(){ awk "BEGIN {print $*}" 2> /dev/null; }
-
-  # Convert bytes to human-readable format
-  hrBytes() {
-    awk '{
-      num=$1;
-      if(num==0) {
-        print "0 B"
-      } else {
-        xxx=(num<0?-num:num)
-        sss=(num<0?-1:1)
-        split("B KB MB GB TB PB",type)
-        for(i=5;yyy < 1;i--) {
-          yyy=xxx / (2^(10*i))
-        }
-        printf "%.0f " type[i+2], yyy*sss
-      }
-    }' <<< "$1";
-  }
-
-  # Convert seconds to human-readable format
-  hrSecs() {
-    day=$(( $1/60/60/24 )); hrs=$(( $1/3600%24 ))
-    mins=$(( ($1%3600)/60 )); secs=$(( $1%60 ))
-    [[ "$day" -ge "2" ]] && plu="s"
-    [[ "$day" -ge "1" ]] && days="$day day${plu}, " || days=""
-    printf "%s%02d:%02d:%02d\\n" "$days" "$hrs" "$mins" "$secs"
-  }
-
-  # Set Colour Codes
-  coltable="/opt/pihole/COL_TABLE"
-  if [[ -f "${coltable}" ]]; then
-    source ${coltable}
-  else
-    COL_NC=""
-    COL_DARK_GRAY=""
-    COL_LIGHT_GREEN=""
-    COL_LIGHT_BLUE=""
-    COL_LIGHT_RED=""
-    COL_YELLOW=""
-    COL_LIGHT_RED=""
-    COL_URG_RED=""
-  fi
-
-  # Get RPi throttle state (RPi 3B only) & model number, or OS distro info
-  if command -v vcgencmd &> /dev/null; then
-    local sys_throttle_raw
-    local sys_rev_raw
-
-    sys_throttle_raw=$(vgt=$(sudo vcgencmd get_throttled); echo "${vgt##*x}")
-
-    # Active Throttle Notice: http://bit.ly/2gnunOo
-    if [[ "$sys_throttle_raw" != "0" ]]; then
-      case "$sys_throttle_raw" in
-        *0001) thr_type="${COL_YELLOW}Under Voltage";;
-        *0002) thr_type="${COL_LIGHT_BLUE}Arm Freq Cap";;
-        *0003) thr_type="${COL_YELLOW}UV${COL_DARK_GRAY},${COL_NC} ${COL_LIGHT_BLUE}AFC";;
-        *0004) thr_type="${COL_LIGHT_RED}Throttled";;
-        *0005) thr_type="${COL_YELLOW}UV${COL_DARK_GRAY},${COL_NC} ${COL_LIGHT_RED}TT";;
-        *0006) thr_type="${COL_LIGHT_BLUE}AFC${COL_DARK_GRAY},${COL_NC} ${COL_LIGHT_RED}TT";;
-        *0007) thr_type="${COL_YELLOW}UV${COL_DARK_GRAY},${COL_NC} ${COL_LIGHT_BLUE}AFC${COL_DARK_GRAY},${COL_NC} ${COL_LIGHT_RED}TT";;
-      esac
-      [[ -n "$thr_type" ]] && sys_throttle="$thr_type${COL_DARK_GRAY}"
+    calcFunc(){ awk "BEGIN {print $*}" 2> /dev/null; }
+
+    # Convert bytes to human-readable format
+    hrBytes() {
+        awk '{
+            num=$1;
+            if(num==0) {
+                print "0 B"
+            } else {
+                xxx=(num<0?-num:num)
+                sss=(num<0?-1:1)
+                split("B KB MB GB TB PB",type)
+                for(i=5;yyy < 1;i--) {
+                    yyy=xxx / (2^(10*i))
+                }
+            printf "%.0f " type[i+2], yyy*sss
+            }
+        }' <<< "$1";
+    }
+
+    # Convert seconds to human-readable format
+    hrSecs() {
+        day=$(( $1/60/60/24 )); hrs=$(( $1/3600%24 ))
+        mins=$(( ($1%3600)/60 )); secs=$(( $1%60 ))
+        [[ "$day" -ge "2" ]] && plu="s"
+        [[ "$day" -ge "1" ]] && days="$day day${plu}, " || days=""
+        printf "%s%02d:%02d:%02d\\n" "$days" "$hrs" "$mins" "$secs"
+    }
+
+    # Set Colour Codes
+    coltable="/opt/pihole/COL_TABLE"
+    if [[ -f "${coltable}" ]]; then
+        source ${coltable}
+    else
+        COL_NC=""
+        COL_DARK_GRAY=""
+        COL_LIGHT_GREEN=""
+        COL_LIGHT_BLUE=""
+        COL_LIGHT_RED=""
+        COL_YELLOW=""
+        COL_LIGHT_RED=""
+        COL_URG_RED=""
     fi
 
-    sys_rev_raw=$(awk '/Revision/ {print $3}' < /proc/cpuinfo)
-    case "$sys_rev_raw" in
-      000[2-6]) sys_model=" 1, Model B";; # 256MB
-      000[7-9]) sys_model=" 1, Model A";; # 256MB
-      000d|000e|000f) sys_model=" 1, Model B";; # 512MB
-      0010|0013) sys_model=" 1, Model B+";; # 512MB
-      0012|0015) sys_model=" 1, Model A+";; # 256MB
-      a0104[0-1]|a21041|a22042) sys_model=" 2, Model B";; # 1GB
-      900021) sys_model=" 1, Model A+";; # 512MB
-      900032) sys_model=" 1, Model B+";; # 512MB
-      90009[2-3]|920093) sys_model=" Zero";; # 512MB
-      9000c1) sys_model=" Zero W";; # 512MB
-      a02082|a[2-3]2082) sys_model=" 3, Model B";; # 1GB
-      *) sys_model="";;
-    esac
-    sys_type="Raspberry Pi$sys_model"
-  else
-    source "/etc/os-release"
-    CODENAME=$(sed 's/[()]//g' <<< "${VERSION/* /}")
-    sys_type="${NAME/ */} ${CODENAME^} $VERSION_ID"
-  fi
-
-  # Get core count
-  sys_cores=$(grep -c "^processor" /proc/cpuinfo)
-
-  # Test existence of clock speed file for ARM CPU
-  if [[ -f "/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq" ]]; then
-    scaling_freq_file="/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq"
-  fi
-
-  # Test existence of temperature file
-  if [[ -f "/sys/class/thermal/thermal_zone0/temp" ]]; then
-    temp_file="/sys/class/thermal/thermal_zone0/temp"
-  elif [[ -f "/sys/class/hwmon/hwmon0/temp1_input" ]]; then
-    temp_file="/sys/class/hwmon/hwmon0/temp1_input"
-  else
-    temp_file=""
-  fi
-
-  # Test existence of setupVars config
-  if [[ -f "/etc/pihole/setupVars.conf" ]]; then
-    setupVars="/etc/pihole/setupVars.conf"
-  fi
-}
-
-get_sys_stats() {
-  local ph_ver_raw
-  local cpu_raw
-  local ram_raw
-  local disk_raw
-
-  # Update every 12 refreshes (Def: every 60s)
-  count=$((count+1))
-  if [[ "$count" == "1" ]] || (( "$count" % 12 == 0 )); then
-    # Do not source setupVars if file does not exist
-    [[ -n "$setupVars" ]] && source "$setupVars"
-
-    mapfile -t ph_ver_raw < <(pihole -v -c 2> /dev/null | sed -n 's/^.* v/v/p')
-    if [[ -n "${ph_ver_raw[0]}" ]]; then
-      ph_core_ver="${ph_ver_raw[0]}"
-      ph_lte_ver="${ph_ver_raw[1]}"
-      ph_ftl_ver="${ph_ver_raw[2]}"
+    # Get RPi throttle state (RPi 3B only) & model number, or OS distro info
+    if command -v vcgencmd &> /dev/null; then
+        local sys_throttle_raw
+        local sys_rev_raw
+
+        sys_throttle_raw=$(vgt=$(sudo vcgencmd get_throttled); echo "${vgt##*x}")
+
+        # Active Throttle Notice: http://bit.ly/2gnunOo
+        if [[ "$sys_throttle_raw" != "0" ]]; then
+            case "$sys_throttle_raw" in
+                *0001) thr_type="${COL_YELLOW}Under Voltage";;
+                *0002) thr_type="${COL_LIGHT_BLUE}Arm Freq Cap";;
+                *0003) thr_type="${COL_YELLOW}UV${COL_DARK_GRAY},${COL_NC} ${COL_LIGHT_BLUE}AFC";;
+                *0004) thr_type="${COL_LIGHT_RED}Throttled";;
+                *0005) thr_type="${COL_YELLOW}UV${COL_DARK_GRAY},${COL_NC} ${COL_LIGHT_RED}TT";;
+                *0006) thr_type="${COL_LIGHT_BLUE}AFC${COL_DARK_GRAY},${COL_NC} ${COL_LIGHT_RED}TT";;
+                *0007) thr_type="${COL_YELLOW}UV${COL_DARK_GRAY},${COL_NC} ${COL_LIGHT_BLUE}AFC${COL_DARK_GRAY},${COL_NC} ${COL_LIGHT_RED}TT";;
+            esac
+        [[ -n "$thr_type" ]] && sys_throttle="$thr_type${COL_DARK_GRAY}"
+        fi
+
+        sys_rev_raw=$(awk '/Revision/ {print $3}' < /proc/cpuinfo)
+        case "$sys_rev_raw" in
+            000[2-6]) sys_model=" 1, Model B";; # 256MB
+            000[7-9]) sys_model=" 1, Model A";; # 256MB
+            000d|000e|000f) sys_model=" 1, Model B";; # 512MB
+            0010|0013) sys_model=" 1, Model B+";; # 512MB
+            0012|0015) sys_model=" 1, Model A+";; # 256MB
+            a0104[0-1]|a21041|a22042) sys_model=" 2, Model B";; # 1GB
+            900021) sys_model=" 1, Model A+";; # 512MB
+            900032) sys_model=" 1, Model B+";; # 512MB
+            90009[2-3]|920093) sys_model=" Zero";; # 512MB
+            9000c1) sys_model=" Zero W";; # 512MB
+            a02082|a[2-3]2082) sys_model=" 3, Model B";; # 1GB
+            a020d3) sys_model=" 3, Model B+";; # 1GB
+            *) sys_model="";;
+        esac
+        sys_type="Raspberry Pi$sys_model"
     else
-      ph_core_ver="-1"
+        source "/etc/os-release"
+        CODENAME=$(sed 's/[()]//g' <<< "${VERSION/* /}")
+        sys_type="${NAME/ */} ${CODENAME^} $VERSION_ID"
     fi
 
-    sys_name=$(hostname)
-
-    [[ -n "$TEMPERATUREUNIT" ]] && temp_unit="$TEMPERATUREUNIT" || temp_unit="c"
+    # Get core count
+    sys_cores=$(grep -c "^processor" /proc/cpuinfo)
 
-    # Get storage stats for partition mounted on /
-    read -r -a disk_raw <<< "$(df -B1 / 2> /dev/null | awk 'END{ print $3,$2,$5 }')"
-    disk_used="${disk_raw[0]}"
-    disk_total="${disk_raw[1]}"
-    disk_perc="${disk_raw[2]}"
-
-    net_gateway=$(route -n | awk '$4 == "UG" {print $2;exit}')
-
-    # Get DHCP stats, if feature is enabled
-    if [[ "$DHCP_ACTIVE" == "true" ]]; then
-      ph_dhcp_max=$(( ${DHCP_END##*.} - ${DHCP_START##*.} + 1 ))
+    # Test existence of clock speed file for ARM CPU
+    if [[ -f "/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq" ]]; then
+        scaling_freq_file="/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq"
     fi
 
-    # Get DNS server count
-    dns_count="0"
-    [[ -n "${PIHOLE_DNS_1}" ]] && dns_count=$((dns_count+1))
-    [[ -n "${PIHOLE_DNS_2}" ]] && dns_count=$((dns_count+1))
-    [[ -n "${PIHOLE_DNS_3}" ]] && dns_count=$((dns_count+1))
-    [[ -n "${PIHOLE_DNS_4}" ]] && dns_count=$((dns_count+1))
-    [[ -n "${PIHOLE_DNS_5}" ]] && dns_count=$((dns_count+1))
-    [[ -n "${PIHOLE_DNS_6}" ]] && dns_count=$((dns_count+1))
-    [[ -n "${PIHOLE_DNS_7}" ]] && dns_count=$((dns_count+1))
-    [[ -n "${PIHOLE_DNS_8}" ]] && dns_count=$((dns_count+1))
-    [[ -n "${PIHOLE_DNS_9}" ]] && dns_count="$dns_count+"
-  fi
-
-  # Get screen size
-  read -r -a scr_size <<< "$(stty size 2>/dev/null || echo 24 80)"
-  scr_lines="${scr_size[0]}"
-  scr_cols="${scr_size[1]}"
-
-  # Determine Chronometer size behaviour
-  if [[ "$scr_cols" -ge 58 ]]; then
-    chrono_width="large"
-  elif [[ "$scr_cols" -gt 40 ]]; then
-    chrono_width="medium"
-  else
-    chrono_width="small"
-  fi
-
-  # Determine max length of divider string
-  scr_line_len=$(( scr_cols - 2 ))
-  [[ "$scr_line_len" -ge 58 ]] && scr_line_len="58"
-  scr_line_str=$(printf "%${scr_line_len}s")
-  scr_line_str="${scr_line_str// /—}"
-
-  sys_uptime=$(hrSecs "$(cut -d. -f1 /proc/uptime)")
-  sys_loadavg=$(cut -d " " -f1,2,3 /proc/loadavg)
-
-  # Get CPU usage, only counting processes over 1% as active
-  # shellcheck disable=SC2009
-  cpu_raw=$(ps -eo pcpu,rss --no-headers | grep -E -v "    0")
-  cpu_tasks=$(wc -l <<< "$cpu_raw")
-  cpu_taskact=$(sed -r "/(^ 0.)/d" <<< "$cpu_raw" | wc -l)
-  cpu_perc=$(awk '{sum+=$1} END {printf "%.0f\n", sum/'"$sys_cores"'}' <<< "$cpu_raw")
-
-  # Get CPU clock speed
-  if [[ -n "$scaling_freq_file" ]]; then
-    cpu_mhz=$(( $(< /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq) / 1000 ))
-  else
-    cpu_mhz=$(lscpu | awk -F ":" '/MHz/ {print $2;exit}')
-    cpu_mhz=$(printf "%.0f" "${cpu_mhz//[[:space:]]/}")
-  fi
-
-  # Determine whether to display CPU clock speed as MHz or GHz
-  if [[ -n "$cpu_mhz" ]]; then
-    [[ "$cpu_mhz" -le "999" ]] && cpu_freq="$cpu_mhz MHz" || cpu_freq="$(printf "%.1f" $(calcFunc "$cpu_mhz"/1000)) GHz"
-    [[ "${cpu_freq}" == *".0"* ]] && cpu_freq="${cpu_freq/.0/}"
-  fi
-
-  # Determine colour for temperature
-  if [[ -n "$temp_file" ]]; then
-    if [[ "$temp_unit" == "C" ]]; then
-      cpu_temp=$(printf "%.0fc\\n" "$(calcFunc "$(< $temp_file) / 1000")")
-
-      case "${cpu_temp::-1}" in
-        -*|[0-9]|[1-3][0-9]) cpu_col="$COL_LIGHT_BLUE";;
-        4[0-9]) cpu_col="";;
-        5[0-9]) cpu_col="$COL_YELLOW";;
-        6[0-9]) cpu_col="$COL_LIGHT_RED";;
-        *) cpu_col="$COL_URG_RED";;
-      esac
-
-      # $COL_NC$COL_DARK_GRAY is needed for $COL_URG_RED
-      cpu_temp_str=" @ $cpu_col$cpu_temp$COL_NC$COL_DARK_GRAY"
-
-    elif [[ "$temp_unit" == "F" ]]; then
-      cpu_temp=$(printf "%.0ff\\n" "$(calcFunc "($(< $temp_file) / 1000) * 9 / 5 + 32")")
-
-      case "${cpu_temp::-1}" in
-        -*|[0-9]|[0-9][0-9]) cpu_col="$COL_LIGHT_BLUE";;
-        1[0-1][0-9]) cpu_col="";;
-        1[2-3][0-9]) cpu_col="$COL_YELLOW";;
-        1[4-5][0-9]) cpu_col="$COL_LIGHT_RED";;
-        *) cpu_col="$COL_URG_RED";;
-      esac
-
-      cpu_temp_str=" @ $cpu_col$cpu_temp$COL_NC$COL_DARK_GRAY"
-
+    # Test existence of temperature file
+    if [[ -f "/sys/class/thermal/thermal_zone0/temp" ]]; then
+        temp_file="/sys/class/thermal/thermal_zone0/temp"
+    elif [[ -f "/sys/class/hwmon/hwmon0/temp1_input" ]]; then
+        temp_file="/sys/class/hwmon/hwmon0/temp1_input"
     else
-      cpu_temp_str=$(printf " @ %.0fk\\n" "$(calcFunc "($(< $temp_file) / 1000) + 273.15")")
+        temp_file=""
     fi
-  else
-    cpu_temp_str=""
-  fi
-
-  read -r -a ram_raw <<< "$(awk '/MemTotal:/{total=$2} /MemFree:/{free=$2} /Buffers:/{buffers=$2} /^Cached:/{cached=$2} END {printf "%.0f %.0f %.0f", (total-free-buffers-cached)*100/total, (total-free-buffers-cached)*1024, total*1024}' /proc/meminfo)"
-  ram_perc="${ram_raw[0]}"
-  ram_used="${ram_raw[1]}"
-  ram_total="${ram_raw[2]}"
-
-  if [[ "$(pihole status web 2> /dev/null)" == "1" ]]; then
-    ph_status="${COL_LIGHT_GREEN}Active"
-  else
-    ph_status="${COL_LIGHT_RED}Offline"
-  fi
-
-  if [[ "$DHCP_ACTIVE" == "true" ]]; then
-    local ph_dhcp_range
 
-    ph_dhcp_range=$(seq -s "|" -f "${DHCP_START%.*}.%g" "${DHCP_START##*.}" "${DHCP_END##*.}")
-
-    # Count dynamic leases from available range, and not static leases
-    ph_dhcp_num=$(grep -cE "$ph_dhcp_range" "/etc/pihole/dhcp.leases")
-    ph_dhcp_percent=$(( ph_dhcp_num * 100 / ph_dhcp_max ))
-  fi
-}
-
-get_ftl_stats() {
-  local stats_raw
-
-  mapfile -t stats_raw < <(pihole-FTL "stats")
-  domains_being_blocked_raw="${stats_raw[0]#* }"
-  dns_queries_today_raw="${stats_raw[1]#* }"
-  ads_blocked_today_raw="${stats_raw[2]#* }"
-  ads_percentage_today_raw="${stats_raw[3]#* }"
-  queries_forwarded_raw="${stats_raw[5]#* }"
-  queries_cached_raw="${stats_raw[6]#* }"
-
-  # Only retrieve these stats when not called from jsonFunc
-  if [[ -z "$1" ]]; then
-    local top_ad_raw
-    local top_domain_raw
-    local top_client_raw
-
-    domains_being_blocked=$(printf "%.0f\\n" "${domains_being_blocked_raw}" 2> /dev/null)
-    dns_queries_today=$(printf "%.0f\\n" "${dns_queries_today_raw}")
-    ads_blocked_today=$(printf "%.0f\\n" "${ads_blocked_today_raw}")
-    ads_percentage_today=$(printf "%'.0f\\n" "${ads_percentage_today_raw}")
-    queries_cached_percentage=$(printf "%.0f\\n" "$(calcFunc "$queries_cached_raw * 100 / ( $queries_forwarded_raw + $queries_cached_raw )")")
-    recent_blocked=$(pihole-FTL recentBlocked)
-    read -r -a top_ad_raw <<< "$(pihole-FTL "top-ads (1)")"
-    read -r -a top_domain_raw <<< "$(pihole-FTL "top-domains (1)")"
-    read -r -a top_client_raw <<< "$(pihole-FTL "top-clients (1)")"
-
-    top_ad="${top_ad_raw[2]}"
-    top_domain="${top_domain_raw[2]}"
-    if [[ "${top_client_raw[3]}" ]]; then
-      top_client="${top_client_raw[3]}"
-    else
-      top_client="${top_client_raw[2]}"
+    # Test existence of setupVars config
+    if [[ -f "/etc/pihole/setupVars.conf" ]]; then
+        setupVars="/etc/pihole/setupVars.conf"
     fi
-  fi
 }
 
-get_strings() {
-  # Expand or contract strings depending on screen size
-  if [[ "$chrono_width" == "large" ]]; then
-    phc_str="        ${COL_DARK_GRAY}Core"
-    lte_str="        ${COL_DARK_GRAY}Web"
-    ftl_str="        ${COL_DARK_GRAY}FTL"
-    api_str="${COL_LIGHT_RED}API Offline"
-
-    host_info="$sys_type"
-    sys_info="$sys_throttle"
-    sys_info2="Active: $cpu_taskact of $cpu_tasks tasks"
-    used_str="Used: "
-    leased_str="Leased: "
-    domains_being_blocked=$(printf "%'.0f" "$domains_being_blocked")
-    ads_blocked_today=$(printf "%'.0f" "$ads_blocked_today")
-    dns_queries_today=$(printf "%'.0f" "$dns_queries_today")
-    ph_info="Blocking: $domains_being_blocked sites"
-    total_str="Total: "
-  else
-    phc_str=" ${COL_DARK_GRAY}Core"
-    lte_str=" ${COL_DARK_GRAY}Web"
-    ftl_str=" ${COL_DARK_GRAY}FTL"
-    api_str="${COL_LIGHT_RED}API Down"
-    ph_info="$domains_being_blocked blocked"
-  fi
-
-  [[ "$sys_cores" -ne 1 ]] && sys_cores_txt="${sys_cores}x "
-  cpu_info="$sys_cores_txt$cpu_freq$cpu_temp_str"
-  ram_info="$used_str$(hrBytes "$ram_used") of $(hrBytes "$ram_total")"
-  disk_info="$used_str$(hrBytes "$disk_used") of $(hrBytes "$disk_total")"
-
-  lan_info="Gateway: $net_gateway"
-  dhcp_info="$leased_str$ph_dhcp_num of $ph_dhcp_max"
-
-  ads_info="$total_str$ads_blocked_today of $dns_queries_today"
-  dns_info="$dns_count DNS servers"
+get_sys_stats() {
+    local ph_ver_raw
+    local cpu_raw
+    local ram_raw
+    local disk_raw
+
+    # Update every 12 refreshes (Def: every 60s)
+    count=$((count+1))
+    if [[ "$count" == "1" ]] || (( "$count" % 12 == 0 )); then
+        # Do not source setupVars if file does not exist
+        [[ -n "$setupVars" ]] && source "$setupVars"
+
+        mapfile -t ph_ver_raw < <(pihole -v -c 2> /dev/null | sed -n 's/^.* v/v/p')
+        if [[ -n "${ph_ver_raw[0]}" ]]; then
+            ph_core_ver="${ph_ver_raw[0]}"
+            ph_lte_ver="${ph_ver_raw[1]}"
+            ph_ftl_ver="${ph_ver_raw[2]}"
+        else
+            ph_core_ver="-1"
+        fi
+
+        sys_name=$(hostname)
+
+        [[ -n "$TEMPERATUREUNIT" ]] && temp_unit="$TEMPERATUREUNIT" || temp_unit="c"
+
+        # Get storage stats for partition mounted on /
+        read -r -a disk_raw <<< "$(df -B1 / 2> /dev/null | awk 'END{ print $3,$2,$5 }')"
+        disk_used="${disk_raw[0]}"
+        disk_total="${disk_raw[1]}"
+        disk_perc="${disk_raw[2]}"
+
+        net_gateway=$(route -n | awk '$4 == "UG" {print $2;exit}')
+
+        # Get DHCP stats, if feature is enabled
+        if [[ "$DHCP_ACTIVE" == "true" ]]; then
+            ph_dhcp_max=$(( ${DHCP_END##*.} - ${DHCP_START##*.} + 1 ))
+        fi
+
+        # Get DNS server count
+        dns_count="0"
+        [[ -n "${PIHOLE_DNS_1}" ]] && dns_count=$((dns_count+1))
+        [[ -n "${PIHOLE_DNS_2}" ]] && dns_count=$((dns_count+1))
+        [[ -n "${PIHOLE_DNS_3}" ]] && dns_count=$((dns_count+1))
+        [[ -n "${PIHOLE_DNS_4}" ]] && dns_count=$((dns_count+1))
+        [[ -n "${PIHOLE_DNS_5}" ]] && dns_count=$((dns_count+1))
+        [[ -n "${PIHOLE_DNS_6}" ]] && dns_count=$((dns_count+1))
+        [[ -n "${PIHOLE_DNS_7}" ]] && dns_count=$((dns_count+1))
+        [[ -n "${PIHOLE_DNS_8}" ]] && dns_count=$((dns_count+1))
+        [[ -n "${PIHOLE_DNS_9}" ]] && dns_count="$dns_count+"
+    fi
 
-  [[ "$recent_blocked" == "0" ]] && recent_blocked="${COL_LIGHT_RED}FTL offline${COL_NC}"
-}
+    # Get screen size
+    read -r -a scr_size <<< "$(stty size 2>/dev/null || echo 24 80)"
+    scr_lines="${scr_size[0]}"
+    scr_cols="${scr_size[1]}"
 
-chronoFunc() {
-  get_init_stats
-
-  for (( ; ; )); do
-    get_sys_stats
-    get_ftl_stats
-    get_strings
-
-    # Strip excess development version numbers
-    if [[ "$ph_core_ver" != "-1" ]]; then
-      phc_ver_str="$phc_str: ${ph_core_ver%-*}${COL_NC}"
-      lte_ver_str="$lte_str: ${ph_lte_ver%-*}${COL_NC}"
-      ftl_ver_str="$ftl_str: ${ph_ftl_ver%-*}${COL_NC}"
+    # Determine Chronometer size behaviour
+    if [[ "$scr_cols" -ge 58 ]]; then
+        chrono_width="large"
+    elif [[ "$scr_cols" -gt 40 ]]; then
+        chrono_width="medium"
     else
-      phc_ver_str="$phc_str: $api_str${COL_NC}"
+        chrono_width="small"
     fi
 
-    # Get refresh number
-    if [[ "$*" == *"-r"* ]]; then
-      num="$*"
-      num="${num/*-r /}"
-      num="${num/ */}"
-      num_str="Refresh set for every $num seconds"
+    # Determine max length of divider string
+    scr_line_len=$(( scr_cols - 2 ))
+    [[ "$scr_line_len" -ge 58 ]] && scr_line_len="58"
+    scr_line_str=$(printf "%${scr_line_len}s")
+    scr_line_str="${scr_line_str// /—}"
+
+    sys_uptime=$(hrSecs "$(cut -d. -f1 /proc/uptime)")
+    sys_loadavg=$(cut -d " " -f1,2,3 /proc/loadavg)
+
+    # Get CPU usage, only counting processes over 1% as active
+    # shellcheck disable=SC2009
+    cpu_raw=$(ps -eo pcpu,rss --no-headers | grep -E -v "    0")
+    cpu_tasks=$(wc -l <<< "$cpu_raw")
+    cpu_taskact=$(sed -r "/(^ 0.)/d" <<< "$cpu_raw" | wc -l)
+    cpu_perc=$(awk '{sum+=$1} END {printf "%.0f\n", sum/'"$sys_cores"'}' <<< "$cpu_raw")
+
+    # Get CPU clock speed
+    if [[ -n "$scaling_freq_file" ]]; then
+        cpu_mhz=$(( $(< /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq) / 1000 ))
     else
-      num_str=""
+        cpu_mhz=$(lscpu | awk -F ":" '/MHz/ {print $2;exit}')
+        cpu_mhz=$(printf "%.0f" "${cpu_mhz//[[:space:]]/}")
     fi
 
-    clear
+    # Determine whether to display CPU clock speed as MHz or GHz
+    if [[ -n "$cpu_mhz" ]]; then
+        [[ "$cpu_mhz" -le "999" ]] && cpu_freq="$cpu_mhz MHz" || cpu_freq="$(printf "%.1f" $(calcFunc "$cpu_mhz"/1000)) GHz"
+        [[ "${cpu_freq}" == *".0"* ]] && cpu_freq="${cpu_freq/.0/}"
+    fi
 
-    # Remove exit message heading on third refresh
-    if [[ "$count" -le 2 ]] && [[ "$*" != *"-e"* ]]; then
-      echo -e " ${COL_LIGHT_GREEN}Pi-hole Chronometer${COL_NC}
- $num_str
- ${COL_LIGHT_RED}Press Ctrl-C to exit${COL_NC}
- ${COL_DARK_GRAY}$scr_line_str${COL_NC}"
+    # Determine colour for temperature
+    if [[ -n "$temp_file" ]]; then
+        if [[ "$temp_unit" == "C" ]]; then
+            cpu_temp=$(printf "%.0fc\\n" "$(calcFunc "$(< $temp_file) / 1000")")
+
+            case "${cpu_temp::-1}" in
+                -*|[0-9]|[1-3][0-9]) cpu_col="$COL_LIGHT_BLUE";;
+                4[0-9]) cpu_col="";;
+                5[0-9]) cpu_col="$COL_YELLOW";;
+                6[0-9]) cpu_col="$COL_LIGHT_RED";;
+                *) cpu_col="$COL_URG_RED";;
+            esac
+
+        # $COL_NC$COL_DARK_GRAY is needed for $COL_URG_RED
+        cpu_temp_str=" @ $cpu_col$cpu_temp$COL_NC$COL_DARK_GRAY"
+
+        elif [[ "$temp_unit" == "F" ]]; then
+            cpu_temp=$(printf "%.0ff\\n" "$(calcFunc "($(< $temp_file) / 1000) * 9 / 5 + 32")")
+
+            case "${cpu_temp::-1}" in
+                -*|[0-9]|[0-9][0-9]) cpu_col="$COL_LIGHT_BLUE";;
+                1[0-1][0-9]) cpu_col="";;
+                1[2-3][0-9]) cpu_col="$COL_YELLOW";;
+                1[4-5][0-9]) cpu_col="$COL_LIGHT_RED";;
+                *) cpu_col="$COL_URG_RED";;
+            esac
+
+            cpu_temp_str=" @ $cpu_col$cpu_temp$COL_NC$COL_DARK_GRAY"
+
+        else
+            cpu_temp_str=$(printf " @ %.0fk\\n" "$(calcFunc "($(< $temp_file) / 1000) + 273.15")")
+        fi
     else
-      echo -e "|¯¯¯(¯)_|¯|_  ___|¯|___$phc_ver_str
-| ¯_/¯|_| ' \\/ _ \\ / -_)$lte_ver_str
-|_| |_| |_||_\\___/_\\___|$ftl_ver_str
- ${COL_DARK_GRAY}$scr_line_str${COL_NC}"
+        cpu_temp_str=""
     fi
 
-    printFunc "  Hostname: " "$sys_name" "$host_info"
-    printFunc "    Uptime: " "$sys_uptime" "$sys_info"
-    printFunc " Task Load: " "$sys_loadavg" "$sys_info2"
-    printFunc " CPU usage: " "$cpu_perc%" "$cpu_info"
-    printFunc " RAM usage: " "$ram_perc%" "$ram_info"
-    printFunc " HDD usage: " "$disk_perc" "$disk_info"
+    read -r -a ram_raw <<< "$(awk '/MemTotal:/{total=$2} /MemFree:/{free=$2} /Buffers:/{buffers=$2} /^Cached:/{cached=$2} END {printf "%.0f %.0f %.0f", (total-free-buffers-cached)*100/total, (total-free-buffers-cached)*1024, total*1024}' /proc/meminfo)"
+    ram_perc="${ram_raw[0]}"
+    ram_used="${ram_raw[1]}"
+    ram_total="${ram_raw[2]}"
 
-    if [[ "$scr_lines" -gt 17 ]] && [[ "$chrono_width" != "small" ]]; then
-      printFunc "  LAN addr: " "${IPV4_ADDRESS/\/*/}" "$lan_info"
+    if [[ "$(pihole status web 2> /dev/null)" == "1" ]]; then
+        ph_status="${COL_LIGHT_GREEN}Active"
+    else
+        ph_status="${COL_LIGHT_RED}Offline"
     fi
 
     if [[ "$DHCP_ACTIVE" == "true" ]]; then
-      printFunc "DHCP usage: " "$ph_dhcp_percent%" "$dhcp_info"
-    fi
-
-    printFunc "   Pi-hole: " "$ph_status" "$ph_info"
-    printFunc " Ads Today: " "$ads_percentage_today%" "$ads_info"
-    printFunc "Local Qrys: " "$queries_cached_percentage%" "$dns_info"
+        local ph_dhcp_range
 
-    printFunc "   Blocked: " "$recent_blocked"
-    printFunc "Top Advert: " "$top_ad"
+        ph_dhcp_range=$(seq -s "|" -f "${DHCP_START%.*}.%g" "${DHCP_START##*.}" "${DHCP_END##*.}")
 
-    # Provide more stats on screens with more lines
-    if [[ "$scr_lines" -eq 17 ]]; then
-      if [[ "$DHCP_ACTIVE" == "true" ]]; then
-        printFunc "Top Domain: " "$top_domain" "last"
-      else
-        print_client="true"
-      fi
-    else
-      print_client="true"
+        # Count dynamic leases from available range, and not static leases
+        ph_dhcp_num=$(grep -cE "$ph_dhcp_range" "/etc/pihole/dhcp.leases")
+        ph_dhcp_percent=$(( ph_dhcp_num * 100 / ph_dhcp_max ))
     fi
+}
 
-    if [[ -n "$print_client" ]]; then
-      printFunc "Top Domain: " "$top_domain"
-      printFunc "Top Client: " "$top_client" "last"
+get_ftl_stats() {
+    local stats_raw
+
+    mapfile -t stats_raw < <(pihole-FTL "stats")
+    domains_being_blocked_raw="${stats_raw[0]#* }"
+    dns_queries_today_raw="${stats_raw[1]#* }"
+    ads_blocked_today_raw="${stats_raw[2]#* }"
+    ads_percentage_today_raw="${stats_raw[3]#* }"
+    queries_forwarded_raw="${stats_raw[5]#* }"
+    queries_cached_raw="${stats_raw[6]#* }"
+
+    # Only retrieve these stats when not called from jsonFunc
+    if [[ -z "$1" ]]; then
+        local top_ad_raw
+        local top_domain_raw
+        local top_client_raw
+
+        domains_being_blocked=$(printf "%.0f\\n" "${domains_being_blocked_raw}" 2> /dev/null)
+        dns_queries_today=$(printf "%.0f\\n" "${dns_queries_today_raw}")
+        ads_blocked_today=$(printf "%.0f\\n" "${ads_blocked_today_raw}")
+        ads_percentage_today=$(printf "%'.0f\\n" "${ads_percentage_today_raw}")
+        queries_cached_percentage=$(printf "%.0f\\n" "$(calcFunc "$queries_cached_raw * 100 / ( $queries_forwarded_raw + $queries_cached_raw )")")
+        recent_blocked=$(pihole-FTL recentBlocked)
+        read -r -a top_ad_raw <<< "$(pihole-FTL "top-ads (1)")"
+        read -r -a top_domain_raw <<< "$(pihole-FTL "top-domains (1)")"
+        read -r -a top_client_raw <<< "$(pihole-FTL "top-clients (1)")"
+
+        top_ad="${top_ad_raw[2]}"
+        top_domain="${top_domain_raw[2]}"
+        if [[ "${top_client_raw[3]}" ]]; then
+            top_client="${top_client_raw[3]}"
+        else
+            top_client="${top_client_raw[2]}"
+        fi
     fi
+}
 
-    # Handle exit/refresh options
-    if [[ "$*" == *"-e"* ]]; then
-      exit 0
+get_strings() {
+    # Expand or contract strings depending on screen size
+    if [[ "$chrono_width" == "large" ]]; then
+        phc_str="        ${COL_DARK_GRAY}Core"
+        lte_str="        ${COL_DARK_GRAY}Web"
+        ftl_str="        ${COL_DARK_GRAY}FTL"
+        api_str="${COL_LIGHT_RED}API Offline"
+
+        host_info="$sys_type"
+        sys_info="$sys_throttle"
+        sys_info2="Active: $cpu_taskact of $cpu_tasks tasks"
+        used_str="Used: "
+        leased_str="Leased: "
+        domains_being_blocked=$(printf "%'.0f" "$domains_being_blocked")
+        ads_blocked_today=$(printf "%'.0f" "$ads_blocked_today")
+        dns_queries_today=$(printf "%'.0f" "$dns_queries_today")
+        ph_info="Blocking: $domains_being_blocked sites"
+        total_str="Total: "
     else
-      if [[ "$*" == *"-r"* ]]; then
-        sleep "$num"
-      else
-        sleep 5
-      fi
+        phc_str=" ${COL_DARK_GRAY}Core"
+        lte_str=" ${COL_DARK_GRAY}Web"
+        ftl_str=" ${COL_DARK_GRAY}FTL"
+        api_str="${COL_LIGHT_RED}API Down"
+        ph_info="$domains_being_blocked blocked"
     fi
 
-  done
+    [[ "$sys_cores" -ne 1 ]] && sys_cores_txt="${sys_cores}x "
+    cpu_info="$sys_cores_txt$cpu_freq$cpu_temp_str"
+    ram_info="$used_str$(hrBytes "$ram_used") of $(hrBytes "$ram_total")"
+    disk_info="$used_str$(hrBytes "$disk_used") of $(hrBytes "$disk_total")"
+
+    lan_info="Gateway: $net_gateway"
+    dhcp_info="$leased_str$ph_dhcp_num of $ph_dhcp_max"
+
+      ads_info="$total_str$ads_blocked_today of $dns_queries_today"
+    dns_info="$dns_count DNS servers"
+
+    [[ "$recent_blocked" == "0" ]] && recent_blocked="${COL_LIGHT_RED}FTL offline${COL_NC}"
+}
+
+chronoFunc() {
+    get_init_stats
+
+    for (( ; ; )); do
+        get_sys_stats
+        get_ftl_stats
+        get_strings
+
+        # Strip excess development version numbers
+        if [[ "$ph_core_ver" != "-1" ]]; then
+            phc_ver_str="$phc_str: ${ph_core_ver%-*}${COL_NC}"
+            lte_ver_str="$lte_str: ${ph_lte_ver%-*}${COL_NC}"
+            ftl_ver_str="$ftl_str: ${ph_ftl_ver%-*}${COL_NC}"
+        else
+            phc_ver_str="$phc_str: $api_str${COL_NC}"
+        fi
+
+        # Get refresh number
+        if [[ "$*" == *"-r"* ]]; then
+            num="$*"
+            num="${num/*-r /}"
+            num="${num/ */}"
+            num_str="Refresh set for every $num seconds"
+        else
+            num_str=""
+        fi
+
+        clear
+
+        # Remove exit message heading on third refresh
+        if [[ "$count" -le 2 ]] && [[ "$*" != *"-e"* ]]; then
+            echo -e " ${COL_LIGHT_GREEN}Pi-hole Chronometer${COL_NC}
+            $num_str
+            ${COL_LIGHT_RED}Press Ctrl-C to exit${COL_NC}
+            ${COL_DARK_GRAY}$scr_line_str${COL_NC}"
+        else
+        echo -e "|¯¯¯(¯)_|¯|_  ___|¯|___$phc_ver_str| ¯_/¯|_| ' \\/ _ \\ / -_)$lte_ver_str|_| |_| |_||_\\___/_\\___|$ftl_ver_str ${COL_DARK_GRAY}$scr_line_str${COL_NC}"
+        fi
+
+        printFunc "  Hostname: " "$sys_name" "$host_info"
+        printFunc "    Uptime: " "$sys_uptime" "$sys_info"
+        printFunc " Task Load: " "$sys_loadavg" "$sys_info2"
+        printFunc " CPU usage: " "$cpu_perc%" "$cpu_info"
+        printFunc " RAM usage: " "$ram_perc%" "$ram_info"
+        printFunc " HDD usage: " "$disk_perc" "$disk_info"
+
+        if [[ "$scr_lines" -gt 17 ]] && [[ "$chrono_width" != "small" ]]; then
+            printFunc "  LAN addr: " "${IPV4_ADDRESS/\/*/}" "$lan_info"
+        fi
+
+        if [[ "$DHCP_ACTIVE" == "true" ]]; then
+            printFunc "DHCP usage: " "$ph_dhcp_percent%" "$dhcp_info"
+        fi
+
+        printFunc "   Pi-hole: " "$ph_status" "$ph_info"
+        printFunc " Ads Today: " "$ads_percentage_today%" "$ads_info"
+        printFunc "Local Qrys: " "$queries_cached_percentage%" "$dns_info"
+
+        printFunc "   Blocked: " "$recent_blocked"
+        printFunc "Top Advert: " "$top_ad"
+
+        # Provide more stats on screens with more lines
+        if [[ "$scr_lines" -eq 17 ]]; then
+            if [[ "$DHCP_ACTIVE" == "true" ]]; then
+                printFunc "Top Domain: " "$top_domain" "last"
+            else
+                print_client="true"
+            fi
+        else
+            print_client="true"
+        fi
+
+        if [[ -n "$print_client" ]]; then
+            printFunc "Top Domain: " "$top_domain"
+            printFunc "Top Client: " "$top_client" "last"
+        fi
+
+        # Handle exit/refresh options
+        if [[ "$*" == *"-e"* ]]; then
+            exit 0
+        else
+            if [[ "$*" == *"-r"* ]]; then
+                sleep "$num"
+            else
+                sleep 5
+            fi
+        fi
+
+    done
 }
 
 jsonFunc() {
-  get_ftl_stats "json"
-  echo "{\"domains_being_blocked\":${domains_being_blocked_raw},\"dns_queries_today\":${dns_queries_today_raw},\"ads_blocked_today\":${ads_blocked_today_raw},\"ads_percentage_today\":${ads_percentage_today_raw}}"
+    get_ftl_stats "json"
+    echo "{\"domains_being_blocked\":${domains_being_blocked_raw},\"dns_queries_today\":${dns_queries_today_raw},\"ads_blocked_today\":${ads_blocked_today_raw},\"ads_percentage_today\":${ads_percentage_today_raw}}"
 }
 
 helpFunc() {
     if [[ "$1" == "?" ]]; then
-      echo "Unknown option. Please view 'pihole -c --help' for more information"
+        echo "Unknown option. Please view 'pihole -c --help' for more information"
     else
-      echo "Usage: pihole -c [options]
+        echo "Usage: pihole -c [options]
 Example: 'pihole -c -j'
 Calculates stats and displays to an LCD
 
@@ -559,15 +557,15 @@ Options:
 }
 
 if [[ $# = 0 ]]; then
-  chronoFunc
+    chronoFunc
 fi
 
 for var in "$@"; do
-  case "$var" in
-    "-j" | "--json"    ) jsonFunc;;
-    "-h" | "--help"    ) helpFunc;;
-    "-r" | "--refresh" ) chronoFunc "$@";;
-    "-e" | "--exit"    ) chronoFunc "$@";;
-    *                  ) helpFunc "?";;
-  esac
+    case "$var" in
+        "-j" | "--json"    ) jsonFunc;;
+        "-h" | "--help"    ) helpFunc;;
+        "-r" | "--refresh" ) chronoFunc "$@";;
+        "-e" | "--exit"    ) chronoFunc "$@";;
+        *                  ) helpFunc "?";;
+    esac
 done
diff --git a/advanced/Scripts/list.sh b/advanced/Scripts/list.sh
index 1d96ea3c8a3cf977c9a2f19fe27156fbc398fc41..87e4ab449d54151d4c34d5f5010e3a9054970b32 100755
--- a/advanced/Scripts/list.sh
+++ b/advanced/Scripts/list.sh
@@ -13,10 +13,11 @@ basename=pihole
 piholeDir=/etc/"${basename}"
 whitelist="${piholeDir}"/whitelist.txt
 blacklist="${piholeDir}"/blacklist.txt
-readonly wildcardlist="/etc/dnsmasq.d/03-pihole-wildcard.conf"
+readonly regexlist="/etc/pihole/regex.list"
 reload=false
 addmode=true
 verbose=true
+wildcard=false
 
 domList=()
 
@@ -28,16 +29,19 @@ source ${colfile}
 
 
 helpFunc() {
-  if [[ "${listMain}" == "${whitelist}" ]]; then
-    param="w"
-    type="white"
-  elif [[ "${listMain}" == "${wildcardlist}" ]]; then
-    param="wild"
-    type="wildcard black"
-  else
-    param="b"
-    type="black"
-  fi
+    if [[ "${listMain}" == "${whitelist}" ]]; then
+        param="w"
+        type="white"
+    elif [[ "${listMain}" == "${regexlist}" && "${wildcard}" == true ]]; then
+        param="-wild"
+        type="wildcard black"
+    elif [[ "${listMain}" == "${regexlist}" ]]; then
+        param="-regex"
+        type="regex black"
+    else
+        param="b"
+        type="black"
+    fi
 
     echo "Usage: pihole -${param} [options] <domain> <domain2 ...>
 Example: 'pihole -${param} site.com', or 'pihole -${param} site1.com site2.com'
@@ -55,212 +59,216 @@ Options:
 }
 
 EscapeRegexp() {
-  # This way we may safely insert an arbitrary
-  # string in our regular expressions
-  # Also remove leading "." if present
-  echo $* | sed 's/^\.*//' | sed "s/[]\.|$(){}?+*^]/\\\\&/g" | sed "s/\\//\\\\\//g"
+    # This way we may safely insert an arbitrary
+    # string in our regular expressions
+    # This sed is intentionally executed in three steps to ease maintainability
+    # The first sed removes any amount of leading dots
+    echo $* | sed 's/^\.*//' | sed "s/[]\.|$(){}?+*^]/\\\\&/g" | sed "s/\\//\\\\\//g"
 }
 
 HandleOther() {
-  # Convert to lowercase
-  domain="${1,,}"
-
-  # Check validity of domain
-  if [[ "${#domain}" -le 253 ]]; then
-    validDomain=$(grep -P "^((-|_)*[a-z\d]((-|_)*[a-z\d])*(-|_)*)(\.(-|_)*([a-z\d]((-|_)*[a-z\d])*))*$" <<< "${domain}") # Valid chars check
-    validDomain=$(grep -P "^[^\.]{1,63}(\.[^\.]{1,63})*$" <<< "${validDomain}") # Length of each label
-  fi
-
-  if [[ -n "${validDomain}" ]]; then
-    domList=("${domList[@]}" ${validDomain})
-  else
-    echo -e "  ${CROSS} ${domain} is not a valid argument or domain name!"
-  fi
+    # Convert to lowercase
+    domain="${1,,}"
+
+    # Check validity of domain (don't check for regex entries)
+    if [[ "${#domain}" -le 253 ]]; then
+        if [[ "${listMain}" == "${regexlist}" && "${wildcard}" == false ]]; then
+            validDomain="${domain}"
+        else
+            validDomain=$(grep -P "^((-|_)*[a-z\\d]((-|_)*[a-z\\d])*(-|_)*)(\\.(-|_)*([a-z\\d]((-|_)*[a-z\\d])*))*$" <<< "${domain}") # Valid chars check
+            validDomain=$(grep -P "^[^\\.]{1,63}(\\.[^\\.]{1,63})*$" <<< "${validDomain}") # Length of each label
+        fi
+    fi
+
+    if [[ -n "${validDomain}" ]]; then
+        domList=("${domList[@]}" ${validDomain})
+    else
+        echo -e "  ${CROSS} ${domain} is not a valid argument or domain name!"
+    fi
 }
 
 PoplistFile() {
-  # Check whitelist file exists, and if not, create it
-  if [[ ! -f "${whitelist}" ]]; then
-    touch "${whitelist}"
-  fi
-
-  # Check blacklist file exists, and if not, create it
-  if [[ ! -f "${blacklist}" ]]; then
-    touch "${blacklist}"
-  fi
-
-  for dom in "${domList[@]}"; do
-      # Logic: If addmode then add to desired list and remove from the other; if delmode then remove from desired list but do not add to the other
-    if ${addmode}; then
-      AddDomain "${dom}" "${listMain}"
-      RemoveDomain "${dom}" "${listAlt}"
-      if [[ "${listMain}" == "${whitelist}" || "${listMain}" == "${blacklist}" ]]; then
-        RemoveDomain "${dom}" "${wildcardlist}"
-      fi
-    else
-      RemoveDomain "${dom}" "${listMain}"
+    # Check whitelist file exists, and if not, create it
+    if [[ ! -f "${whitelist}" ]]; then
+        touch "${whitelist}"
+    fi
+
+    # Check blacklist file exists, and if not, create it
+    if [[ ! -f "${blacklist}" ]]; then
+        touch "${blacklist}"
     fi
+
+    for dom in "${domList[@]}"; do
+        # Logic: If addmode then add to desired list and remove from the other; if delmode then remove from desired list but do not add to the other
+        if ${addmode}; then
+            AddDomain "${dom}" "${listMain}"
+            RemoveDomain "${dom}" "${listAlt}"
+        else
+            RemoveDomain "${dom}" "${listMain}"
+        fi
   done
 }
 
 AddDomain() {
-  list="$2"
-  domain=$(EscapeRegexp "$1")
-
-  [[ "${list}" == "${whitelist}" ]] && listname="whitelist"
-  [[ "${list}" == "${blacklist}" ]] && listname="blacklist"
-  [[ "${list}" == "${wildcardlist}" ]] && listname="wildcard blacklist"
-
-  if [[ "${list}" == "${whitelist}" || "${list}" == "${blacklist}" ]]; then
-    [[ "${list}" == "${whitelist}" && -z "${type}" ]] && type="--whitelist-only"
-    [[ "${list}" == "${blacklist}" && -z "${type}" ]] && type="--blacklist-only"
-    bool=true
-    # Is the domain in the list we want to add it to?
-    grep -Ex -q "${domain}" "${list}" > /dev/null 2>&1 || bool=false
-
-    if [[ "${bool}" == false ]]; then
-      # Domain not found in the whitelist file, add it!
-      if [[ "${verbose}" == true ]]; then
-      echo -e "  ${INFO} Adding $1 to $listname..."
-      fi
-      reload=true
-      # Add it to the list we want to add it to
-      echo "$1" >> "${list}"
-    else
-      if [[ "${verbose}" == true ]]; then
-        echo -e "  ${INFO} ${1} already exists in ${listname}, no need to add!"
-      fi
+    list="$2"
+    domain=$(EscapeRegexp "$1")
+
+    [[ "${list}" == "${whitelist}" ]] && listname="whitelist"
+    [[ "${list}" == "${blacklist}" ]] && listname="blacklist"
+
+    if [[ "${list}" == "${whitelist}" || "${list}" == "${blacklist}" ]]; then
+        [[ "${list}" == "${whitelist}" && -z "${type}" ]] && type="--whitelist-only"
+        [[ "${list}" == "${blacklist}" && -z "${type}" ]] && type="--blacklist-only"
+        bool=true
+        # Is the domain in the list we want to add it to?
+        grep -Ex -q "${domain}" "${list}" > /dev/null 2>&1 || bool=false
+
+        if [[ "${bool}" == false ]]; then
+            # Domain not found in the whitelist file, add it!
+            if [[ "${verbose}" == true ]]; then
+                echo -e "  ${INFO} Adding ${1} to ${listname}..."
+            fi
+            reload=true
+            # Add it to the list we want to add it to
+            echo "$1" >> "${list}"
+        else
+            if [[ "${verbose}" == true ]]; then
+                echo -e "  ${INFO} ${1} already exists in ${listname}, no need to add!"
+            fi
+        fi
+    elif [[ "${list}" == "${regexlist}" ]]; then
+        [[ -z "${type}" ]] && type="--wildcard-only"
+        bool=true
+        domain="${1}"
+
+        [[ "${wildcard}" == true ]] && domain="(^|\\.)${domain//\./\\.}$"
+
+        # Is the domain in the list?
+        # Search only for exactly matching lines
+        grep -Fx "${domain}" "${regexlist}" > /dev/null 2>&1 || bool=false
+
+        if [[ "${bool}" == false ]]; then
+            if [[ "${verbose}" == true ]]; then
+                echo -e "  ${INFO} Adding ${domain} to regex list..."
+            fi
+            reload="restart"
+            echo "$domain" >> "${regexlist}"
+        else
+            if [[ "${verbose}" == true ]]; then
+                echo -e "  ${INFO} ${domain} already exists in regex list, no need to add!"
+            fi
+        fi
     fi
-  elif [[ "${list}" == "${wildcardlist}" ]]; then
-    source "${piholeDir}/setupVars.conf"
-    # Remove the /* from the end of the IP addresses
-    IPV4_ADDRESS=${IPV4_ADDRESS%/*}
-    IPV6_ADDRESS=${IPV6_ADDRESS%/*}
-    [[ -z "${type}" ]] && type="--wildcard-only"
-    bool=true
-    # Is the domain in the list?
-    grep -e "address=\/${domain}\/" "${wildcardlist}" > /dev/null 2>&1 || bool=false
-
-    if [[ "${bool}" == false ]]; then
-      if [[ "${verbose}" == true ]]; then
-      echo -e "  ${INFO} Adding $1 to wildcard blacklist..."
-      fi
-      reload="restart"
-      echo "address=/$1/${IPV4_ADDRESS}" >> "${wildcardlist}"
-      if [[ "${#IPV6_ADDRESS}" > 0 ]]; then
-        echo "address=/$1/${IPV6_ADDRESS}" >> "${wildcardlist}"
-      fi
-    else
-      if [[ "${verbose}" == true ]]; then
-        echo -e "  ${INFO} ${1} already exists in wildcard blacklist, no need to add!"
-      fi
-    fi
-  fi
 }
 
 RemoveDomain() {
-  list="$2"
-  domain=$(EscapeRegexp "$1")
-
-  [[ "${list}" == "${whitelist}" ]] && listname="whitelist"
-  [[ "${list}" == "${blacklist}" ]] && listname="blacklist"
-  [[ "${list}" == "${wildcardlist}" ]] && listname="wildcard blacklist"
-
-  if [[ "${list}" == "${whitelist}" || "${list}" == "${blacklist}" ]]; then
-    bool=true
-    [[ "${list}" == "${whitelist}" && -z "${type}" ]] && type="--whitelist-only"
-    [[ "${list}" == "${blacklist}" && -z "${type}" ]] && type="--blacklist-only"
-    # Is it in the list? Logic follows that if its whitelisted it should not be blacklisted and vice versa
-    grep -Ex -q "${domain}" "${list}" > /dev/null 2>&1 || bool=false
-    if [[ "${bool}" == true ]]; then
-      # Remove it from the other one
-      echo -e "  ${INFO} Removing $1 from $listname..."
-      # /I flag: search case-insensitive
-      sed -i "/${domain}/Id" "${list}"
-      reload=true
-    else
-      if [[ "${verbose}" == true ]]; then
-        echo -e "  ${INFO} ${1} does not exist in ${listname}, no need to remove!"
-      fi
+    list="$2"
+    domain=$(EscapeRegexp "$1")
+
+    [[ "${list}" == "${whitelist}" ]] && listname="whitelist"
+    [[ "${list}" == "${blacklist}" ]] && listname="blacklist"
+
+    if [[ "${list}" == "${whitelist}" || "${list}" == "${blacklist}" ]]; then
+        bool=true
+        [[ "${list}" == "${whitelist}" && -z "${type}" ]] && type="--whitelist-only"
+        [[ "${list}" == "${blacklist}" && -z "${type}" ]] && type="--blacklist-only"
+        # Is it in the list? Logic follows that if its whitelisted it should not be blacklisted and vice versa
+        grep -Ex -q "${domain}" "${list}" > /dev/null 2>&1 || bool=false
+        if [[ "${bool}" == true ]]; then
+            # Remove it from the other one
+            echo -e "  ${INFO} Removing $1 from ${listname}..."
+            # /I flag: search case-insensitive
+            sed -i "/${domain}/Id" "${list}"
+            reload=true
+        else
+            if [[ "${verbose}" == true ]]; then
+                echo -e "  ${INFO} ${1} does not exist in ${listname}, no need to remove!"
+            fi
+        fi
+    elif [[ "${list}" == "${regexlist}" ]]; then
+        [[ -z "${type}" ]] && type="--wildcard-only"
+        domain="${1}"
+
+        [[ "${wildcard}" == true ]] && domain="(^|\\.)${domain//\./\\.}$"
+
+        bool=true
+        # Is it in the list?
+        grep -Fx "${domain}" "${regexlist}" > /dev/null 2>&1 || bool=false
+        if [[ "${bool}" == true ]]; then
+            # Remove it from the other one
+            echo -e "  ${INFO} Removing $domain from regex list..."
+            local lineNumber
+            lineNumber=$(grep -Fnx "$domain" "${list}" | cut -f1 -d:)
+            sed -i "${lineNumber}d" "${list}"
+            reload=true
+        else
+            if [[ "${verbose}" == true ]]; then
+                echo -e "  ${INFO} ${domain} does not exist in regex list, no need to remove!"
+            fi
+        fi
     fi
-  elif [[ "${list}" == "${wildcardlist}" ]]; then
-    [[ -z "${type}" ]] && type="--wildcard-only"
-    bool=true
-    # Is it in the list?
-    grep -e "address=\/${domain}\/" "${wildcardlist}" > /dev/null 2>&1 || bool=false
-    if [[ "${bool}" == true ]]; then
-      # Remove it from the other one
-      echo -e "  ${INFO} Removing $1 from $listname..."
-      # /I flag: search case-insensitive
-      sed -i "/address=\/${domain}/Id" "${list}"
-      reload=true
-    else
-      if [[ "${verbose}" == true ]]; then
-        echo -e "  ${INFO} ${1} does not exist in ${listname}, no need to remove!"
-      fi
-    fi
-  fi
 }
 
 # Update Gravity
 Reload() {
-  echo ""
-  pihole -g --skip-download "${type:-}"
+    echo ""
+    pihole -g --skip-download "${type:-}"
 }
 
 Displaylist() {
-  if [[ -f ${listMain} ]]; then
-    if [[ "${listMain}" == "${whitelist}" ]]; then
-      string="gravity resistant domains"
+    if [[ -f ${listMain} ]]; then
+        if [[ "${listMain}" == "${whitelist}" ]]; then
+            string="gravity resistant domains"
+        else
+            string="domains caught in the sinkhole"
+        fi
+        verbose=false
+        echo -e "Displaying $string:\n"
+        count=1
+        while IFS= read -r RD || [ -n "${RD}" ]; do
+            echo "  ${count}: ${RD}"
+            count=$((count+1))
+        done < "${listMain}"
     else
-      string="domains caught in the sinkhole"
+        echo -e "  ${COL_LIGHT_RED}${listMain} does not exist!${COL_NC}"
     fi
-    verbose=false
-    echo -e "Displaying $string:\n"
-    count=1
-    while IFS= read -r RD; do
-      echo "  ${count}: ${RD}"
-      count=$((count+1))
-    done < "${listMain}"
-  else
-    echo -e "  ${COL_LIGHT_RED}${listMain} does not exist!${COL_NC}"
-  fi
-  exit 0;
+    exit 0;
 }
 
 NukeList() {
-  if [[ -f "${listMain}" ]]; then
-    # Back up original list
-    cp "${listMain}" "${listMain}.bck~"
-    # Empty out file
-    echo "" > "${listMain}"
-  fi
+    if [[ -f "${listMain}" ]]; then
+        # Back up original list
+        cp "${listMain}" "${listMain}.bck~"
+        # Empty out file
+        echo "" > "${listMain}"
+    fi
 }
 
 for var in "$@"; do
-  case "${var}" in
-    "-w" | "whitelist"   ) listMain="${whitelist}"; listAlt="${blacklist}";;
-    "-b" | "blacklist"   ) listMain="${blacklist}"; listAlt="${whitelist}";;
-    "-wild" | "wildcard" ) listMain="${wildcardlist}";;
-    "-nr"| "--noreload"  ) reload=false;;
-    "-d" | "--delmode"   ) addmode=false;;
-    "-q" | "--quiet"     ) verbose=false;;
-    "-h" | "--help"      ) helpFunc;;
-    "-l" | "--list"      ) Displaylist;;
-    "--nuke"             ) NukeList;;
-    *                    ) HandleOther "${var}";;
-  esac
+    case "${var}" in
+        "-w" | "whitelist"   ) listMain="${whitelist}"; listAlt="${blacklist}";;
+        "-b" | "blacklist"   ) listMain="${blacklist}"; listAlt="${whitelist}";;
+        "--wild" | "wildcard" ) listMain="${regexlist}"; wildcard=true;;
+        "--regex" | "regex"   ) listMain="${regexlist}";;
+        "-nr"| "--noreload"  ) reload=false;;
+        "-d" | "--delmode"   ) addmode=false;;
+        "-q" | "--quiet"     ) verbose=false;;
+        "-h" | "--help"      ) helpFunc;;
+        "-l" | "--list"      ) Displaylist;;
+        "--nuke"             ) NukeList;;
+        *                    ) HandleOther "${var}";;
+    esac
 done
 
 shift
 
 if [[ $# = 0 ]]; then
-  helpFunc
+    helpFunc
 fi
 
 PoplistFile
 
 if [[ "${reload}" != false ]]; then
-  # Ensure that "restart" is used for Wildcard updates
-  Reload "${reload}"
+    # Ensure that "restart" is used for Wildcard updates
+    Reload "${reload}"
 fi
diff --git a/advanced/Scripts/piholeCheckout.sh b/advanced/Scripts/piholeCheckout.sh
index 21919ddf3d854fe6b9bc9d4f315aff22dfd36327..977d1552f0d47cf31d059ee863666b999346b82d 100644
--- a/advanced/Scripts/piholeCheckout.sh
+++ b/advanced/Scripts/piholeCheckout.sh
@@ -17,346 +17,179 @@ source "${PI_HOLE_FILES_DIR}/automated install/basic-install.sh"
 # piholeGitURL set in basic-install.sh
 # is_repo() sourced from basic-install.sh
 # setupVars set in basic-install.sh
+# check_download_exists sourced from basic-install.sh
+# fully_fetch_repo sourced from basic-install.sh
+# get_available_branches sourced from basic-install.sh
+# fetch_checkout_pull_branch sourced from basic-install.sh
+# checkout_pull_branch sourced from basic-install.sh
 
 source "${setupVars}"
-update="false"
-
-coltable="/opt/pihole/COL_TABLE"
-source ${coltable}
-
-check_download_exists() {
-  status=$(curl --head --silent "https://ftl.pi-hole.net/${1}" | head -n 1)
-  if grep -q "404" <<< "$status"; then
-    return 1
-  else
-    return 0
-  fi
-}
-
-FTLinstall() {
-  # Download and install FTL binary
-  local binary
-  binary="${1}"
-  local path
-  path="${2}"
-  local str
-  str="Installing FTL"
-  echo -ne "  ${INFO} ${str}..."
-
-  if curl -sSL --fail "https://ftl.pi-hole.net/${path}" -o "/tmp/${binary}"; then
-    # Get sha1 of the binary we just downloaded for verification.
-    curl -sSL --fail "https://ftl.pi-hole.net/${path}.sha1" -o "/tmp/${binary}.sha1"
-    # Check if we just downloaded text, or a binary file.
-    cd /tmp || return 1
-    if sha1sum --status --quiet -c "${binary}".sha1; then
-      echo -n "transferred... "
-      stop_service pihole-FTL &> /dev/null
-      install -T -m 0755 "/tmp/${binary}" "/usr/bin/pihole-FTL"
-      rm "/tmp/${binary}" "/tmp/${binary}.sha1"
-      start_service pihole-FTL &> /dev/null
-      echo -e "${OVER}  ${TICK} ${str}"
-      return 0
-    else
-      echo -e "${OVER}  ${CROSS} ${str}"
-      echo -e "  ${COL_LIGHT_RED}Error: Download of binary from ftl.pi-hole.net failed${COL_NC}"
-      return 1
-    fi
-  else
-    echo -e "${OVER}  ${CROSS} ${str}"
-    echo -e "  ${COL_LIGHT_RED}Error: URL not found${COL_NC}"
-  fi
-}
-
-get_binary_name() {
-  local machine
-  machine=$(uname -m)
-
-  local str
-  str="Detecting architecture"
-  echo -ne "  ${INFO} ${str}..."
-  if [[ "${machine}" == "arm"* || "${machine}" == *"aarch"* ]]; then
-    # ARM
-    local rev
-    rev=$(uname -m | sed "s/[^0-9]//g;")
-    local lib
-    lib=$(ldd /bin/ls | grep -E '^\s*/lib' | awk '{ print $1 }')
-    if [[ "${lib}" == "/lib/ld-linux-aarch64.so.1" ]]; then
-      echo -e "${OVER}  ${TICK} Detected ARM-aarch64 architecture"
-      binary="pihole-FTL-aarch64-linux-gnu"
-    elif [[ "${lib}" == "/lib/ld-linux-armhf.so.3" ]]; then
-      if [[ "$rev" -gt "6" ]]; then
-        echo -e "${OVER}  ${TICK} Detected ARM-hf architecture (armv7+)"
-        binary="pihole-FTL-arm-linux-gnueabihf"
-      else
-        echo -e "${OVER}  ${TICK} Detected ARM-hf architecture (armv6 or lower) Using ARM binary"
-        binary="pihole-FTL-arm-linux-gnueabi"
-      fi
-    else
-      echo -e "${OVER}  ${TICK} Detected ARM architecture"
-      binary="pihole-FTL-arm-linux-gnueabi"
-    fi
-  elif [[ "${machine}" == "ppc" ]]; then
-    # PowerPC
-    echo -e "${OVER}  ${TICK} Detected PowerPC architecture"
-    binary="pihole-FTL-powerpc-linux-gnu"
-  elif [[ "${machine}" == "x86_64" ]]; then
-    # 64bit
-    echo -e "${OVER}  ${TICK} Detected x86_64 architecture"
-    binary="pihole-FTL-linux-x86_64"
-  else
-    # Something else - we try to use 32bit executable and warn the user
-    if [[ ! "${machine}" == "i686" ]]; then
-      echo -e "${OVER}  ${CROSS} ${str}...
-      ${COL_LIGHT_RED}Not able to detect architecture (unknown: ${machine}), trying 32bit executable
-      Contact support if you experience issues (e.g: FTL not running)${COL_NC}"
-    else
-      echo -e "${OVER}  ${TICK} Detected 32bit (i686) architecture"
-    fi
-    binary="pihole-FTL-linux-x86_32"
-  fi
-}
-
-fully_fetch_repo() {
-  # Add upstream branches to shallow clone
-  local directory="${1}"
-
-  cd "${directory}" || return 1
-  if is_repo "${directory}"; then
-    git remote set-branches origin '*' || return 1
-    git fetch --quiet || return 1
-  else
-    return 1
-  fi
-  return 0
-}
-
-get_available_branches() {
-  # Return available branches
-  local directory
-  directory="${1}"
-  local output
-
-  cd "${directory}" || return 1
-  # Get reachable remote branches, but store STDERR as STDOUT variable
-  output=$( { git remote show origin | grep 'tracked' | sed 's/tracked//;s/ //g'; } 2>&1 )
-  echo "$output"
-  return
-}
-
-fetch_checkout_pull_branch() {
-  # Check out specified branch
-  local directory
-  directory="${1}"
-  local branch
-  branch="${2}"
-
-  # Set the reference for the requested branch, fetch, check it put and pull it
-  cd "${directory}" || return 1
-  git remote set-branches origin "${branch}" || return 1
-  git stash --all --quiet &> /dev/null || true
-  git clean --quiet --force -d || true
-  git fetch --quiet || return 1
-  checkout_pull_branch "${directory}" "${branch}" || return 1
-}
-
-checkout_pull_branch() {
-  # Check out specified branch
-  local directory
-  directory="${1}"
-  local branch
-  branch="${2}"
-  local oldbranch
-
-  cd "${directory}" || return 1
-
-  oldbranch="$(git symbolic-ref HEAD)"
-
-  str="Switching to branch: '${branch}' from '${oldbranch}'"
-  echo -ne "  ${INFO} $str"
-  git checkout "${branch}" --quiet || return 1
-  echo -e "${OVER}  ${TICK} $str"
-
-
-  if [[ "$(git diff "${oldbranch}" | grep -c "^")" -gt "0" ]]; then
-    update="true"
-  fi
-
-  git_pull=$(git pull || return 1)
-
-  if [[ "$git_pull" == *"up-to-date"* ]]; then
-    echo -e "  ${INFO} ${git_pull}"
-  else
-    echo -e "$git_pull\\n"
-  fi
-
-  return 0
-}
 
 warning1() {
-  echo "  Please note that changing branches severely alters your Pi-hole subsystems"
-  echo "  Features that work on the master branch, may not on a development branch"
-  echo -e "  ${COL_LIGHT_RED}This feature is NOT supported unless a Pi-hole developer explicitly asks!${COL_NC}"
-  read -r -p "  Have you read and understood this? [y/N] " response
-  case "${response}" in
-  [yY][eE][sS]|[yY])
-    echo ""
-    return 0
-    ;;
-  *)
-    echo -e "\\n  ${INFO} Branch change has been cancelled"
-    return 1
-    ;;
-  esac
+    echo "  Please note that changing branches severely alters your Pi-hole subsystems"
+    echo "  Features that work on the master branch, may not on a development branch"
+    echo -e "  ${COL_LIGHT_RED}This feature is NOT supported unless a Pi-hole developer explicitly asks!${COL_NC}"
+    read -r -p "  Have you read and understood this? [y/N] " response
+    case "${response}" in
+        [yY][eE][sS]|[yY])
+            echo ""
+            return 0
+            ;;
+        *)
+            echo -e "\\n  ${INFO} Branch change has been cancelled"
+            return 1
+            ;;
+    esac
 }
 
 checkout() {
-  local corebranches
-  local webbranches
+    local corebranches
+    local webbranches
 
-  # Avoid globbing
-  set -f
+    # Avoid globbing
+    set -f
 
-  # This is unlikely
-  if ! is_repo "${PI_HOLE_FILES_DIR}" ; then
-    echo -e "  ${COL_LIGHT_RED}Error: Core Pi-hole repo is missing from system!
-  Please re-run install script from https://github.com/pi-hole/pi-hole${COL_NC}"
-    exit 1;
-  fi
-  if [[ "${INSTALL_WEB}" == "true" ]]; then
-    if ! is_repo "${webInterfaceDir}" ; then
-     echo -e "  ${COL_LIGHT_RED}Error: Web Admin repo is missing from system!
-  Please re-run install script from https://github.com/pi-hole/pi-hole${COL_NC}"
-      exit 1;
+    # This is unlikely
+    if ! is_repo "${PI_HOLE_FILES_DIR}" ; then
+        echo -e "  ${COL_LIGHT_RED}Error: Core Pi-hole repo is missing from system!"
+        echo -e "  Please re-run install script from https://github.com/pi-hole/pi-hole${COL_NC}"
+        exit 1;
     fi
-  fi
-
-  if [[ -z "${1}" ]]; then
-    echo -e "  ${COL_LIGHT_RED}Invalid option${COL_NC}
-  Try 'pihole checkout --help' for more information."
-    exit 1
-  fi
-
-  if ! warning1 ; then
-    exit 1
-  fi
-
-  if [[ "${1}" == "dev" ]] ; then
-    # Shortcut to check out development branches
-    echo -e "  ${INFO} Shortcut \"dev\" detected - checking out development / devel branches..."
-    echo ""
-    echo -e "  ${INFO} Pi-hole Core"
-    fetch_checkout_pull_branch "${PI_HOLE_FILES_DIR}" "development" || { echo "  ${CROSS} Unable to pull Core developement branch"; exit 1; }
-    if [[ "${INSTALL_WEB}" == "true" ]]; then
-      echo ""
-      echo -e "  ${INFO} Web interface"
-      fetch_checkout_pull_branch "${webInterfaceDir}" "devel" || { echo "  ${CROSS} Unable to pull Web development branch"; exit 1; }
+    if [[ "${INSTALL_WEB_INTERFACE}" == "true" ]]; then
+        if ! is_repo "${webInterfaceDir}" ; then
+            echo -e "  ${COL_LIGHT_RED}Error: Web Admin repo is missing from system!"
+            echo -e "  Please re-run install script from https://github.com/pi-hole/pi-hole${COL_NC}"
+            exit 1;
+        fi
     fi
-    #echo -e "  ${TICK} Pi-hole Core"
 
-    get_binary_name
-    local path
-    path="development/${binary}"
-    echo "development" > /etc/pihole/ftlbranch
-    FTLinstall "${binary}" "${path}"
-  elif [[ "${1}" == "master" ]] ; then
-    # Shortcut to check out master branches
-    echo -e "  ${INFO} Shortcut \"master\" detected - checking out master branches..."
-    echo -e "  ${INFO} Pi-hole core"
-    fetch_checkout_pull_branch "${PI_HOLE_FILES_DIR}" "master" || { echo "  ${CROSS} Unable to pull Core master branch"; exit 1; }
-    if [[ ${INSTALL_WEB} == "true" ]]; then
-      echo -e "  ${INFO} Web interface"
-      fetch_checkout_pull_branch "${webInterfaceDir}" "master" || { echo "  ${CROSS} Unable to pull Web master branch"; exit 1; }
-    fi
-    #echo -e "  ${TICK} Web Interface"
-    get_binary_name
-    local path
-    path="master/${binary}"
-    echo "master" > /etc/pihole/ftlbranch
-    FTLinstall "${binary}" "${path}"
-  elif [[ "${1}" == "core" ]] ; then
-    str="Fetching branches from ${piholeGitUrl}"
-    echo -ne "  ${INFO} $str"
-    if ! fully_fetch_repo "${PI_HOLE_FILES_DIR}" ; then
-      echo -e "${OVER}  ${CROSS} $str"
-      exit 1
-    fi
-    corebranches=($(get_available_branches "${PI_HOLE_FILES_DIR}"))
-
-    if [[ "${corebranches[*]}" == *"master"* ]]; then
-      echo -e "${OVER}  ${TICK} $str
-  ${INFO} ${#corebranches[@]} branches available for Pi-hole Core"
-    else
-      # Print STDERR output from get_available_branches
-      echo -e "${OVER}  ${CROSS} $str\\n\\n${corebranches[*]}"
-      exit 1
-    fi
-
-    echo ""
-    # Have the user choose the branch they want
-    if ! (for e in "${corebranches[@]}"; do [[ "$e" == "${2}" ]] && exit 0; done); then
-      echo -e "  ${INFO} Requested branch \"${2}\" is not available"
-      echo -e "  ${INFO} Available branches for Core are:"
-      for e in "${corebranches[@]}"; do echo "      - $e"; done
-      exit 1
-    fi
-    checkout_pull_branch "${PI_HOLE_FILES_DIR}" "${2}"
-  elif [[ "${1}" == "web" ]] && [[ "${INSTALL_WEB}" == "true" ]] ; then
-    str="Fetching branches from ${webInterfaceGitUrl}"
-    echo -ne "  ${INFO} $str"
-    if ! fully_fetch_repo "${webInterfaceDir}" ; then
-      echo -e "${OVER}  ${CROSS} $str"
-      exit 1
+    if [[ -z "${1}" ]]; then
+        echo -e "  ${COL_LIGHT_RED}Invalid option${COL_NC}"
+        echo -e "  Try 'pihole checkout --help' for more information."
+        exit 1
     fi
-    webbranches=($(get_available_branches "${webInterfaceDir}"))
 
-    if [[ "${webbranches[*]}" == *"master"* ]]; then
-      echo -e "${OVER}  ${TICK} $str
-  ${INFO} ${#webbranches[@]} branches available for Web Admin"
-    else
-      # Print STDERR output from get_available_branches
-      echo -e "${OVER}  ${CROSS} $str\\n\\n${webbranches[*]}"
-      exit 1
+    if ! warning1 ; then
+        exit 1
     fi
 
-    echo ""
-    # Have the user choose the branch they want
-    if ! (for e in "${webbranches[@]}"; do [[ "$e" == "${2}" ]] && exit 0; done); then
-      echo -e "  ${INFO} Requested branch \"${2}\" is not available"
-      echo -e "  ${INFO} Available branches for Web Admin are:"
-      for e in "${webbranches[@]}"; do echo "      - $e"; done
-      exit 1
-    fi
-    checkout_pull_branch "${webInterfaceDir}" "${2}"
-  elif [[ "${1}" == "ftl" ]] ; then
-    get_binary_name
-    local path
-    path="${2}/${binary}"
+    if [[ "${1}" == "dev" ]] ; then
+        # Shortcut to check out development branches
+        echo -e "  ${INFO} Shortcut \"dev\" detected - checking out development / devel branches..."
+        echo ""
+        echo -e "  ${INFO} Pi-hole Core"
+        fetch_checkout_pull_branch "${PI_HOLE_FILES_DIR}" "development" || { echo "  ${CROSS} Unable to pull Core developement branch"; exit 1; }
+        if [[ "${INSTALL_WEB_INTERFACE}" == "true" ]]; then
+            echo ""
+            echo -e "  ${INFO} Web interface"
+            fetch_checkout_pull_branch "${webInterfaceDir}" "devel" || { echo "  ${CROSS} Unable to pull Web development branch"; exit 1; }
+        fi
+        #echo -e "  ${TICK} Pi-hole Core"
+
+        get_binary_name
+        local path
+        path="development/${binary}"
+        echo "development" > /etc/pihole/ftlbranch
+    elif [[ "${1}" == "master" ]] ; then
+        # Shortcut to check out master branches
+        echo -e "  ${INFO} Shortcut \"master\" detected - checking out master branches..."
+        echo -e "  ${INFO} Pi-hole core"
+        fetch_checkout_pull_branch "${PI_HOLE_FILES_DIR}" "master" || { echo "  ${CROSS} Unable to pull Core master branch"; exit 1; }
+        if [[ ${INSTALL_WEB_INTERFACE} == "true" ]]; then
+            echo -e "  ${INFO} Web interface"
+            fetch_checkout_pull_branch "${webInterfaceDir}" "master" || { echo "  ${CROSS} Unable to pull Web master branch"; exit 1; }
+        fi
+        #echo -e "  ${TICK} Web Interface"
+        get_binary_name
+        local path
+        path="master/${binary}"
+        echo "master" > /etc/pihole/ftlbranch
+    elif [[ "${1}" == "core" ]] ; then
+        str="Fetching branches from ${piholeGitUrl}"
+        echo -ne "  ${INFO} $str"
+        if ! fully_fetch_repo "${PI_HOLE_FILES_DIR}" ; then
+            echo -e "${OVER}  ${CROSS} $str"
+            exit 1
+        fi
+        corebranches=($(get_available_branches "${PI_HOLE_FILES_DIR}"))
+
+        if [[ "${corebranches[*]}" == *"master"* ]]; then
+            echo -e "${OVER}  ${TICK} $str"
+            echo -e "${INFO} ${#corebranches[@]} branches available for Pi-hole Core"
+        else
+            # Print STDERR output from get_available_branches
+            echo -e "${OVER}  ${CROSS} $str\\n\\n${corebranches[*]}"
+            exit 1
+        fi
+
+        echo ""
+        # Have the user choose the branch they want
+        if ! (for e in "${corebranches[@]}"; do [[ "$e" == "${2}" ]] && exit 0; done); then
+            echo -e "  ${INFO} Requested branch \"${2}\" is not available"
+            echo -e "  ${INFO} Available branches for Core are:"
+            for e in "${corebranches[@]}"; do echo "      - $e"; done
+            exit 1
+        fi
+        checkout_pull_branch "${PI_HOLE_FILES_DIR}" "${2}"
+    elif [[ "${1}" == "web" ]] && [[ "${INSTALL_WEB_INTERFACE}" == "true" ]] ; then
+        str="Fetching branches from ${webInterfaceGitUrl}"
+        echo -ne "  ${INFO} $str"
+        if ! fully_fetch_repo "${webInterfaceDir}" ; then
+            echo -e "${OVER}  ${CROSS} $str"
+            exit 1
+        fi
+        webbranches=($(get_available_branches "${webInterfaceDir}"))
+
+        if [[ "${webbranches[*]}" == *"master"* ]]; then
+            echo -e "${OVER}  ${TICK} $str"
+            echo -e "${INFO} ${#webbranches[@]} branches available for Web Admin"
+        else
+            # Print STDERR output from get_available_branches
+            echo -e "${OVER}  ${CROSS} $str\\n\\n${webbranches[*]}"
+            exit 1
+        fi
+
+        echo ""
+        # Have the user choose the branch they want
+        if ! (for e in "${webbranches[@]}"; do [[ "$e" == "${2}" ]] && exit 0; done); then
+            echo -e "  ${INFO} Requested branch \"${2}\" is not available"
+            echo -e "  ${INFO} Available branches for Web Admin are:"
+            for e in "${webbranches[@]}"; do echo "      - $e"; done
+            exit 1
+        fi
+        checkout_pull_branch "${webInterfaceDir}" "${2}"
+    elif [[ "${1}" == "ftl" ]] ; then
+        get_binary_name
+        local path
+        path="${2}/${binary}"
+
+        if check_download_exists "$path"; then
+            echo "  ${TICK} Branch ${2} exists"
+            echo "${2}" > /etc/pihole/ftlbranch
+            FTLinstall "${binary}"
+            start_service pihole-FTL
+            enable_service pihole-FTL
+        else
+            echo "  ${CROSS} Requested branch \"${2}\" is not available"
+            ftlbranches=( $(git ls-remote https://github.com/pi-hole/ftl | grep 'heads' | sed 's/refs\/heads\///;s/ //g' | awk '{print $2}') )
+            echo -e "  ${INFO} Available branches for FTL are:"
+            for e in "${ftlbranches[@]}"; do echo "      - $e"; done
+            exit 1
+        fi
 
-    if check_download_exists "$path"; then
-        echo "  ${TICK} Branch ${2} exists"
-        echo "${2}" > /etc/pihole/ftlbranch
-        FTLinstall "${binary}" "${path}"
     else
-        echo "  ${CROSS} Requested branch \"${2}\" is not available"
-        ftlbranches=( $(git ls-remote https://github.com/pi-hole/ftl | grep 'heads' | sed 's/refs\/heads\///;s/ //g' | awk '{print $2}') )
-        echo -e "  ${INFO} Available branches for FTL are:"
-        for e in "${ftlbranches[@]}"; do echo "      - $e"; done
+        echo -e "  ${INFO} Requested option \"${1}\" is not available"
         exit 1
     fi
 
-  else
-    echo -e "  ${INFO} Requested option \"${1}\" is not available"
-    exit 1
-  fi
-
-  # Force updating everything
-  if [[ ( ! "${1}" == "web" && ! "${1}" == "ftl" ) && "${update}" == "true" ]]; then
-    echo -e "  ${INFO} Running installer to upgrade your installation"
-    if "${PI_HOLE_FILES_DIR}/automated install/basic-install.sh" --unattended; then
-      exit 0
-    else
-      echo -e "  ${COL_LIGHT_RED} Error: Unable to complete update, please contact support${COL_NC}"
-      exit 1
+    # Force updating everything
+    if [[  ! "${1}" == "web" && ! "${1}" == "ftl" ]]; then
+        echo -e "  ${INFO} Running installer to upgrade your installation"
+        if "${PI_HOLE_FILES_DIR}/automated install/basic-install.sh" --unattended; then
+            exit 0
+        else
+            echo -e "  ${COL_LIGHT_RED} Error: Unable to complete update, please contact support${COL_NC}"
+            exit 1
+        fi
     fi
-  fi
 }
diff --git a/advanced/Scripts/piholeDebug.sh b/advanced/Scripts/piholeDebug.sh
index b668af9405dc8fa460c324e459a43be204a9da37..b8377f73dd0c3f743c38db3d6bad63b7115a2621 100755
--- a/advanced/Scripts/piholeDebug.sh
+++ b/advanced/Scripts/piholeDebug.sh
@@ -8,6 +8,7 @@
 # This file is copyright under the latest version of the EUPL.
 # Please see LICENSE file for your rights under this license.
 
+# shellcheck source=/dev/null
 
 # -e option instructs bash to immediately exit if any command [1] has a non-zero exit status
 # -u a reference to any variable you haven't previously defined
@@ -28,16 +29,16 @@ PIHOLE_COLTABLE_FILE="${PIHOLE_SCRIPTS_DIRECTORY}/COL_TABLE"
 if [[ -f ${PIHOLE_COLTABLE_FILE} ]]; then
   source ${PIHOLE_COLTABLE_FILE}
 else
-  COL_NC='\e[0m' # No Color
-  COL_RED='\e[1;91m'
-  COL_GREEN='\e[1;32m'
-  COL_YELLOW='\e[1;33m'
-  COL_PURPLE='\e[1;35m'
-  COL_CYAN='\e[0;36m'
-  TICK="[${COL_GREEN}✓${COL_NC}]"
-  CROSS="[${COL_RED}✗${COL_NC}]"
-  INFO="[i]"
-  OVER="\r\033[K"
+    COL_NC='\e[0m' # No Color
+    COL_RED='\e[1;91m'
+    COL_GREEN='\e[1;32m'
+    COL_YELLOW='\e[1;33m'
+    COL_PURPLE='\e[1;35m'
+    COL_CYAN='\e[0;36m'
+    TICK="[${COL_GREEN}✓${COL_NC}]"
+    CROSS="[${COL_RED}✗${COL_NC}]"
+    INFO="[i]"
+    #OVER="\r\033[K"
 fi
 
 OBFUSCATED_PLACEHOLDER="<DOMAIN OBFUSCATED>"
@@ -74,7 +75,7 @@ WEB_SERVER_LOG_DIRECTORY="${LOG_DIRECTORY}/lighttpd"
 WEB_SERVER_CONFIG_DIRECTORY="/etc/lighttpd"
 HTML_DIRECTORY="/var/www/html"
 WEB_GIT_DIRECTORY="${HTML_DIRECTORY}/admin"
-BLOCK_PAGE_DIRECTORY="${HTML_DIRECTORY}/pihole"
+#BLOCK_PAGE_DIRECTORY="${HTML_DIRECTORY}/pihole"
 
 # Files required by Pi-hole
 # https://discourse.pi-hole.net/t/what-files-does-pi-hole-use/1684
@@ -85,14 +86,14 @@ PIHOLE_DHCP_CONFIG_FILE="${DNSMASQ_D_DIRECTORY}/02-pihole-dhcp.conf"
 PIHOLE_WILDCARD_CONFIG_FILE="${DNSMASQ_D_DIRECTORY}/03-wildcard.conf"
 
 WEB_SERVER_CONFIG_FILE="${WEB_SERVER_CONFIG_DIRECTORY}/lighttpd.conf"
-WEB_SERVER_CUSTOM_CONFIG_FILE="${WEB_SERVER_CONFIG_DIRECTORY}/external.conf"
+#WEB_SERVER_CUSTOM_CONFIG_FILE="${WEB_SERVER_CONFIG_DIRECTORY}/external.conf"
 
 PIHOLE_DEFAULT_AD_LISTS="${PIHOLE_DIRECTORY}/adlists.default"
 PIHOLE_USER_DEFINED_AD_LISTS="${PIHOLE_DIRECTORY}/adlists.list"
 PIHOLE_BLACKLIST_FILE="${PIHOLE_DIRECTORY}/blacklist.txt"
 PIHOLE_BLOCKLIST_FILE="${PIHOLE_DIRECTORY}/gravity.list"
 PIHOLE_INSTALL_LOG_FILE="${PIHOLE_DIRECTORY}/install.log"
-PIHOLE_RAW_BLOCKLIST_FILES=${PIHOLE_DIRECTORY}/list.*
+PIHOLE_RAW_BLOCKLIST_FILES="${PIHOLE_DIRECTORY}/list.*"
 PIHOLE_LOCAL_HOSTS_FILE="${PIHOLE_DIRECTORY}/local.list"
 PIHOLE_LOGROTATE_FILE="${PIHOLE_DIRECTORY}/logrotate"
 PIHOLE_SETUP_VARS_FILE="${PIHOLE_DIRECTORY}/setupVars.conf"
@@ -105,7 +106,7 @@ FTL_PID="${RUN_DIRECTORY}/pihole-FTL.pid"
 FTL_PORT="${RUN_DIRECTORY}/pihole-FTL.port"
 
 PIHOLE_LOG="${LOG_DIRECTORY}/pihole.log"
-PIHOLE_LOG_GZIPS=${LOG_DIRECTORY}/pihole.log.[0-9].*
+PIHOLE_LOG_GZIPS="${LOG_DIRECTORY}/pihole.log.[0-9].*"
 PIHOLE_DEBUG_LOG="${LOG_DIRECTORY}/pihole_debug.log"
 PIHOLE_DEBUG_LOG_SANITIZED="${LOG_DIRECTORY}/pihole_debug-sanitized.log"
 PIHOLE_FTL_LOG="${LOG_DIRECTORY}/pihole-FTL.log"
@@ -115,53 +116,52 @@ PIHOLE_WEB_SERVER_ERROR_LOG_FILE="${WEB_SERVER_LOG_DIRECTORY}/error.log"
 
 # An array of operating system "pretty names" that we officialy support
 # We can loop through the array at any time to see if it matches a value
-SUPPORTED_OS=("Raspbian" "Ubuntu" "Fedora" "Debian" "CentOS")
+#SUPPORTED_OS=("Raspbian" "Ubuntu" "Fedora" "Debian" "CentOS")
 
 # Store Pi-hole's processes in an array for easy use and parsing
 PIHOLE_PROCESSES=( "dnsmasq" "lighttpd" "pihole-FTL" )
 
 # Store the required directories in an array so it can be parsed through
-REQUIRED_DIRECTORIES=(${CORE_GIT_DIRECTORY}
-${CRON_D_DIRECTORY}
-${DNSMASQ_D_DIRECTORY}
-${PIHOLE_DIRECTORY}
-${PIHOLE_SCRIPTS_DIRECTORY}
-${BIN_DIRECTORY}
-${RUN_DIRECTORY}
-${LOG_DIRECTORY}
-${WEB_SERVER_LOG_DIRECTORY}
-${WEB_SERVER_CONFIG_DIRECTORY}
-${HTML_DIRECTORY}
-${WEB_GIT_DIRECTORY}
-${BLOCK_PAGE_DIRECTORY})
+#REQUIRED_DIRECTORIES=("${CORE_GIT_DIRECTORY}"
+#"${CRON_D_DIRECTORY}"
+#"${DNSMASQ_D_DIRECTORY}"
+#"${PIHOLE_DIRECTORY}"
+#"${PIHOLE_SCRIPTS_DIRECTORY}"
+#"${BIN_DIRECTORY}"
+#"${RUN_DIRECTORY}"
+#"${LOG_DIRECTORY}"
+#"${WEB_SERVER_LOG_DIRECTORY}"
+#"${WEB_SERVER_CONFIG_DIRECTORY}"
+#"${HTML_DIRECTORY}"
+#"${WEB_GIT_DIRECTORY}"
+#"${BLOCK_PAGE_DIRECTORY}")
 
 # Store the required directories in an array so it can be parsed through
-mapfile -t array <<< "$var"
-REQUIRED_FILES=(${PIHOLE_CRON_FILE}
-${PIHOLE_DNS_CONFIG_FILE}
-${PIHOLE_DHCP_CONFIG_FILE}
-${PIHOLE_WILDCARD_CONFIG_FILE}
-${WEB_SERVER_CONFIG_FILE}
-${PIHOLE_DEFAULT_AD_LISTS}
-${PIHOLE_USER_DEFINED_AD_LISTS}
-${PIHOLE_BLACKLIST_FILE}
-${PIHOLE_BLOCKLIST_FILE}
-${PIHOLE_INSTALL_LOG_FILE}
-${PIHOLE_RAW_BLOCKLIST_FILES}
-${PIHOLE_LOCAL_HOSTS_FILE}
-${PIHOLE_LOGROTATE_FILE}
-${PIHOLE_SETUP_VARS_FILE}
-${PIHOLE_WHITELIST_FILE}
-${PIHOLE_COMMAND}
-${PIHOLE_COLTABLE_FILE}
-${FTL_PID}
-${FTL_PORT}
-${PIHOLE_LOG}
-${PIHOLE_LOG_GZIPS}
-${PIHOLE_DEBUG_LOG}
-${PIHOLE_FTL_LOG}
-${PIHOLE_WEB_SERVER_ACCESS_LOG_FILE}
-${PIHOLE_WEB_SERVER_ERROR_LOG_FILE})
+REQUIRED_FILES=("${PIHOLE_CRON_FILE}"
+"${PIHOLE_DNS_CONFIG_FILE}"
+"${PIHOLE_DHCP_CONFIG_FILE}"
+"${PIHOLE_WILDCARD_CONFIG_FILE}"
+"${WEB_SERVER_CONFIG_FILE}"
+"${PIHOLE_DEFAULT_AD_LISTS}"
+"${PIHOLE_USER_DEFINED_AD_LISTS}"
+"${PIHOLE_BLACKLIST_FILE}"
+"${PIHOLE_BLOCKLIST_FILE}"
+"${PIHOLE_INSTALL_LOG_FILE}"
+"${PIHOLE_RAW_BLOCKLIST_FILES}"
+"${PIHOLE_LOCAL_HOSTS_FILE}"
+"${PIHOLE_LOGROTATE_FILE}"
+"${PIHOLE_SETUP_VARS_FILE}"
+"${PIHOLE_WHITELIST_FILE}"
+"${PIHOLE_COMMAND}"
+"${PIHOLE_COLTABLE_FILE}"
+"${FTL_PID}"
+"${FTL_PORT}"
+"${PIHOLE_LOG}"
+"${PIHOLE_LOG_GZIPS}"
+"${PIHOLE_DEBUG_LOG}"
+"${PIHOLE_FTL_LOG}"
+"${PIHOLE_WEB_SERVER_ACCESS_LOG_FILE}"
+"${PIHOLE_WEB_SERVER_ERROR_LOG_FILE}")
 
 DISCLAIMER="This process collects information from your Pi-hole, and optionally uploads it to a unique and random directory on tricorder.pi-hole.net.
 
@@ -171,980 +171,1038 @@ NOTE: All log files auto-delete after 48 hours and ONLY the Pi-hole developers c
 "
 
 show_disclaimer(){
-  log_write "${DISCLAIMER}"
+    log_write "${DISCLAIMER}"
 }
 
 source_setup_variables() {
-  # Display the current test that is running
-  log_write "\n${COL_PURPLE}*** [ INITIALIZING ]${COL_NC} Sourcing setup variables"
-  # If the variable file exists,
-  if ls "${PIHOLE_SETUP_VARS_FILE}" 1> /dev/null 2>&1; then
-    log_write "${INFO} Sourcing ${PIHOLE_SETUP_VARS_FILE}...";
-    # source it
-    source ${PIHOLE_SETUP_VARS_FILE}
-  else
-    # If it can't, show an error
-    log_write "${PIHOLE_SETUP_VARS_FILE} ${COL_RED}does not exist or cannot be read.${COL_NC}"
-  fi
+    # Display the current test that is running
+    log_write "\\n${COL_PURPLE}*** [ INITIALIZING ]${COL_NC} Sourcing setup variables"
+    # If the variable file exists,
+    if ls "${PIHOLE_SETUP_VARS_FILE}" 1> /dev/null 2>&1; then
+        log_write "${INFO} Sourcing ${PIHOLE_SETUP_VARS_FILE}...";
+        # source it
+        source ${PIHOLE_SETUP_VARS_FILE}
+    else
+        # If it can't, show an error
+        log_write "${PIHOLE_SETUP_VARS_FILE} ${COL_RED}does not exist or cannot be read.${COL_NC}"
+    fi
 }
 
 make_temporary_log() {
-  # Create a random temporary file for the log
-  TEMPLOG=$(mktemp /tmp/pihole_temp.XXXXXX)
-  # Open handle 3 for templog
-  # https://stackoverflow.com/questions/18460186/writing-outputs-to-log-file-and-console
-  exec 3>"$TEMPLOG"
-  # Delete templog, but allow for addressing via file handle
-  # This lets us write to the log without having a temporary file on the drive, which
-  # is meant to be a security measure so there is not a lingering file on the drive during the debug process
-  rm "$TEMPLOG"
+    # Create a random temporary file for the log
+    TEMPLOG=$(mktemp /tmp/pihole_temp.XXXXXX)
+    # Open handle 3 for templog
+    # https://stackoverflow.com/questions/18460186/writing-outputs-to-log-file-and-console
+    exec 3>"$TEMPLOG"
+    # Delete templog, but allow for addressing via file handle
+    # This lets us write to the log without having a temporary file on the drive, which
+    # is meant to be a security measure so there is not a lingering file on the drive during the debug process
+    rm "$TEMPLOG"
 }
 
 log_write() {
-  # echo arguments to both the log and the console
-  echo -e "${@}" | tee -a /proc/$$/fd/3
+    # echo arguments to both the log and the console
+    echo -e "${@}" | tee -a /proc/$$/fd/3
 }
 
 copy_to_debug_log() {
-  # Copy the contents of file descriptor 3 into the debug log
-  cat /proc/$$/fd/3 > "${PIHOLE_DEBUG_LOG}"
-  # Since we use color codes such as '\e[1;33m', they should be removed before being
-  # uploaded to our server, since it can't properly display in color
-  # This is accomplished by use sed to remove characters matching that patter
-  # The entire file is then copied over to a sanitized version of the log
-  sed 's/\[[0-9;]\{1,5\}m//g' > "${PIHOLE_DEBUG_LOG_SANITIZED}" <<< cat "${PIHOLE_DEBUG_LOG}"
+    # Copy the contents of file descriptor 3 into the debug log
+    cat /proc/$$/fd/3 > "${PIHOLE_DEBUG_LOG}"
+    # Since we use color codes such as '\e[1;33m', they should be removed before being
+    # uploaded to our server, since it can't properly display in color
+    # This is accomplished by use sed to remove characters matching that patter
+    # The entire file is then copied over to a sanitized version of the log
+    sed 's/\[[0-9;]\{1,5\}m//g' > "${PIHOLE_DEBUG_LOG_SANITIZED}" <<< cat "${PIHOLE_DEBUG_LOG}"
 }
 
 initialize_debug() {
-  # Clear the screen so the debug log is readable
-  clear
-  show_disclaimer
-  # Display that the debug process is beginning
-  log_write "${COL_PURPLE}*** [ INITIALIZING ]${COL_NC}"
-  # Timestamp the start of the log
-  log_write "${INFO} $(date "+%Y-%m-%d:%H:%M:%S") debug log has been initialized."
+    # Clear the screen so the debug log is readable
+    clear
+    show_disclaimer
+    # Display that the debug process is beginning
+    log_write "${COL_PURPLE}*** [ INITIALIZING ]${COL_NC}"
+    # Timestamp the start of the log
+    log_write "${INFO} $(date "+%Y-%m-%d:%H:%M:%S") debug log has been initialized."
 }
 
 # This is a function for visually displaying the curent test that is being run.
 # Accepts one variable: the name of what is being diagnosed
 # Colors do not show in the dasboard, but the icons do: [i], [✓], and [✗]
 echo_current_diagnostic() {
-  # Colors are used for visually distinguishing each test in the output
-  # These colors do not show in the GUI, but the formatting will
-  log_write "\n${COL_PURPLE}*** [ DIAGNOSING ]:${COL_NC} ${1}"
+    # Colors are used for visually distinguishing each test in the output
+    # These colors do not show in the GUI, but the formatting will
+    log_write "\\n${COL_PURPLE}*** [ DIAGNOSING ]:${COL_NC} ${1}"
 }
 
 compare_local_version_to_git_version() {
-  # The git directory to check
-  local git_dir="${1}"
-  # The named component of the project (Core or Web)
-  local pihole_component="${2}"
-  # If we are checking the Core versions,
-  if [[ "${pihole_component}" == "Core" ]]; then
-    # We need to search for "Pi-hole" when using pihole -v
-    local search_term="Pi-hole"
-  elif [[ "${pihole_component}" == "Web" ]]; then
-    # We need to search for "AdminLTE" so store it in a variable as well
-    local search_term="AdminLTE"
-  fi
-  # Display what we are checking
-  echo_current_diagnostic "${pihole_component} version"
-  # Store the error message in a variable in case we want to change and/or reuse it
-  local error_msg="git status failed"
-  # If the pihole git directory exists,
-  if [[ -d "${git_dir}" ]]; then
-    # move into it
-    cd "${git_dir}" || \
-    # If not, show an error
-    log_write "${COL_RED}Could not cd into ${git_dir}$COL_NC"
-    if git status &> /dev/null; then
-      # The current version the user is on
-      local remote_version
-      remote_version=$(git describe --tags --abbrev=0);
-      # What branch they are on
-      local remote_branch
-      remote_branch=$(git rev-parse --abbrev-ref HEAD);
-      # The commit they are on
-      local remote_commit
-      remote_commit=$(git describe --long --dirty --tags --always)
-      # echo this information out to the user in a nice format
-      # If the current version matches what pihole -v produces, the user is up-to-date
-      if [[ "${remote_version}" == "$(pihole -v | awk '/${search_term}/ {print $6}' | cut -d ')' -f1)" ]]; then
-        log_write "${TICK} ${pihole_component}: ${COL_GREEN}${remote_version}${COL_NC}"
-      # If not,
-      else
-        # echo the current version in yellow, signifying it's something to take a look at, but not a critical error
-        # Also add a URL to an FAQ
-        log_write "${INFO} ${pihole_component}: ${COL_YELLOW}${remote_version:-Untagged}${COL_NC} (${FAQ_UPDATE_PI_HOLE})"
-      fi
-
-      # If the repo is on the master branch, they are on the stable codebase
-      if [[ "${remote_branch}" == "master" ]]; then
-        # so the color of the text is green
-        log_write "${INFO} Branch: ${COL_GREEN}${remote_branch}${COL_NC}"
-      # If it is any other branch, they are in a developement branch
-      else
-        # So show that in yellow, signifying it's something to take a look at, but not a critical error
-        log_write "${INFO} Branch: ${COL_YELLOW}${remote_branch:-Detached}${COL_NC} (${FAQ_CHECKOUT_COMMAND})"
-      fi
-        # echo the current commit
-        log_write "${INFO} Commit: ${remote_commit}"
-    # If git status failed,
+    # The git directory to check
+    local git_dir="${1}"
+    # The named component of the project (Core or Web)
+    local pihole_component="${2}"
+    # If we are checking the Core versions,
+    if [[ "${pihole_component}" == "Core" ]]; then
+        # We need to search for "Pi-hole" when using pihole -v
+        local search_term="Pi-hole"
+    elif [[ "${pihole_component}" == "Web" ]]; then
+        # We need to search for "AdminLTE" so store it in a variable as well
+        #shellcheck disable=2034
+        local search_term="AdminLTE"
+    fi
+    # Display what we are checking
+    echo_current_diagnostic "${pihole_component} version"
+    # Store the error message in a variable in case we want to change and/or reuse it
+    local error_msg="git status failed"
+    # If the pihole git directory exists,
+    if [[ -d "${git_dir}" ]]; then
+        # move into it
+        cd "${git_dir}" || \
+        # If not, show an error
+        log_write "${COL_RED}Could not cd into ${git_dir}$COL_NC"
+        if git status &> /dev/null; then
+            # The current version the user is on
+            local remote_version
+            remote_version=$(git describe --tags --abbrev=0);
+            # What branch they are on
+            local remote_branch
+            remote_branch=$(git rev-parse --abbrev-ref HEAD);
+            # The commit they are on
+            local remote_commit
+            remote_commit=$(git describe --long --dirty --tags --always)
+            # echo this information out to the user in a nice format
+            # If the current version matches what pihole -v produces, the user is up-to-date
+            if [[ "${remote_version}" == "$(pihole -v | awk '/${search_term}/ {print $6}' | cut -d ')' -f1)" ]]; then
+                log_write "${TICK} ${pihole_component}: ${COL_GREEN}${remote_version}${COL_NC}"
+            # If not,
+            else
+                # echo the current version in yellow, signifying it's something to take a look at, but not a critical error
+                # Also add a URL to an FAQ
+                log_write "${INFO} ${pihole_component}: ${COL_YELLOW}${remote_version:-Untagged}${COL_NC} (${FAQ_UPDATE_PI_HOLE})"
+            fi
+
+            # If the repo is on the master branch, they are on the stable codebase
+            if [[ "${remote_branch}" == "master" ]]; then
+                # so the color of the text is green
+                log_write "${INFO} Branch: ${COL_GREEN}${remote_branch}${COL_NC}"
+            # If it is any other branch, they are in a developement branch
+            else
+                # So show that in yellow, signifying it's something to take a look at, but not a critical error
+                log_write "${INFO} Branch: ${COL_YELLOW}${remote_branch:-Detached}${COL_NC} (${FAQ_CHECKOUT_COMMAND})"
+            fi
+            # echo the current commit
+            log_write "${INFO} Commit: ${remote_commit}"
+        # If git status failed,
+        else
+            # Return an error message
+            log_write "${error_msg}"
+            # and exit with a non zero code
+            return 1
+        fi
     else
-      # Return an error message
-      log_write "${error_msg}"
-      # and exit with a non zero code
-      return 1
+        :
     fi
-  else
-    :
-  fi
 }
 
 check_ftl_version() {
-  local ftl_name="FTL"
-  echo_current_diagnostic "${ftl_name} version"
-  # Use the built in command to check FTL's version
-  FTL_VERSION=$(pihole-FTL version)
-  # Compare the current FTL version to the remote version
-  if [[ "${FTL_VERSION}" == "$(pihole -v | awk '/FTL/ {print $6}' | cut -d ')' -f1)" ]]; then
-    # If they are the same, FTL is up-to-date
-    log_write "${TICK} ${ftl_name}: ${COL_GREEN}${FTL_VERSION}${COL_NC}"
-  else
-    # If not, show it in yellow, signifying there is an update
-    log_write "${TICK} ${ftl_name}: ${COL_YELLOW}${FTL_VERSION}${COL_NC} (${FAQ_UPDATE_PI_HOLE})"
-  fi
+    local ftl_name="FTL"
+    echo_current_diagnostic "${ftl_name} version"
+    # Use the built in command to check FTL's version
+    FTL_VERSION=$(pihole-FTL version)
+    # Compare the current FTL version to the remote version
+    if [[ "${FTL_VERSION}" == "$(pihole -v | awk '/FTL/ {print $6}' | cut -d ')' -f1)" ]]; then
+        # If they are the same, FTL is up-to-date
+        log_write "${TICK} ${ftl_name}: ${COL_GREEN}${FTL_VERSION}${COL_NC}"
+    else
+        # If not, show it in yellow, signifying there is an update
+        log_write "${TICK} ${ftl_name}: ${COL_YELLOW}${FTL_VERSION}${COL_NC} (${FAQ_UPDATE_PI_HOLE})"
+    fi
 }
 
 # Checks the core version of the Pi-hole codebase
 check_component_versions() {
-  # Check the Web version, branch, and commit
-  compare_local_version_to_git_version "${CORE_GIT_DIRECTORY}" "Core"
-  # Check the Web version, branch, and commit
-  compare_local_version_to_git_version "${WEB_GIT_DIRECTORY}" "Web"
-  # Check the FTL version
-  check_ftl_version
+    # Check the Web version, branch, and commit
+    compare_local_version_to_git_version "${CORE_GIT_DIRECTORY}" "Core"
+    # Check the Web version, branch, and commit
+    compare_local_version_to_git_version "${WEB_GIT_DIRECTORY}" "Web"
+    # Check the FTL version
+    check_ftl_version
 }
 
 
 get_program_version() {
-  local program_name="${1}"
-  # Create a loval variable so this function can be safely reused
-  local program_version
-  echo_current_diagnostic "${program_name} version"
-  # Evalutate the program we are checking, if it is any of the ones below, show the version
-  case "${program_name}" in
-    "lighttpd") program_version="$(${program_name} -v |& head -n1 | cut -d '/' -f2 | cut -d ' ' -f1)"
-    ;;
-    "dnsmasq") program_version="$(${program_name} -v |& head -n1 | awk '{print $3}')"
-    ;;
-    "php") program_version="$(${program_name} -v |& head -n1 | cut -d '-' -f1 | cut -d ' ' -f2)"
-    ;;
-    # If a match is not found, show an error
-    *) echo "Unrecognized program";
-  esac
-  # If the program does not have a version (the variable is empty)
-  if [[ -z "${program_version}" ]]; then
-    # Display and error
-    log_write "${CROSS} ${COL_RED}${program_name} version could not be detected.${COL_NC}"
-  else
-    # Otherwise, display the version
-    log_write "${INFO} ${program_version}"
-  fi
+    local program_name="${1}"
+    # Create a loval variable so this function can be safely reused
+    local program_version
+    echo_current_diagnostic "${program_name} version"
+    # Evalutate the program we are checking, if it is any of the ones below, show the version
+    case "${program_name}" in
+        "lighttpd") program_version="$(${program_name} -v |& head -n1 | cut -d '/' -f2 | cut -d ' ' -f1)"
+                    ;;
+        "dnsmasq") program_version="$(${program_name} -v |& head -n1 | awk '{print $3}')"
+                    ;;
+        "php") program_version="$(${program_name} -v |& head -n1 | cut -d '-' -f1 | cut -d ' ' -f2)"
+                ;;
+        # If a match is not found, show an error
+        *) echo "Unrecognized program";
+    esac
+    # If the program does not have a version (the variable is empty)
+    if [[ -z "${program_version}" ]]; then
+        # Display and error
+        log_write "${CROSS} ${COL_RED}${program_name} version could not be detected.${COL_NC}"
+    else
+        # Otherwise, display the version
+        log_write "${INFO} ${program_version}"
+    fi
 }
 
 # These are the most critical dependencies of Pi-hole, so we check for them
 # and their versions, using the functions above.
 check_critical_program_versions() {
-  # Use the function created earlier and bundle them into one function that checks all the version numbers
-  get_program_version "dnsmasq"
-  get_program_version "lighttpd"
-  get_program_version "php"
+    # Use the function created earlier and bundle them into one function that checks all the version numbers
+    get_program_version "dnsmasq"
+    get_program_version "lighttpd"
+    get_program_version "php"
 }
 
 is_os_supported() {
-  local os_to_check="${1}"
-  # Strip just the base name of the system using sed
-  the_os=$(echo ${os_to_check} | sed 's/ .*//')
-  # If the variable is one of our supported OSes,
-  case "${the_os}" in
-    # Print it in green
-    "Raspbian") log_write "${TICK} ${COL_GREEN}${os_to_check}${COL_NC}";;
-    "Ubuntu") log_write "${TICK} ${COL_GREEN}${os_to_check}${COL_NC}";;
-    "Fedora") log_write "${TICK} ${COL_GREEN}${os_to_check}${COL_NC}";;
-    "Debian") log_write "${TICK} ${COL_GREEN}${os_to_check}${COL_NC}";;
-    "CentOS") log_write "${TICK} ${COL_GREEN}${os_to_check}${COL_NC}";;
-      # If not, show it in red and link to our software requirements page
-      *) log_write "${CROSS} ${COL_RED}${os_to_check}${COL_NC} (${FAQ_HARDWARE_REQUIREMENTS})";
-  esac
+    local os_to_check="${1}"
+    # Strip just the base name of the system using sed
+    # shellcheck disable=SC2001
+    the_os=$(echo "${os_to_check}" | sed 's/ .*//')
+    # If the variable is one of our supported OSes,
+    case "${the_os}" in
+        # Print it in green
+        "Raspbian") log_write "${TICK} ${COL_GREEN}${os_to_check}${COL_NC}";;
+        "Ubuntu") log_write "${TICK} ${COL_GREEN}${os_to_check}${COL_NC}";;
+        "Fedora") log_write "${TICK} ${COL_GREEN}${os_to_check}${COL_NC}";;
+        "Debian") log_write "${TICK} ${COL_GREEN}${os_to_check}${COL_NC}";;
+        "CentOS") log_write "${TICK} ${COL_GREEN}${os_to_check}${COL_NC}";;
+        # If not, show it in red and link to our software requirements page
+        *) log_write "${CROSS} ${COL_RED}${os_to_check}${COL_NC} (${FAQ_HARDWARE_REQUIREMENTS})";
+    esac
 }
 
 get_distro_attributes() {
-  # Put the current Internal Field Separator into another variable so it can be restored later
-  OLD_IFS="$IFS"
-  # Store the distro info in an array and make it global since the OS won't change,
-  # but we'll keep it within the function for better unit testing
-  IFS=$'\r\n' command eval 'distro_info=( $(cat /etc/*release) )'
-
-  # Set a named variable for better readability
-  local distro_attribute
-  # For each line found in an /etc/*release file,
-  for distro_attribute in "${distro_info[@]}"; do
-    # store the key in a variable
-    local pretty_name_key=$(echo "${distro_attribute}" | grep "PRETTY_NAME" | cut -d '=' -f1)
-    # we need just the OS PRETTY_NAME,
-    if [[ "${pretty_name_key}" == "PRETTY_NAME" ]]; then
-      # so save in in a variable when we find it
-      PRETTY_NAME_VALUE=$(echo "${distro_attribute}" | grep "PRETTY_NAME" | cut -d '=' -f2- | tr -d '"')
-      # then pass it as an argument that checks if the OS is supported
-      is_os_supported "${PRETTY_NAME_VALUE}"
-    else
-      # Since we only need the pretty name, we can just skip over anything that is not a match
-      :
-    fi
-  done
-  # Set the IFS back to what it was
-  IFS="$OLD_IFS"
+    # Put the current Internal Field Separator into another variable so it can be restored later
+    OLD_IFS="$IFS"
+    # Store the distro info in an array and make it global since the OS won't change,
+    # but we'll keep it within the function for better unit testing
+    local distro_info
+    #shellcheck disable=SC2016
+    IFS=$'\r\n' command eval 'distro_info=( $(cat /etc/*release) )'
+
+    # Set a named variable for better readability
+    local distro_attribute
+    # For each line found in an /etc/*release file,
+    for distro_attribute in "${distro_info[@]}"; do
+        # store the key in a variable
+        local pretty_name_key
+        pretty_name_key=$(echo "${distro_attribute}" | grep "PRETTY_NAME" | cut -d '=' -f1)
+        # we need just the OS PRETTY_NAME,
+        if [[ "${pretty_name_key}" == "PRETTY_NAME" ]]; then
+            # so save in in a variable when we find it
+            PRETTY_NAME_VALUE=$(echo "${distro_attribute}" | grep "PRETTY_NAME" | cut -d '=' -f2- | tr -d '"')
+            # then pass it as an argument that checks if the OS is supported
+            is_os_supported "${PRETTY_NAME_VALUE}"
+        else
+            # Since we only need the pretty name, we can just skip over anything that is not a match
+            :
+        fi
+    done
+    # Set the IFS back to what it was
+    IFS="$OLD_IFS"
 }
 
 diagnose_operating_system() {
-  # error message in a variable so we can easily modify it later (or re-use it)
-  local error_msg="Distribution unknown -- most likely you are on an unsupported platform and may run into issues."
-  # Display the current test that is running
-  echo_current_diagnostic "Operating system"
-
-  # If there is a /etc/*release file, it's probably a supported operating system, so we can
-  if ls /etc/*release 1> /dev/null 2>&1; then
-    # display the attributes to the user from the function made earlier
-    get_distro_attributes
-  else
-    # If it doesn't exist, it's not a system we currently support and link to FAQ
-    log_write "${CROSS} ${COL_RED}${error_msg}${COL_NC} (${FAQ_HARDWARE_REQUIREMENTS})"
-  fi
+    # error message in a variable so we can easily modify it later (or re-use it)
+    local error_msg="Distribution unknown -- most likely you are on an unsupported platform and may run into issues."
+    # Display the current test that is running
+    echo_current_diagnostic "Operating system"
+
+    # If there is a /etc/*release file, it's probably a supported operating system, so we can
+    if ls /etc/*release 1> /dev/null 2>&1; then
+        # display the attributes to the user from the function made earlier
+        get_distro_attributes
+    else
+        # If it doesn't exist, it's not a system we currently support and link to FAQ
+        log_write "${CROSS} ${COL_RED}${error_msg}${COL_NC} (${FAQ_HARDWARE_REQUIREMENTS})"
+    fi
 }
 
 check_selinux() {
-  # SELinux is not supported by the Pi-hole
-  echo_current_diagnostic "SELinux"
-  # Check if a SELinux configuration file exists
-  if [[ -f /etc/selinux/config ]]; then
-    # If a SELinux configuration file was found, check the default SELinux mode.
-    DEFAULT_SELINUX=$(awk -F= '/^SELINUX=/ {print $2}' /etc/selinux/config)
-    case "${DEFAULT_SELINUX,,}" in
-      enforcing)
-        log_write "${CROSS} ${COL_RED}Default SELinux: $DEFAULT_SELINUX${COL_NC}"
-        ;;
-      *)  # 'permissive' and 'disabled'
-        log_write "${TICK} ${COL_GREEN}Default SELinux: $DEFAULT_SELINUX${COL_NC}";
-        ;;
-    esac
-    # Check the current state of SELinux
-    CURRENT_SELINUX=$(getenforce)
-    case "${CURRENT_SELINUX,,}" in
-      enforcing)
-        log_write "${CROSS} ${COL_RED}Current SELinux: $CURRENT_SELINUX${COL_NC}"
-        ;;
-      *)  # 'permissive' and 'disabled'
-        log_write "${TICK} ${COL_GREEN}Current SELinux: $CURRENT_SELINUX${COL_NC}";
-        ;;
-    esac
-  else
-    log_write "${INFO} ${COL_GREEN}SELinux not detected${COL_NC}";
-  fi
+    # SELinux is not supported by the Pi-hole
+    echo_current_diagnostic "SELinux"
+    # Check if a SELinux configuration file exists
+    if [[ -f /etc/selinux/config ]]; then
+        # If a SELinux configuration file was found, check the default SELinux mode.
+        DEFAULT_SELINUX=$(awk -F= '/^SELINUX=/ {print $2}' /etc/selinux/config)
+        case "${DEFAULT_SELINUX,,}" in
+            enforcing)
+                log_write "${CROSS} ${COL_RED}Default SELinux: $DEFAULT_SELINUX${COL_NC}"
+                ;;
+            *)  # 'permissive' and 'disabled'
+                log_write "${TICK} ${COL_GREEN}Default SELinux: $DEFAULT_SELINUX${COL_NC}";
+                ;;
+        esac
+        # Check the current state of SELinux
+        CURRENT_SELINUX=$(getenforce)
+        case "${CURRENT_SELINUX,,}" in
+            enforcing)
+                log_write "${CROSS} ${COL_RED}Current SELinux: $CURRENT_SELINUX${COL_NC}"
+                ;;
+            *)  # 'permissive' and 'disabled'
+                log_write "${TICK} ${COL_GREEN}Current SELinux: $CURRENT_SELINUX${COL_NC}";
+                ;;
+        esac
+    else
+        log_write "${INFO} ${COL_GREEN}SELinux not detected${COL_NC}";
+    fi
 }
 
 processor_check() {
-  echo_current_diagnostic "Processor"
-  # Store the processor type in a variable
-  PROCESSOR=$(uname -m)
-  # If it does not contain a value,
-  if [[ -z "${PROCESSOR}" ]]; then
-    # we couldn't detect it, so show an error
-    PROCESSOR=$(lscpu | awk '/Architecture/ {print $2}')
-    log_write "${CROSS} ${COL_RED}${PROCESSOR}${COL_NC} has not been tested with FTL, but may still work: (${FAQ_FTL_COMPATIBILITY})"
-  else
-    # Check if the architecture is currently supported for FTL
-    case "${PROCESSOR}" in
-      "amd64") "${TICK} ${COL_GREEN}${PROCESSOR}${COL_NC}"
-      ;;
-      "armv6l") "${TICK} ${COL_GREEN}${PROCESSOR}${COL_NC}"
-      ;;
-      "armv6") "${TICK} ${COL_GREEN}${PROCESSOR}${COL_NC}"
-      ;;
-      "armv7l") "${TICK} ${COL_GREEN}${PROCESSOR}${COL_NC}"
-      ;;
-      "aarch64") "${TICK} ${COL_GREEN}${PROCESSOR}${COL_NC}"
-      ;;
-    # Otherwise, show the processor type
-    *) log_write "${INFO} ${PROCESSOR}";
-    esac
-  fi
+    echo_current_diagnostic "Processor"
+    # Store the processor type in a variable
+    PROCESSOR=$(uname -m)
+    # If it does not contain a value,
+    if [[ -z "${PROCESSOR}" ]]; then
+        # we couldn't detect it, so show an error
+        PROCESSOR=$(lscpu | awk '/Architecture/ {print $2}')
+        log_write "${CROSS} ${COL_RED}${PROCESSOR}${COL_NC} has not been tested with FTL, but may still work: (${FAQ_FTL_COMPATIBILITY})"
+    else
+        # Check if the architecture is currently supported for FTL
+        case "${PROCESSOR}" in
+            "amd64") log_write "${TICK} ${COL_GREEN}${PROCESSOR}${COL_NC}"
+                ;;
+            "armv6l") log_write "${TICK} ${COL_GREEN}${PROCESSOR}${COL_NC}"
+                ;;
+            "armv6") log_write "${TICK} ${COL_GREEN}${PROCESSOR}${COL_NC}"
+                ;;
+            "armv7l") log_write "${TICK} ${COL_GREEN}${PROCESSOR}${COL_NC}"
+                ;;
+            "aarch64") log_write "${TICK} ${COL_GREEN}${PROCESSOR}${COL_NC}"
+                ;;
+            # Otherwise, show the processor type
+            *) log_write "${INFO} ${PROCESSOR}";
+        esac
+    fi
 }
 
 parse_setup_vars() {
-  echo_current_diagnostic "Setup variables"
-  # If the file exists,
-  if [[ -r "${PIHOLE_SETUP_VARS_FILE}" ]]; then
-    # parse it
-    parse_file "${PIHOLE_SETUP_VARS_FILE}"
-  else
-    # If not, show an error
-    log_write "${CROSS} ${COL_RED}Could not read ${PIHOLE_SETUP_VARS_FILE}.${COL_NC}"
-  fi
+    echo_current_diagnostic "Setup variables"
+    # If the file exists,
+    if [[ -r "${PIHOLE_SETUP_VARS_FILE}" ]]; then
+        # parse it
+        parse_file "${PIHOLE_SETUP_VARS_FILE}"
+    else
+        # If not, show an error
+        log_write "${CROSS} ${COL_RED}Could not read ${PIHOLE_SETUP_VARS_FILE}.${COL_NC}"
+    fi
+}
+
+parse_locale() {
+    local pihole_locale
+    echo_current_diagnostic "Locale"
+    pihole_locale="$(locale)"
+    parse_file "${pihole_locale}"
 }
 
 does_ip_match_setup_vars() {
-  # Check for IPv4 or 6
-  local protocol="${1}"
-  # IP address to check for
-  local ip_address="${2}"
-  # See what IP is in the setupVars.conf file
-  local setup_vars_ip=$(< ${PIHOLE_SETUP_VARS_FILE} grep IPV${protocol}_ADDRESS | cut -d '=' -f2)
-  # If it's an IPv6 address
-  if [[ "${protocol}" == "6" ]]; then
-    # Strip off the / (CIDR notation)
-    if [[ "${ip_address%/*}" == "${setup_vars_ip%/*}" ]]; then
-      # if it matches, show it in green
-      log_write "   ${COL_GREEN}${ip_address%/*}${COL_NC} matches the IP found in ${PIHOLE_SETUP_VARS_FILE}"
-    else
-      # otherwise show it in red with an FAQ URL
-      log_write "   ${COL_RED}${ip_address%/*}${COL_NC} does not match the IP found in ${PIHOLE_SETUP_VARS_FILE} (${FAQ_ULA})"
-    fi
+    # Check for IPv4 or 6
+    local protocol="${1}"
+    # IP address to check for
+    local ip_address="${2}"
+    # See what IP is in the setupVars.conf file
+    local setup_vars_ip
+    setup_vars_ip=$(< ${PIHOLE_SETUP_VARS_FILE} grep IPV"${protocol}"_ADDRESS | cut -d '=' -f2)
+    # If it's an IPv6 address
+    if [[ "${protocol}" == "6" ]]; then
+        # Strip off the / (CIDR notation)
+        if [[ "${ip_address%/*}" == "${setup_vars_ip%/*}" ]]; then
+            # if it matches, show it in green
+            log_write "   ${COL_GREEN}${ip_address%/*}${COL_NC} matches the IP found in ${PIHOLE_SETUP_VARS_FILE}"
+        else
+            # otherwise show it in red with an FAQ URL
+            log_write "   ${COL_RED}${ip_address%/*}${COL_NC} does not match the IP found in ${PIHOLE_SETUP_VARS_FILE} (${FAQ_ULA})"
+        fi
 
-  else
-    # if the protocol isn't 6, it's 4 so no need to strip the CIDR notation
-    # since it exists in the setupVars.conf that way
-    if [[ "${ip_address}" == "${setup_vars_ip}" ]]; then
-      # show in green if it matches
-      log_write "   ${COL_GREEN}${ip_address}${COL_NC} matches the IP found in ${PIHOLE_SETUP_VARS_FILE}"
     else
-      # otherwise show it in red
-      log_write "   ${COL_RED}${ip_address}${COL_NC} does not match the IP found in ${PIHOLE_SETUP_VARS_FILE} (${FAQ_ULA})"
+        # if the protocol isn't 6, it's 4 so no need to strip the CIDR notation
+        # since it exists in the setupVars.conf that way
+        if [[ "${ip_address}" == "${setup_vars_ip}" ]]; then
+            # show in green if it matches
+            log_write "   ${COL_GREEN}${ip_address}${COL_NC} matches the IP found in ${PIHOLE_SETUP_VARS_FILE}"
+        else
+            # otherwise show it in red
+            log_write "   ${COL_RED}${ip_address}${COL_NC} does not match the IP found in ${PIHOLE_SETUP_VARS_FILE} (${FAQ_ULA})"
+        fi
     fi
-  fi
 }
 
 detect_ip_addresses() {
-  # First argument should be a 4 or a 6
-  local protocol=${1}
-  # Use ip to show the addresses for the chosen protocol
-  # Store the values in an arry so they can be looped through
-  # Get the lines that are in the file(s) and store them in an array for parsing later
-  declare -a ip_addr_list=( $(ip -${protocol} addr show dev ${PIHOLE_INTERFACE} | awk -F ' ' '{ for(i=1;i<=NF;i++) if ($i ~ '/^inet/') print $(i+1) }') )
-
-  # If there is something in the IP address list,
-  if [[ -n ${ip_addr_list} ]]; then
-    # Local iterator
-    local i
-    # Display the protocol and interface
-    log_write "${TICK} IPv${protocol} address(es) bound to the ${PIHOLE_INTERFACE} interface:"
-    # Since there may be more than one IP address, store them in an array
-    for i in "${!ip_addr_list[@]}"; do
-      # For each one in the list, print it out
-      does_ip_match_setup_vars "${protocol}" "${ip_addr_list[$i]}"
-    done
-    # Print a blank line just for formatting
-    log_write ""
-  else
-    # If there are no IPs detected, explain that the protocol is not configured
-    log_write "${CROSS} ${COL_RED}No IPv${protocol} address(es) found on the ${PIHOLE_INTERFACE}${COL_NC} interface.\n"
-    return 1
-  fi
-  # If the protocol is v6
-  if [[ "${protocol}" == "6" ]]; then
-    # let the user know that as long as there is one green address, things should be ok
-    log_write "   ^ Please note that you may have more than one IP address listed."
-    log_write "   As long as one of them is green, and it matches what is in ${PIHOLE_SETUP_VARS_FILE}, there is no need for concern.\n"
-    log_write "   The link to the FAQ is for an issue that sometimes occurs when the IPv6 address changes, which is why we check for it.\n"
-  fi
+    # First argument should be a 4 or a 6
+    local protocol=${1}
+    # Use ip to show the addresses for the chosen protocol
+    # Store the values in an arry so they can be looped through
+    # Get the lines that are in the file(s) and store them in an array for parsing later
+    mapfile -t ip_addr_list < <(ip -"${protocol}" addr show dev "${PIHOLE_INTERFACE}" | awk -F ' ' '{ for(i=1;i<=NF;i++) if ($i ~ '/^inet/') print $(i+1) }')
+
+    # If there is something in the IP address list,
+    if [[ -n ${ip_addr_list[*]} ]]; then
+        # Local iterator
+        local i
+        # Display the protocol and interface
+        log_write "${TICK} IPv${protocol} address(es) bound to the ${PIHOLE_INTERFACE} interface:"
+        # Since there may be more than one IP address, store them in an array
+        for i in "${!ip_addr_list[@]}"; do
+            # For each one in the list, print it out
+            does_ip_match_setup_vars "${protocol}" "${ip_addr_list[$i]}"
+        done
+        # Print a blank line just for formatting
+        log_write ""
+    else
+        # If there are no IPs detected, explain that the protocol is not configured
+        log_write "${CROSS} ${COL_RED}No IPv${protocol} address(es) found on the ${PIHOLE_INTERFACE}${COL_NC} interface.\\n"
+        return 1
+    fi
+    # If the protocol is v6
+    if [[ "${protocol}" == "6" ]]; then
+        # let the user know that as long as there is one green address, things should be ok
+        log_write "   ^ Please note that you may have more than one IP address listed."
+        log_write "   As long as one of them is green, and it matches what is in ${PIHOLE_SETUP_VARS_FILE}, there is no need for concern.\\n"
+        log_write "   The link to the FAQ is for an issue that sometimes occurs when the IPv6 address changes, which is why we check for it.\\n"
+    fi
 }
 
 ping_ipv4_or_ipv6() {
-  # Give the first argument a readable name (a 4 or a six should be the argument)
-  local protocol="${1}"
-  # If the protocol is 6,
-  if [[ ${protocol} == "6" ]]; then
-    # use ping6
-    cmd="ping6"
-    # and Google's public IPv6 address
-    public_address="2001:4860:4860::8888"
-  else
-    # Otherwise, just use ping
-    cmd="ping"
-    # and Google's public IPv4 address
-    public_address="8.8.8.8"
-  fi
+    # Give the first argument a readable name (a 4 or a six should be the argument)
+    local protocol="${1}"
+    # If the protocol is 6,
+    if [[ ${protocol} == "6" ]]; then
+        # use ping6
+        cmd="ping6"
+        # and Google's public IPv6 address
+        public_address="2001:4860:4860::8888"
+    else
+        # Otherwise, just use ping
+        cmd="ping"
+        # and Google's public IPv4 address
+        public_address="8.8.8.8"
+    fi
 }
 
 ping_gateway() {
-  local protocol="${1}"
-  ping_ipv4_or_ipv6 "${protocol}"
-  # Check if we are using IPv4 or IPv6
-  # Find the default gateway using IPv4 or IPv6
-  local gateway
-  gateway="$(ip -${protocol} route | grep default | cut -d ' ' -f 3)"
-
-  # If the gateway variable has a value (meaning a gateway was found),
-  if [[ -n "${gateway}" ]]; then
-    log_write "${INFO} Default IPv${protocol} gateway: ${gateway}"
-    # Let the user know we will ping the gateway for a response
-    log_write "   * Pinging ${gateway}..."
-    # Try to quietly ping the gateway 3 times, with a timeout of 3 seconds, using numeric output only,
-    # on the pihole interface, and tail the last three lines of the output
-    # If pinging the gateway is not successful,
-    if ! ${cmd} -c 3 -W 2 -n ${gateway} -I ${PIHOLE_INTERFACE} >/dev/null; then
-      # let the user know
-      log_write "${CROSS} ${COL_RED}Gateway did not respond.${COL_NC} ($FAQ_GATEWAY)\n"
-      # and return an error code
-      return 1
-    # Otherwise,
-    else
-      # show a success
-      log_write "${TICK} ${COL_GREEN}Gateway responded.${COL_NC}"
-      # and return a success code
-      return 0
+    local protocol="${1}"
+    ping_ipv4_or_ipv6 "${protocol}"
+    # Check if we are using IPv4 or IPv6
+    # Find the default gateway using IPv4 or IPv6
+    local gateway
+    gateway="$(ip -"${protocol}" route | grep default | cut -d ' ' -f 3)"
+
+    # If the gateway variable has a value (meaning a gateway was found),
+    if [[ -n "${gateway}" ]]; then
+        log_write "${INFO} Default IPv${protocol} gateway: ${gateway}"
+        # Let the user know we will ping the gateway for a response
+        log_write "   * Pinging ${gateway}..."
+        # Try to quietly ping the gateway 3 times, with a timeout of 3 seconds, using numeric output only,
+        # on the pihole interface, and tail the last three lines of the output
+        # If pinging the gateway is not successful,
+        if ! ${cmd} -c 1 -W 2 -n "${gateway}" -I "${PIHOLE_INTERFACE}" >/dev/null; then
+            # let the user know
+            log_write "${CROSS} ${COL_RED}Gateway did not respond.${COL_NC} ($FAQ_GATEWAY)\\n"
+            # and return an error code
+            return 1
+        # Otherwise,
+        else
+            # show a success
+            log_write "${TICK} ${COL_GREEN}Gateway responded.${COL_NC}"
+            # and return a success code
+            return 0
+        fi
     fi
-  fi
 }
 
 ping_internet() {
-  local protocol="${1}"
-  # Ping a public address using the protocol passed as an argument
-  ping_ipv4_or_ipv6 "${protocol}"
-  log_write "* Checking Internet connectivity via IPv${protocol}..."
-  # Try to ping the address 3 times
-  if ! ${cmd} -W 2 -c 3 -n ${public_address} -I ${PIHOLE_INTERFACE} >/dev/null; then
-    # if it's unsuccessful, show an error
-    log_write "${CROSS} ${COL_RED}Cannot reach the Internet.${COL_NC}\n"
-    return 1
-  else
-    # Otherwise, show success
-    log_write "${TICK} ${COL_GREEN}Query responded.${COL_NC}\n"
-    return 0
-  fi
+    local protocol="${1}"
+    # Ping a public address using the protocol passed as an argument
+    ping_ipv4_or_ipv6 "${protocol}"
+    log_write "* Checking Internet connectivity via IPv${protocol}..."
+    # Try to ping the address 3 times
+    if ! ${cmd} -c 1 -W 2 -n ${public_address} -I "${PIHOLE_INTERFACE}" >/dev/null; then
+        # if it's unsuccessful, show an error
+        log_write "${CROSS} ${COL_RED}Cannot reach the Internet.${COL_NC}\\n"
+        return 1
+    else
+        # Otherwise, show success
+        log_write "${TICK} ${COL_GREEN}Query responded.${COL_NC}\\n"
+        return 0
+    fi
 }
 
 compare_port_to_service_assigned() {
-  local service_name="${1}"
-  # The programs we use may change at some point, so they are in a varible here
-  local resolver="dnsmasq"
-  local web_server="lighttpd"
-  local ftl="pihole-FTL"
-  if [[ "${service_name}" == "${resolver}" ]] || [[ "${service_name}" == "${web_server}" ]] || [[ "${service_name}" == "${ftl}" ]]; then
+    local service_name="${1}"
+    # The programs we use may change at some point, so they are in a varible here
+    local resolver="dnsmasq"
+    local web_server="lighttpd"
+    local ftl="pihole-FTL"
+    if [[ "${service_name}" == "${resolver}" ]] || [[ "${service_name}" == "${web_server}" ]] || [[ "${service_name}" == "${ftl}" ]]; then
         # if port 53 is dnsmasq, show it in green as it's standard
         log_write "[${COL_GREEN}${port_number}${COL_NC}] is in use by ${COL_GREEN}${service_name}${COL_NC}"
-      # Otherwise,
-      else
+    # Otherwise,
+    else
         # Show the service name in red since it's non-standard
         log_write "[${COL_RED}${port_number}${COL_NC}] is in use by ${COL_RED}${service_name}${COL_NC} (${FAQ_HARDWARE_REQUIREMENTS_PORTS})"
-      fi
+    fi
 }
 
 check_required_ports() {
-  echo_current_diagnostic "Ports in use"
-  # Since Pi-hole needs 53, 80, and 4711, check what they are being used by
-  # so we can detect any issues
-  local resolver="dnsmasq"
-  local web_server="lighttpd"
-  local ftl="pihole-FTL"
-  # Create an array for these ports in use
-  ports_in_use=()
-  # Sort the addresses and remove duplicates
-  while IFS= read -r line; do
-      ports_in_use+=( "$line" )
-  done < <( lsof -i -P -n | awk -F' ' '/LISTEN/ {print $9, $1}' | sort -n | uniq | cut -d':' -f2 )
-
-  # Now that we have the values stored,
-  for i in "${!ports_in_use[@]}"; do
-    # loop through them and assign some local variables
-    local port_number
-    port_number="$(echo "${ports_in_use[$i]}" | awk '{print $1}')"
-    local service_name
-    service_name=$(echo "${ports_in_use[$i]}" | awk '{print $2}')
-    # Use a case statement to determine if the right services are using the right ports
-    case "${port_number}" in
-      53) compare_port_to_service_assigned  "${resolver}"
-          ;;
-      80) compare_port_to_service_assigned  "${web_server}"
-          ;;
-      4711) compare_port_to_service_assigned  "${ftl}"
-          ;;
-      # If it's not a default port that Pi-hole needs, just print it out for the user to see
-      *) log_write "[${port_number}] is in use by ${service_name}";
-    esac
-  done
+    echo_current_diagnostic "Ports in use"
+    # Since Pi-hole needs 53, 80, and 4711, check what they are being used by
+    # so we can detect any issues
+    local resolver="dnsmasq"
+    local web_server="lighttpd"
+    local ftl="pihole-FTL"
+    # Create an array for these ports in use
+    ports_in_use=()
+    # Sort the addresses and remove duplicates
+    while IFS= read -r line; do
+        ports_in_use+=( "$line" )
+    done < <( lsof -iTCP -sTCP:LISTEN -P -n +c 10 )
+
+    # Now that we have the values stored,
+    for i in "${!ports_in_use[@]}"; do
+        # loop through them and assign some local variables
+        local service_name
+        service_name=$(echo "${ports_in_use[$i]}" | awk '{print $1}')
+        local protocol_type
+        protocol_type=$(echo "${ports_in_use[$i]}" | awk '{print $5}')
+        local port_number
+        port_number="$(echo "${ports_in_use[$i]}" | awk '{print $9}')"
+
+        # Skip the line if it's the titles of the columns the lsof command produces
+        if [[ "${service_name}" == COMMAND ]]; then
+            continue
+        fi
+        # Use a case statement to determine if the right services are using the right ports
+        case "${port_number}" in
+            53) compare_port_to_service_assigned  "${resolver}"
+                ;;
+            80) compare_port_to_service_assigned  "${web_server}"
+                ;;
+            4711) compare_port_to_service_assigned  "${ftl}"
+                ;;
+            # If it's not a default port that Pi-hole needs, just print it out for the user to see
+            *) log_write "${port_number} ${service_name} (${protocol_type})";
+        esac
+    done
 }
 
 check_networking() {
-  # Runs through several of the functions made earlier; we just clump them
-  # together since they are all related to the networking aspect of things
-  echo_current_diagnostic "Networking"
-  detect_ip_addresses "4"
-  detect_ip_addresses "6"
-  ping_gateway "4"
-  ping_gateway "6"
-  check_required_ports
+    # Runs through several of the functions made earlier; we just clump them
+    # together since they are all related to the networking aspect of things
+    echo_current_diagnostic "Networking"
+    detect_ip_addresses "4"
+    detect_ip_addresses "6"
+    ping_gateway "4"
+    ping_gateway "6"
+    check_required_ports
 }
 
 check_x_headers() {
-  # The X-Headers allow us to determine from the command line if the Web
-  # lighttpd.conf has a directive to show "X-Pi-hole: A black hole for Internet advertisements."
-  # in the header of any Pi-holed domain
-  # Similarly, it will show "X-Pi-hole: The Pi-hole Web interface is working!" if you view the header returned
-  # when accessing the dashboard (i.e curl -I pi.hole/admin/)
-  # server is operating correctly
-  echo_current_diagnostic "Dashboard and block page"
-  # Use curl -I to get the header and parse out just the X-Pi-hole one
-  local block_page
-  block_page=$(curl -Is localhost | awk '/X-Pi-hole/' | tr -d '\r')
-  # Do it for the dashboard as well, as the header is different than above
-  local dashboard
-  dashboard=$(curl -Is localhost/admin/ | awk '/X-Pi-hole/' | tr -d '\r')
-  # Store what the X-Header shoud be in variables for comparision later
-  local block_page_working
-  block_page_working="X-Pi-hole: A black hole for Internet advertisements."
-  local dashboard_working
-  dashboard_working="X-Pi-hole: The Pi-hole Web interface is working!"
-  local full_curl_output_block_page
-  full_curl_output_block_page="$(curl -Is localhost)"
-  local full_curl_output_dashboard
-  full_curl_output_dashboard="$(curl -Is localhost/admin/)"
-  # If the X-header found by curl matches what is should be,
-  if [[ $block_page == "$block_page_working" ]]; then
-    # display a success message
-    log_write "$TICK ${COL_GREEN}${block_page}${COL_NC}"
-  else
-    # Otherwise, show an error
-    log_write "$CROSS ${COL_RED}X-Header does not match or could not be retrieved.${COL_NC}"
-    log_write "${COL_RED}${full_curl_output_block_page}${COL_NC}"
-  fi
-
-  # Same logic applies to the dashbord as above, if the X-Header matches what a working system shoud have,
-  if [[ $dashboard == "$dashboard_working" ]]; then
-    # then we can show a success
-    log_write "$TICK ${COL_GREEN}${dashboard}${COL_NC}"
-  else
-    # Othewise, it's a failure since the X-Headers either don't exist or have been modified in some way
-    log_write "$CROSS ${COL_RED}X-Header does not match or could not be retrieved.${COL_NC}"
-    log_write "${COL_RED}${full_curl_output_dashboard}${COL_NC}"
-  fi
+    # The X-Headers allow us to determine from the command line if the Web
+    # lighttpd.conf has a directive to show "X-Pi-hole: A black hole for Internet advertisements."
+    # in the header of any Pi-holed domain
+    # Similarly, it will show "X-Pi-hole: The Pi-hole Web interface is working!" if you view the header returned
+    # when accessing the dashboard (i.e curl -I pi.hole/admin/)
+    # server is operating correctly
+    echo_current_diagnostic "Dashboard and block page"
+    # Use curl -I to get the header and parse out just the X-Pi-hole one
+    local block_page
+    block_page=$(curl -Is localhost | awk '/X-Pi-hole/' | tr -d '\r')
+    # Do it for the dashboard as well, as the header is different than above
+    local dashboard
+    dashboard=$(curl -Is localhost/admin/ | awk '/X-Pi-hole/' | tr -d '\r')
+    # Store what the X-Header shoud be in variables for comparision later
+    local block_page_working
+    block_page_working="X-Pi-hole: A black hole for Internet advertisements."
+    local dashboard_working
+    dashboard_working="X-Pi-hole: The Pi-hole Web interface is working!"
+    local full_curl_output_block_page
+    full_curl_output_block_page="$(curl -Is localhost)"
+    local full_curl_output_dashboard
+    full_curl_output_dashboard="$(curl -Is localhost/admin/)"
+    # If the X-header found by curl matches what is should be,
+    if [[ $block_page == "$block_page_working" ]]; then
+        # display a success message
+        log_write "$TICK Block page X-Header: ${COL_GREEN}${block_page}${COL_NC}"
+    else
+        # Otherwise, show an error
+        log_write "$CROSS Block page X-Header: ${COL_RED}X-Header does not match or could not be retrieved.${COL_NC}"
+        log_write "${COL_RED}${full_curl_output_block_page}${COL_NC}"
+    fi
+
+    # Same logic applies to the dashbord as above, if the X-Header matches what a working system shoud have,
+    if [[ $dashboard == "$dashboard_working" ]]; then
+        # then we can show a success
+        log_write "$TICK Web interface X-Header: ${COL_GREEN}${dashboard}${COL_NC}"
+    else
+        # Othewise, it's a failure since the X-Headers either don't exist or have been modified in some way
+        log_write "$CROSS Web interface X-Header: ${COL_RED}X-Header does not match or could not be retrieved.${COL_NC}"
+        log_write "${COL_RED}${full_curl_output_dashboard}${COL_NC}"
+    fi
 }
 
 dig_at() {
-  # We need to test if Pi-hole can properly resolve domain names
-  # as it is an essential piece of the software
-
-  # Store the arguments as variables with names
-  local protocol="${1}"
-  local IP="${2}"
-  echo_current_diagnostic "Name resolution (IPv${protocol}) using a random blocked domain and a known ad-serving domain"
-  # Set more local variables
-  # We need to test name resolution locally, via Pi-hole, and via a public resolver
-  local local_dig
-  local pihole_dig
-  local remote_dig
-  # Use a static domain that we know has IPv4 and IPv6 to avoid false positives
-  # Sometimes the randomly chosen domains don't use IPv6, or something else is wrong with them
-  local remote_url="doubleclick.com"
-
-  # If the protocol (4 or 6) is 6,
-  if [[ ${protocol} == "6" ]]; then
-    # Set the IPv6 variables and record type
-    local local_address="::1"
-    local pihole_address="${IPV6_ADDRESS%/*}"
-    local remote_address="2001:4860:4860::8888"
-    local record_type="AAAA"
-  # Othwerwise, it should be 4
-  else
-    # so use the IPv4 values
-    local local_address="127.0.0.1"
-    local pihole_address="${IPV4_ADDRESS%/*}"
-    local remote_address="8.8.8.8"
-    local record_type="A"
-  fi
-
-  # Find a random blocked url that has not been whitelisted.
-  # This helps emulate queries to different domains that a user might query
-  # It will also give extra assurance that Pi-hole is correctly resolving and blocking domains
-  local random_url=$(shuf -n 1 "${PIHOLE_BLOCKLIST_FILE}" | awk -F ' ' '{ print $2 }')
-
-  # First, do a dig on localhost to see if Pi-hole can use itself to block a domain
-  if local_dig=$(dig +tries=1 +time=2 -"${protocol}" "${random_url}" @${local_address} +short "${record_type}"); then
-    # If it can, show sucess
-    log_write "${TICK} ${random_url} ${COL_GREEN}is ${local_dig}${COL_NC} via ${COL_CYAN}localhost$COL_NC (${local_address})"
-  else
-    # Otherwise, show a failure
-    log_write "${CROSS} ${COL_RED}Failed to resolve${COL_NC} ${random_url} via ${COL_RED}localhost${COL_NC} (${local_address})"
-  fi
-
-  # Next we need to check if Pi-hole can resolve a domain when the query is sent to it's IP address
-  # This better emulates how clients will interact with Pi-hole as opposed to above where Pi-hole is
-  # just asing itself locally
-  # The default timeouts and tries are reduced in case the DNS server isn't working, so the user isn't waiting for too long
-
-  # If Pi-hole can dig itself from it's IP (not the loopback address)
-  if pihole_dig=$(dig +tries=1 +time=2 -"${protocol}" "${random_url}" @${pihole_address} +short "${record_type}"); then
-    # show a success
-    log_write "${TICK} ${random_url} ${COL_GREEN}is ${pihole_dig}${COL_NC} via ${COL_CYAN}Pi-hole${COL_NC} (${pihole_address})"
-  else
-    # Othewise, show a failure
-    log_write "${CROSS} ${COL_RED}Failed to resolve${COL_NC} ${random_url} via ${COL_RED}Pi-hole${COL_NC} (${pihole_address})"
-  fi
-
-  # Finally, we need to make sure legitimate queries can out to the Internet using an external, public DNS server
-  # We are using the static remote_url here instead of a random one because we know it works with IPv4 and IPv6
-  if remote_dig=$(dig +tries=1 +time=2 -"${protocol}" "${remote_url}" @${remote_address} +short "${record_type}" | head -n1); then
-    # If successful, the real IP of the domain will be returned instead of Pi-hole's IP
-    log_write "${TICK} ${remote_url} ${COL_GREEN}is ${remote_dig}${COL_NC} via ${COL_CYAN}a remote, public DNS server${COL_NC} (${remote_address})"
-  else
-    # Otherwise, show an error
-    log_write "${CROSS} ${COL_RED}Failed to resolve${COL_NC} ${remote_url} via ${COL_RED}a remote, public DNS server${COL_NC} (${remote_address})"
-  fi
-}
+    # We need to test if Pi-hole can properly resolve domain names
+    # as it is an essential piece of the software
+
+    # Store the arguments as variables with names
+    local protocol="${1}"
+    local IP="${2}"
+    echo_current_diagnostic "Name resolution (IPv${protocol}) using a random blocked domain and a known ad-serving domain"
+    # Set more local variables
+    # We need to test name resolution locally, via Pi-hole, and via a public resolver
+    local local_dig
+    local pihole_dig
+    local remote_dig
+    # Use a static domain that we know has IPv4 and IPv6 to avoid false positives
+    # Sometimes the randomly chosen domains don't use IPv6, or something else is wrong with them
+    local remote_url="doubleclick.com"
+
+    # If the protocol (4 or 6) is 6,
+    if [[ ${protocol} == "6" ]]; then
+        # Set the IPv6 variables and record type
+        local local_address="::1"
+        local pihole_address="${IP}"
+        local remote_address="2001:4860:4860::8888"
+        local record_type="AAAA"
+    # Othwerwise, it should be 4
+    else
+        # so use the IPv4 values
+        local local_address="127.0.0.1"
+        local pihole_address="${IP}"
+        local remote_address="8.8.8.8"
+        local record_type="A"
+    fi
 
-process_status(){
-  # Check to make sure Pi-hole's services are running and active
-  echo_current_diagnostic "Pi-hole processes"
-  # Local iterator
-  local i
-  # For each process,
-  for i in "${PIHOLE_PROCESSES[@]}"; do
-    # If systemd
-    if command -v systemctl &> /dev/null; then
-      # get its status via systemctl
-      local status_of_process=$(systemctl is-active "${i}")
+    # Find a random blocked url that has not been whitelisted.
+    # This helps emulate queries to different domains that a user might query
+    # It will also give extra assurance that Pi-hole is correctly resolving and blocking domains
+    local random_url
+    random_url=$(shuf -n 1 "${PIHOLE_BLOCKLIST_FILE}")
+
+    # First, do a dig on localhost to see if Pi-hole can use itself to block a domain
+    if local_dig=$(dig +tries=1 +time=2 -"${protocol}" "${random_url}" @${local_address} +short "${record_type}"); then
+        # If it can, show sucess
+        log_write "${TICK} ${random_url} ${COL_GREEN}is ${local_dig}${COL_NC} via ${COL_CYAN}localhost$COL_NC (${local_address})"
     else
-      # Otherwise, use the service command
-      local status_of_process=$(service "${i}" status | awk '/Active:/ {print $2}') &> /dev/null
+        # Otherwise, show a failure
+        log_write "${CROSS} ${COL_RED}Failed to resolve${COL_NC} ${random_url} via ${COL_RED}localhost${COL_NC} (${local_address})"
     fi
-    # and print it out to the user
-    if [[ "${status_of_process}" == "active" ]]; then
-      # If it's active, show it in green
-      log_write "${TICK} ${COL_GREEN}${i}${COL_NC} daemon is ${COL_GREEN}${status_of_process}${COL_NC}"
+
+    # Next we need to check if Pi-hole can resolve a domain when the query is sent to it's IP address
+    # This better emulates how clients will interact with Pi-hole as opposed to above where Pi-hole is
+    # just asing itself locally
+    # The default timeouts and tries are reduced in case the DNS server isn't working, so the user isn't waiting for too long
+
+    # If Pi-hole can dig itself from it's IP (not the loopback address)
+    if pihole_dig=$(dig +tries=1 +time=2 -"${protocol}" "${random_url}" @"${pihole_address}" +short "${record_type}"); then
+        # show a success
+        log_write "${TICK} ${random_url} ${COL_GREEN}is ${pihole_dig}${COL_NC} via ${COL_CYAN}Pi-hole${COL_NC} (${pihole_address})"
     else
-      # If it's not, show it in red
-      log_write "${CROSS} ${COL_RED}${i}${COL_NC} daemon is ${COL_RED}${status_of_process}${COL_NC}"
+        # Othewise, show a failure
+        log_write "${CROSS} ${COL_RED}Failed to resolve${COL_NC} ${random_url} via ${COL_RED}Pi-hole${COL_NC} (${pihole_address})"
     fi
-  done
+
+    # Finally, we need to make sure legitimate queries can out to the Internet using an external, public DNS server
+    # We are using the static remote_url here instead of a random one because we know it works with IPv4 and IPv6
+    if remote_dig=$(dig +tries=1 +time=2 -"${protocol}" "${remote_url}" @${remote_address} +short "${record_type}" | head -n1); then
+        # If successful, the real IP of the domain will be returned instead of Pi-hole's IP
+        log_write "${TICK} ${remote_url} ${COL_GREEN}is ${remote_dig}${COL_NC} via ${COL_CYAN}a remote, public DNS server${COL_NC} (${remote_address})"
+    else
+        # Otherwise, show an error
+        log_write "${CROSS} ${COL_RED}Failed to resolve${COL_NC} ${remote_url} via ${COL_RED}a remote, public DNS server${COL_NC} (${remote_address})"
+    fi
+}
+
+process_status(){
+    # Check to make sure Pi-hole's services are running and active
+    echo_current_diagnostic "Pi-hole processes"
+    # Local iterator
+    local i
+    # For each process,
+    for i in "${PIHOLE_PROCESSES[@]}"; do
+        # If systemd
+        if command -v systemctl &> /dev/null; then
+            # get its status via systemctl
+            local status_of_process
+            status_of_process=$(systemctl is-active "${i}")
+        else
+            # Otherwise, use the service command
+            local status_of_process
+            status_of_process=$(service "${i}" status | awk '/Active:/ {print $2}') &> /dev/null
+        fi
+        # and print it out to the user
+        if [[ "${status_of_process}" == "active" ]]; then
+            # If it's active, show it in green
+            log_write "${TICK} ${COL_GREEN}${i}${COL_NC} daemon is ${COL_GREEN}${status_of_process}${COL_NC}"
+        else
+            # If it's not, show it in red
+            log_write "${CROSS} ${COL_RED}${i}${COL_NC} daemon is ${COL_RED}${status_of_process}${COL_NC}"
+        fi
+    done
 }
 
 make_array_from_file() {
-  local filename="${1}"
-  # The second argument can put a limit on how many line should be read from the file
-  # Since some of the files are so large, this is helpful to limit the output
-  local limit=${2}
-  # A local iterator for testing if we are at the limit above
-  local i=0
-  # Set the array to be empty so we can start fresh when the function is used
-  local file_content=()
-  # If the file is a directory
-  if [[ -d "${filename}" ]]; then
-    # do nothing since it cannot be parsed
-    :
-  else
-    # Otherwise, read the file line by line
-    while IFS= read -r line;do
-      # Othwerise, strip out comments and blank lines
-      new_line=$(echo "${line}" | sed -e 's/#.*$//' -e '/^$/d')
-      # If the line still has content (a non-zero value)
-      if [[ -n "${new_line}" ]]; then
-        # Put it into the array
-        file_content+=("${new_line}")
-      else
-        # Otherwise, it's a blank line or comment, so do nothing
+    local filename="${1}"
+    # The second argument can put a limit on how many line should be read from the file
+    # Since some of the files are so large, this is helpful to limit the output
+    local limit=${2}
+    # A local iterator for testing if we are at the limit above
+    local i=0
+    # Set the array to be empty so we can start fresh when the function is used
+    local file_content=()
+    # If the file is a directory
+    if [[ -d "${filename}" ]]; then
+        # do nothing since it cannot be parsed
         :
-      fi
-      # Increment the iterator +1
-      i=$((i+1))
-      # but if the limit of lines we want to see is exceeded
-      if [[ -z ${limit} ]]; then
-        # do nothing
-        :
-      elif [[ $i -eq ${limit} ]]; then
-        break
-      fi
-    done < "${filename}"
-      # Now the we have made an array of the file's content
-      for each_line in "${file_content[@]}"; do
-        # Print each line
-        # At some point, we may want to check the file line-by-line, so that's the reason for an array
-        log_write "   ${each_line}"
-      done
-   fi
+    else
+        # Otherwise, read the file line by line
+        while IFS= read -r line;do
+            # Othwerise, strip out comments and blank lines
+            new_line=$(echo "${line}" | sed -e 's/#.*$//' -e '/^$/d')
+            # If the line still has content (a non-zero value)
+            if [[ -n "${new_line}" ]]; then
+                # Put it into the array
+                file_content+=("${new_line}")
+            else
+                # Otherwise, it's a blank line or comment, so do nothing
+                :
+            fi
+            # Increment the iterator +1
+            i=$((i+1))
+            # but if the limit of lines we want to see is exceeded
+            if [[ -z ${limit} ]]; then
+                # do nothing
+                :
+            elif [[ $i -eq ${limit} ]]; then
+                break
+            fi
+        done < "${filename}"
+        # Now the we have made an array of the file's content
+        for each_line in "${file_content[@]}"; do
+            # Print each line
+            # At some point, we may want to check the file line-by-line, so that's the reason for an array
+            log_write "   ${each_line}"
+        done
+    fi
 }
 
 parse_file() {
-  # Set the first argument passed to this function as a named variable for better readability
-  local filename="${1}"
-  # Put the current Internal Field Separator into another variable so it can be restored later
-  OLD_IFS="$IFS"
-  # Get the lines that are in the file(s) and store them in an array for parsing later
-  IFS=$'\r\n' command eval 'file_info=( $(cat "${filename}") )'
-
-  # Set a named variable for better readability
-  local file_lines
-  # For each line in the file,
-  for file_lines in "${file_info[@]}"; do
-    if [[ ! -z "${file_lines}" ]]; then
-      # don't include the Web password hash
-      [[ "${file_linesline}" =~ ^\#.*$  || ! "${file_lines}" || "${file_lines}" == "WEBPASSWORD="* ]] && continue
-      # otherwise, display the lines of the file
-      log_write "    ${file_lines}"
+    # Set the first argument passed to this function as a named variable for better readability
+    local filename="${1}"
+    # Put the current Internal Field Separator into another variable so it can be restored later
+    OLD_IFS="$IFS"
+    # Get the lines that are in the file(s) and store them in an array for parsing later
+    local file_info
+    if [[ -f "$filename" ]]; then
+        #shellcheck disable=SC2016
+        IFS=$'\r\n' command eval 'file_info=( $(cat "${filename}") )'
+    else
+        read -a file_info <<< $filename
     fi
-  done
-  # Set the IFS back to what it was
-  IFS="$OLD_IFS"
+    # Set a named variable for better readability
+    local file_lines
+    # For each line in the file,
+    for file_lines in "${file_info[@]}"; do
+        if [[ ! -z "${file_lines}" ]]; then
+            # don't include the Web password hash
+            [[ "${file_lines}" =~ ^\#.*$  || ! "${file_lines}" || "${file_lines}" == "WEBPASSWORD="* ]] && continue
+            # otherwise, display the lines of the file
+            log_write "    ${file_lines}"
+        fi
+    done
+    # Set the IFS back to what it was
+    IFS="$OLD_IFS"
 }
 
 check_name_resolution() {
-  # Check name resoltion from localhost, Pi-hole's IP, and Google's name severs
-  # using the function we created earlier
-  dig_at 4 "${IPV4_ADDRESS%/*}"
-  # If IPv6 enabled,
-  if [[ "${IPV6_ADDRESS}" ]]; then
-    # check resolution
-    dig_at 6 "${IPV6_ADDRESS%/*}"
-  fi
+    # Check name resoltion from localhost, Pi-hole's IP, and Google's name severs
+    # using the function we created earlier
+    dig_at 4 "${IPV4_ADDRESS%/*}"
+    # If IPv6 enabled,
+    if [[ "${IPV6_ADDRESS}" ]]; then
+        # check resolution
+        dig_at 6 "${IPV6_ADDRESS%/*}"
+    fi
 }
 
 # This function can check a directory exists
 # Pi-hole has files in several places, so we will reuse this function
 dir_check() {
-  # Set the first argument passed to tihs function as a named variable for better readability
-  local directory="${1}"
-  # Display the current test that is running
-  echo_current_diagnostic "contents of ${COL_CYAN}${directory}${COL_NC}"
-  # For each file in the directory,
-  for filename in ${directory}; do
-    # check if exists first; if it does,
-    if ls "${filename}" 1> /dev/null 2>&1; then
-      # do nothing
-      :
-    else
-      # Otherwise, show an error
-      log_write "${COL_RED}${directory} does not exist.${COL_NC}"
-    fi
-  done
+    # Set the first argument passed to tihs function as a named variable for better readability
+    local directory="${1}"
+    # Display the current test that is running
+    echo_current_diagnostic "contents of ${COL_CYAN}${directory}${COL_NC}"
+    # For each file in the directory,
+    for filename in ${directory}; do
+        # check if exists first; if it does,
+        if ls "${filename}" 1> /dev/null 2>&1; then
+            # do nothing
+            :
+        else
+            # Otherwise, show an error
+            log_write "${COL_RED}${directory} does not exist.${COL_NC}"
+        fi
+    done
 }
 
 list_files_in_dir() {
-  # Set the first argument passed to tihs function as a named variable for better readability
-  local dir_to_parse="${1}"
-  # Store the files found in an array
-  local files_found=( $(ls "${dir_to_parse}") )
-  # For each file in the array,
-  for each_file in "${files_found[@]}"; do
-    if [[ -d "${dir_to_parse}/${each_file}" ]]; then
-      # If it's a directoy, do nothing
-      :
-    elif [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_BLOCKLIST_FILE}" ]] || \
-         [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_DEBUG_LOG}" ]] || \
-         [[ ${dir_to_parse}/${each_file} == ${PIHOLE_RAW_BLOCKLIST_FILES} ]] || \
-         [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_INSTALL_LOG_FILE}" ]] || \
-         [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_SETUP_VARS_FILE}" ]] || \
-         [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_LOG}" ]] || \
-         [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_WEB_SERVER_ACCESS_LOG_FILE}" ]] || \
-         [[ ${dir_to_parse}/${each_file} == ${PIHOLE_LOG_GZIPS} ]]; then
-           :
-    else
-      # Then, parse the file's content into an array so each line can be analyzed if need be
-      for i in "${!REQUIRED_FILES[@]}"; do
-        if [[ "${dir_to_parse}/${each_file}" == ${REQUIRED_FILES[$i]} ]]; then
-          # display the filename
-          log_write "\n${COL_GREEN}$(ls -ld ${dir_to_parse}/${each_file})${COL_NC}"
-          # Check if the file we want to view has a limit (because sometimes we just need a little bit of info from the file, not the entire thing)
-          case "${dir_to_parse}/${each_file}" in
-            # If it's Web server error log, just give the first 25 lines
-            "${PIHOLE_WEB_SERVER_ERROR_LOG_FILE}") make_array_from_file "${dir_to_parse}/${each_file}" 25
-            ;;
-            # Same for the FTL log
-            "${PIHOLE_FTL_LOG}") make_array_from_file "${dir_to_parse}/${each_file}" 25
-            ;;
-            # parse the file into an array in case we ever need to analyze it line-by-line
-            *) make_array_from_file "${dir_to_parse}/${each_file}";
-          esac
+    # Set the first argument passed to tihs function as a named variable for better readability
+    local dir_to_parse="${1}"
+    # Store the files found in an array
+    mapfile -t files_found < <(ls "${dir_to_parse}")
+    # For each file in the array,
+    for each_file in "${files_found[@]}"; do
+        if [[ -d "${dir_to_parse}/${each_file}" ]]; then
+            # If it's a directoy, do nothing
+            :
+        elif [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_BLOCKLIST_FILE}" ]] || \
+            [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_DEBUG_LOG}" ]] || \
+            [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_RAW_BLOCKLIST_FILES}" ]] || \
+            [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_INSTALL_LOG_FILE}" ]] || \
+            [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_SETUP_VARS_FILE}" ]] || \
+            [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_LOG}" ]] || \
+            [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_WEB_SERVER_ACCESS_LOG_FILE}" ]] || \
+            [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_LOG_GZIPS}" ]]; then
+            :
         else
-          # Otherwise, do nothing since it's not a file needed for Pi-hole so we don't care about it
-          :
+            # Then, parse the file's content into an array so each line can be analyzed if need be
+            for i in "${!REQUIRED_FILES[@]}"; do
+                if [[ "${dir_to_parse}/${each_file}" == "${REQUIRED_FILES[$i]}" ]]; then
+                    # display the filename
+                    log_write "\\n${COL_GREEN}$(ls -ld "${dir_to_parse}"/"${each_file}")${COL_NC}"
+                    # Check if the file we want to view has a limit (because sometimes we just need a little bit of info from the file, not the entire thing)
+                    case "${dir_to_parse}/${each_file}" in
+                        # If it's Web server error log, just give the first 25 lines
+                        "${PIHOLE_WEB_SERVER_ERROR_LOG_FILE}") make_array_from_file "${dir_to_parse}/${each_file}" 25
+                            ;;
+                        # Same for the FTL log
+                        "${PIHOLE_FTL_LOG}") head_tail_log "${dir_to_parse}/${each_file}" 35
+                            ;;
+                        # parse the file into an array in case we ever need to analyze it line-by-line
+                        *) make_array_from_file "${dir_to_parse}/${each_file}";
+                    esac
+                else
+                    # Otherwise, do nothing since it's not a file needed for Pi-hole so we don't care about it
+                    :
+                fi
+            done
         fi
-      done
-    fi
-  done
+    done
 }
 
 show_content_of_files_in_dir() {
-  # Set a local variable for better readability
-  local directory="${1}"
-  # Check if the directory exists
-  dir_check "${directory}"
-  # if it does, list the files in it
-  list_files_in_dir "${directory}"
+    # Set a local variable for better readability
+    local directory="${1}"
+    # Check if the directory exists
+    dir_check "${directory}"
+    # if it does, list the files in it
+    list_files_in_dir "${directory}"
 }
 
 show_content_of_pihole_files() {
-  # Show the content of the files in each of Pi-hole's folders
-  show_content_of_files_in_dir "${PIHOLE_DIRECTORY}"
-  show_content_of_files_in_dir "${DNSMASQ_D_DIRECTORY}"
-  show_content_of_files_in_dir "${WEB_SERVER_CONFIG_DIRECTORY}"
-  show_content_of_files_in_dir "${CRON_D_DIRECTORY}"
-  show_content_of_files_in_dir "${WEB_SERVER_LOG_DIRECTORY}"
-  show_content_of_files_in_dir "${LOG_DIRECTORY}"
+    # Show the content of the files in each of Pi-hole's folders
+    show_content_of_files_in_dir "${PIHOLE_DIRECTORY}"
+    show_content_of_files_in_dir "${DNSMASQ_D_DIRECTORY}"
+    show_content_of_files_in_dir "${WEB_SERVER_CONFIG_DIRECTORY}"
+    show_content_of_files_in_dir "${CRON_D_DIRECTORY}"
+    show_content_of_files_in_dir "${WEB_SERVER_LOG_DIRECTORY}"
+    show_content_of_files_in_dir "${LOG_DIRECTORY}"
+}
+
+head_tail_log() {
+    # The file being processed
+    local filename="${1}"
+    # The number of lines to use for head and tail
+    local qty="${2}"
+    local head_line
+    local tail_line
+    # Put the current Internal Field Separator into another variable so it can be restored later
+    OLD_IFS="$IFS"
+    # Get the lines that are in the file(s) and store them in an array for parsing later
+    IFS=$'\r\n'
+    local log_head=()
+    mapfile -t log_head < <(head -n "${qty}" "${filename}")
+    log_write "   ${COL_CYAN}-----head of $(basename "${filename}")------${COL_NC}"
+    for head_line in "${log_head[@]}"; do
+        log_write "   ${head_line}"
+    done
+    log_write ""
+    local log_tail=()
+    mapfile -t log_tail < <(tail -n "${qty}" "${filename}")
+    log_write "   ${COL_CYAN}-----tail of $(basename "${filename}")------${COL_NC}"
+    for tail_line in "${log_tail[@]}"; do
+        log_write "   ${tail_line}"
+    done
+    # Set the IFS back to what it was
+    IFS="$OLD_IFS"
 }
 
 analyze_gravity_list() {
-  echo_current_diagnostic "Gravity list"
-  local head_line
-  local tail_line
-  # Put the current Internal Field Separator into another variable so it can be restored later
-  OLD_IFS="$IFS"
-  # Get the lines that are in the file(s) and store them in an array for parsing later
-  IFS=$'\r\n'
-  local gravity_permissions=$(ls -ld "${PIHOLE_BLOCKLIST_FILE}")
-  log_write "${COL_GREEN}${gravity_permissions}${COL_NC}"
-  local gravity_head=()
-  gravity_head=( $(head -n 4 ${PIHOLE_BLOCKLIST_FILE}) )
-  log_write "   ${COL_CYAN}-----head of $(basename ${PIHOLE_BLOCKLIST_FILE})------${COL_NC}"
-  for head_line in "${gravity_head[@]}"; do
-    log_write "   ${head_line}"
-  done
-  log_write ""
-  local gravity_tail=()
-  gravity_tail=( $(tail -n 4 ${PIHOLE_BLOCKLIST_FILE}) )
-  log_write "   ${COL_CYAN}-----tail of $(basename ${PIHOLE_BLOCKLIST_FILE})------${COL_NC}"
-  for tail_line in "${gravity_tail[@]}"; do
-    log_write "   ${tail_line}"
-  done
-  # Set the IFS back to what it was
-  IFS="$OLD_IFS"
+    echo_current_diagnostic "Gravity list"
+    local head_line
+    local tail_line
+    # Put the current Internal Field Separator into another variable so it can be restored later
+    OLD_IFS="$IFS"
+    # Get the lines that are in the file(s) and store them in an array for parsing later
+    IFS=$'\r\n'
+    local gravity_permissions
+    gravity_permissions=$(ls -ld "${PIHOLE_BLOCKLIST_FILE}")
+    log_write "${COL_GREEN}${gravity_permissions}${COL_NC}"
+    local gravity_head=()
+    mapfile -t gravity_head < <(head -n 4 ${PIHOLE_BLOCKLIST_FILE})
+    log_write "   ${COL_CYAN}-----head of $(basename ${PIHOLE_BLOCKLIST_FILE})------${COL_NC}"
+    for head_line in "${gravity_head[@]}"; do
+        log_write "   ${head_line}"
+    done
+    log_write ""
+    local gravity_tail=()
+    mapfile -t gravity_tail < <(tail -n 4 ${PIHOLE_BLOCKLIST_FILE})
+    log_write "   ${COL_CYAN}-----tail of $(basename ${PIHOLE_BLOCKLIST_FILE})------${COL_NC}"
+    for tail_line in "${gravity_tail[@]}"; do
+        log_write "   ${tail_line}"
+    done
+    # Set the IFS back to what it was
+    IFS="$OLD_IFS"
 }
 
 analyze_pihole_log() {
-  echo_current_diagnostic "Pi-hole log"
-  local head_line
-  # Put the current Internal Field Separator into another variable so it can be restored later
-  OLD_IFS="$IFS"
-  # Get the lines that are in the file(s) and store them in an array for parsing later
-  IFS=$'\r\n'
-  local pihole_log_permissions=$(ls -ld "${PIHOLE_LOG}")
-  log_write "${COL_GREEN}${pihole_log_permissions}${COL_NC}"
-  local pihole_log_head=()
-  pihole_log_head=( $(head -n 20 ${PIHOLE_LOG}) )
-  log_write "   ${COL_CYAN}-----head of $(basename ${PIHOLE_LOG})------${COL_NC}"
-  local error_to_check_for
-  local line_to_obfuscate
-  local obfuscated_line
-  for head_line in "${pihole_log_head[@]}"; do
-    # A common error in the pihole.log is when there is a non-hosts formatted file
-    # that the DNS server is attempting to read.  Since it's not formatted
-    # correctly, there will be an entry for "bad address at line n"
-    # So we can check for that here and highlight it in red so the user can see it easily
-    error_to_check_for=$(echo ${head_line} | grep 'bad address at')
-    # Some users may not want to have the domains they visit sent to us
-    # To that end, we check for lines in the log that would contain a domain name
-    line_to_obfuscate=$(echo ${head_line} | grep ': query\|: forwarded\|: reply')
-    # If the variable contains a value, it found an error in the log
-    if [[ -n ${error_to_check_for} ]]; then
-      # So we can print it in red to make it visible to the user
-      log_write "   ${CROSS} ${COL_RED}${head_line}${COL_NC} (${FAQ_BAD_ADDRESS})"
-    else
-      # If the variable does not a value (the current default behavior), so do not obfuscate anything
-      if [[ -z ${OBFUSCATE} ]]; then
-        log_write "   ${head_line}"
-      # Othwerise, a flag was passed to this command to obfuscate domains in the log
-      else
-        # So first check if there are domains in the log that should be obfuscated
-        if [[ -n ${line_to_obfuscate} ]]; then
-          # If there are, we need to use awk to replace only the domain name (the 6th field in the log)
-          # so we substitue the domain for the placeholder value
-          obfuscated_line=$(echo ${line_to_obfuscate} | awk -v placeholder="${OBFUSCATED_PLACEHOLDER}" '{sub($6,placeholder); print $0}')
-          log_write "   ${obfuscated_line}"
+    echo_current_diagnostic "Pi-hole log"
+    local head_line
+    # Put the current Internal Field Separator into another variable so it can be restored later
+    OLD_IFS="$IFS"
+    # Get the lines that are in the file(s) and store them in an array for parsing later
+    IFS=$'\r\n'
+    local pihole_log_permissions
+    pihole_log_permissions=$(ls -ld "${PIHOLE_LOG}")
+    log_write "${COL_GREEN}${pihole_log_permissions}${COL_NC}"
+    local pihole_log_head=()
+    mapfile -t pihole_log_head < <(head -n 20 ${PIHOLE_LOG})
+    log_write "   ${COL_CYAN}-----head of $(basename ${PIHOLE_LOG})------${COL_NC}"
+    local error_to_check_for
+    local line_to_obfuscate
+    local obfuscated_line
+    for head_line in "${pihole_log_head[@]}"; do
+        # A common error in the pihole.log is when there is a non-hosts formatted file
+        # that the DNS server is attempting to read.  Since it's not formatted
+        # correctly, there will be an entry for "bad address at line n"
+        # So we can check for that here and highlight it in red so the user can see it easily
+        error_to_check_for=$(echo "${head_line}" | grep 'bad address at')
+        # Some users may not want to have the domains they visit sent to us
+        # To that end, we check for lines in the log that would contain a domain name
+        line_to_obfuscate=$(echo "${head_line}" | grep ': query\|: forwarded\|: reply')
+        # If the variable contains a value, it found an error in the log
+        if [[ -n ${error_to_check_for} ]]; then
+            # So we can print it in red to make it visible to the user
+            log_write "   ${CROSS} ${COL_RED}${head_line}${COL_NC} (${FAQ_BAD_ADDRESS})"
         else
-          log_write "   ${head_line}"
+            # If the variable does not a value (the current default behavior), so do not obfuscate anything
+            if [[ -z ${OBFUSCATE} ]]; then
+                log_write "   ${head_line}"
+            # Othwerise, a flag was passed to this command to obfuscate domains in the log
+            else
+                # So first check if there are domains in the log that should be obfuscated
+                if [[ -n ${line_to_obfuscate} ]]; then
+                    # If there are, we need to use awk to replace only the domain name (the 6th field in the log)
+                    # so we substitue the domain for the placeholder value
+                    obfuscated_line=$(echo "${line_to_obfuscate}" | awk -v placeholder="${OBFUSCATED_PLACEHOLDER}" '{sub($6,placeholder); print $0}')
+                    log_write "   ${obfuscated_line}"
+                else
+                    log_write "   ${head_line}"
+                fi
+            fi
         fi
-      fi
-    fi
-  done
-  log_write ""
-  # Set the IFS back to what it was
-  IFS="$OLD_IFS"
+    done
+    log_write ""
+    # Set the IFS back to what it was
+    IFS="$OLD_IFS"
 }
 
 tricorder_use_nc_or_ssl() {
-  # Users can submit their debug logs using nc (unencrypted) or openssl (enrypted) if available
-  # Check for openssl first since encryption is a good thing
-  if command -v openssl &> /dev/null; then
-    # If the command exists,
-    log_write "    * Using ${COL_GREEN}openssl${COL_NC} for transmission."
-    # encrypt and transmit the log and store the token returned in a variable
-    tricorder_token=$(< ${PIHOLE_DEBUG_LOG_SANITIZED} openssl s_client -quiet -connect tricorder.pi-hole.net:${TRICORDER_SSL_PORT_NUMBER} 2> /dev/null)
-  # Otherwise,
-  else
-    # use net cat
-    log_write "${INFO} Using ${COL_YELLOW}netcat${COL_NC} for transmission."
-    # Save the token returned by our server in a variable
-    tricorder_token=$(< ${PIHOLE_DEBUG_LOG_SANITIZED} nc tricorder.pi-hole.net ${TRICORDER_NC_PORT_NUMBER})
-  fi
+    # Users can submit their debug logs using nc (unencrypted) or openssl (enrypted) if available
+    # Check for openssl first since encryption is a good thing
+    if command -v openssl &> /dev/null; then
+        # If the command exists,
+        log_write "    * Using ${COL_GREEN}openssl${COL_NC} for transmission."
+        # encrypt and transmit the log and store the token returned in a variable
+        tricorder_token=$(< ${PIHOLE_DEBUG_LOG_SANITIZED} openssl s_client -quiet -connect tricorder.pi-hole.net:${TRICORDER_SSL_PORT_NUMBER} 2> /dev/null)
+    # Otherwise,
+    else
+        # use net cat
+        log_write "${INFO} Using ${COL_YELLOW}netcat${COL_NC} for transmission."
+        # Save the token returned by our server in a variable
+        tricorder_token=$(< ${PIHOLE_DEBUG_LOG_SANITIZED} nc tricorder.pi-hole.net ${TRICORDER_NC_PORT_NUMBER})
+    fi
 }
 
 
 upload_to_tricorder() {
-  local username="pihole"
-  # Set the permissions and owner
-  chmod 644 ${PIHOLE_DEBUG_LOG}
-  chown "$USER":"${username}" ${PIHOLE_DEBUG_LOG}
-
-  # Let the user know debugging is complete with something strikingly visual
-  log_write ""
-  log_write "${COL_PURPLE}********************************************${COL_NC}"
-  log_write "${COL_PURPLE}********************************************${COL_NC}"
-	log_write "${TICK} ${COL_GREEN}** FINISHED DEBUGGING! **${COL_NC}\n"
-
-  # Provide information on what they should do with their token
-	log_write "    * The debug log can be uploaded to tricorder.pi-hole.net for sharing with developers only."
-  log_write "    * For more information, see: ${TRICORDER_CONTEST}"
-  log_write "    * If available, we'll use openssl to upload the log, otherwise it will fall back to netcat."
-  # If pihole -d is running automatically (usually throught the dashboard)
-	if [[ "${AUTOMATED}" ]]; then
-    # let the user know
-    log_write "${INFO} Debug script running in automated mode"
-    # and then decide again which tool to use to submit it
-    tricorder_use_nc_or_ssl
-  # If we're not running in automated mode,
-	else
-    echo ""
-    # give the user a choice of uploading it or not
-    # Users can review the log file locally (or the output of the script since they are the same) and try to self-diagnose their problem
-	  read -r -p "[?] Would you like to upload the log? [y/N] " response
-	  case ${response} in
-      # If they say yes, run our function for uploading the log
-		  [yY][eE][sS]|[yY]) tricorder_use_nc_or_ssl;;
-      # If they choose no, just exit out of the script
-		  *) log_write "    * Log will ${COL_GREEN}NOT${COL_NC} be uploaded to tricorder.";exit;
-	  esac
-  fi
-	# Check if tricorder.pi-hole.net is reachable and provide token
-  # along with some additional useful information
-	if [[ -n "${tricorder_token}" ]]; then
-    # Again, try to make this visually striking so the user realizes they need to do something with this information
-    # Namely, provide the Pi-hole devs with the token
-    log_write ""
-    log_write "${COL_PURPLE}***********************************${COL_NC}"
-    log_write "${COL_PURPLE}***********************************${COL_NC}"
-		log_write "${TICK} Your debug token is: ${COL_GREEN}${tricorder_token}${COL_NC}"
-    log_write "${COL_PURPLE}***********************************${COL_NC}"
-    log_write "${COL_PURPLE}***********************************${COL_NC}"
+    local username="pihole"
+    # Set the permissions and owner
+    chmod 644 ${PIHOLE_DEBUG_LOG}
+    chown "$USER":"${username}" ${PIHOLE_DEBUG_LOG}
+
+    # Let the user know debugging is complete with something strikingly visual
     log_write ""
-		log_write "   * Provide the token above to the Pi-hole team for assistance at"
-		log_write "   * ${FORUMS_URL}"
-    log_write "   * Your log will self-destruct on our server after ${COL_RED}48 hours${COL_NC}."
-  # If no token was generated
-  else
-    # Show an error and some help instructions
-		log_write "${CROSS}  ${COL_RED}There was an error uploading your debug log.${COL_NC}"
-		log_write "   * Please try again or contact the Pi-hole team for assistance."
-	fi
+    log_write "${COL_PURPLE}********************************************${COL_NC}"
+    log_write "${COL_PURPLE}********************************************${COL_NC}"
+    log_write "${TICK} ${COL_GREEN}** FINISHED DEBUGGING! **${COL_NC}\\n"
+
+    # Provide information on what they should do with their token
+    log_write "    * The debug log can be uploaded to tricorder.pi-hole.net for sharing with developers only."
+    log_write "    * For more information, see: ${TRICORDER_CONTEST}"
+    log_write "    * If available, we'll use openssl to upload the log, otherwise it will fall back to netcat."
+    # If pihole -d is running automatically (usually throught the dashboard)
+    if [[ "${AUTOMATED}" ]]; then
+        # let the user know
+        log_write "${INFO} Debug script running in automated mode"
+        # and then decide again which tool to use to submit it
+        tricorder_use_nc_or_ssl
+        # If we're not running in automated mode,
+    else
+        echo ""
+        # give the user a choice of uploading it or not
+        # Users can review the log file locally (or the output of the script since they are the same) and try to self-diagnose their problem
+        read -r -p "[?] Would you like to upload the log? [y/N] " response
+        case ${response} in
+            # If they say yes, run our function for uploading the log
+            [yY][eE][sS]|[yY]) tricorder_use_nc_or_ssl;;
+            # If they choose no, just exit out of the script
+            *) log_write "    * Log will ${COL_GREEN}NOT${COL_NC} be uploaded to tricorder.";exit;
+        esac
+    fi
+    # Check if tricorder.pi-hole.net is reachable and provide token
+    # along with some additional useful information
+    if [[ -n "${tricorder_token}" ]]; then
+        # Again, try to make this visually striking so the user realizes they need to do something with this information
+        # Namely, provide the Pi-hole devs with the token
+        log_write ""
+        log_write "${COL_PURPLE}***********************************${COL_NC}"
+        log_write "${COL_PURPLE}***********************************${COL_NC}"
+        log_write "${TICK} Your debug token is: ${COL_GREEN}${tricorder_token}${COL_NC}"
+        log_write "${COL_PURPLE}***********************************${COL_NC}"
+        log_write "${COL_PURPLE}***********************************${COL_NC}"
+        log_write ""
+        log_write "   * Provide the token above to the Pi-hole team for assistance at"
+        log_write "   * ${FORUMS_URL}"
+        log_write "   * Your log will self-destruct on our server after ${COL_RED}48 hours${COL_NC}."
+    # If no token was generated
+    else
+        # Show an error and some help instructions
+        log_write "${CROSS}  ${COL_RED}There was an error uploading your debug log.${COL_NC}"
+        log_write "   * Please try again or contact the Pi-hole team for assistance."
+    fi
     # Finally, show where the log file is no matter the outcome of the function so users can look at it
-		log_write "   * A local copy of the debug log can be found at: ${COL_CYAN}${PIHOLE_DEBUG_LOG_SANITIZED}${COL_NC}\n"
+    log_write "   * A local copy of the debug log can be found at: ${COL_CYAN}${PIHOLE_DEBUG_LOG_SANITIZED}${COL_NC}\\n"
 }
 
 # Run through all the functions we made
@@ -1165,6 +1223,7 @@ parse_setup_vars
 check_x_headers
 analyze_gravity_list
 show_content_of_pihole_files
+parse_locale
 analyze_pihole_log
 copy_to_debug_log
 upload_to_tricorder
diff --git a/advanced/Scripts/piholeLogFlush.sh b/advanced/Scripts/piholeLogFlush.sh
index 5fd9832e7c537fb1b47448f5434c4cf24ae4f419..4847282f0951a6221bc16ba2b8617a4b55ae4e8d 100755
--- a/advanced/Scripts/piholeLogFlush.sh
+++ b/advanced/Scripts/piholeLogFlush.sh
@@ -16,48 +16,51 @@ source ${colfile}
 # Constructed to return nothing when
 # a) the setting is not present in the config file, or
 # b) the setting is commented out (e.g. "#DBFILE=...")
-DBFILE="$(sed -n -e 's/^\s^.DBFILE\s*=\s*//p' /etc/pihole/pihole-FTL.conf)"
+FTLconf="/etc/pihole/pihole-FTL.conf"
+if [ -e "$FTLconf" ]; then
+    DBFILE="$(sed -n -e 's/^\s*DBFILE\s*=\s*//p' ${FTLconf})"
+fi
 # Test for empty string. Use standard path in this case.
 if [ -z "$DBFILE" ]; then
-  DBFILE="/etc/pihole/pihole-FTL.db"
+    DBFILE="/etc/pihole/pihole-FTL.db"
 fi
 
 if [[ "$@" != *"quiet"* ]]; then
-  echo -ne "  ${INFO} Flushing /var/log/pihole.log ..."
+    echo -ne "  ${INFO} Flushing /var/log/pihole.log ..."
 fi
 if [[ "$@" == *"once"* ]]; then
-  # Nightly logrotation
-  if command -v /usr/sbin/logrotate >/dev/null; then
-    # Logrotate once
-    /usr/sbin/logrotate --force /etc/pihole/logrotate
-  else
-    # Copy pihole.log over to pihole.log.1
-    # and empty out pihole.log
-    # Note that moving the file is not an option, as
-    # dnsmasq would happily continue writing into the
-    # moved file (it will have the same file handler)
-    cp /var/log/pihole.log /var/log/pihole.log.1
-    echo " " > /var/log/pihole.log
-  fi
+    # Nightly logrotation
+    if command -v /usr/sbin/logrotate >/dev/null; then
+        # Logrotate once
+        /usr/sbin/logrotate --force /etc/pihole/logrotate
+    else
+        # Copy pihole.log over to pihole.log.1
+        # and empty out pihole.log
+        # Note that moving the file is not an option, as
+        # dnsmasq would happily continue writing into the
+        # moved file (it will have the same file handler)
+        cp /var/log/pihole.log /var/log/pihole.log.1
+        echo " " > /var/log/pihole.log
+    fi
 else
-  # Manual flushing
-  if command -v /usr/sbin/logrotate >/dev/null; then
-    # Logrotate twice to move all data out of sight of FTL
-    /usr/sbin/logrotate --force /etc/pihole/logrotate; sleep 3
-    /usr/sbin/logrotate --force /etc/pihole/logrotate
-  else
-    # Flush both pihole.log and pihole.log.1 (if existing)
-    echo " " > /var/log/pihole.log
-    if [ -f /var/log/pihole.log.1 ]; then
-      echo " " > /var/log/pihole.log.1
+    # Manual flushing
+    if command -v /usr/sbin/logrotate >/dev/null; then
+        # Logrotate twice to move all data out of sight of FTL
+        /usr/sbin/logrotate --force /etc/pihole/logrotate; sleep 3
+        /usr/sbin/logrotate --force /etc/pihole/logrotate
+    else
+        # Flush both pihole.log and pihole.log.1 (if existing)
+        echo " " > /var/log/pihole.log
+        if [ -f /var/log/pihole.log.1 ]; then
+            echo " " > /var/log/pihole.log.1
+        fi
     fi
-  fi
-  # Delete most recent 24 hours from FTL's database, leave even older data intact (don't wipe out all history)
-  deleted=$(sqlite3 "${DBFILE}" "DELETE FROM queries WHERE timestamp >= strftime('%s','now')-86400; select changes() from queries limit 1")
+    # Delete most recent 24 hours from FTL's database, leave even older data intact (don't wipe out all history)
+    deleted=$(sqlite3 "${DBFILE}" "DELETE FROM queries WHERE timestamp >= strftime('%s','now')-86400; select changes() from queries limit 1")
 
 fi
 
 if [[ "$@" != *"quiet"* ]]; then
-  echo -e "${OVER}  ${TICK} Flushed /var/log/pihole.log"
-  echo -e "  ${TICK} Deleted ${deleted} queries from database"
+    echo -e "${OVER}  ${TICK} Flushed /var/log/pihole.log"
+    echo -e "  ${TICK} Deleted ${deleted} queries from database"
 fi
diff --git a/advanced/Scripts/query.sh b/advanced/Scripts/query.sh
new file mode 100644
index 0000000000000000000000000000000000000000..b599aa6be3910d91a1779e14c328f14c276256b7
--- /dev/null
+++ b/advanced/Scripts/query.sh
@@ -0,0 +1,239 @@
+#!/usr/bin/env bash
+# shellcheck disable=SC1090
+# Pi-hole: A black hole for Internet advertisements
+# (c) 2018 Pi-hole, LLC (https://pi-hole.net)
+# Network-wide ad blocking via your own hardware.
+#
+# Query Domain Lists
+#
+# This file is copyright under the latest version of the EUPL.
+# Please see LICENSE file for your rights under this license.
+
+# Globals
+piholeDir="/etc/pihole"
+adListsList="$piholeDir/adlists.list"
+wildcardlist="/etc/dnsmasq.d/03-pihole-wildcard.conf"
+options="$*"
+adlist=""
+all=""
+exact=""
+blockpage=""
+matchType="match"
+
+colfile="/opt/pihole/COL_TABLE"
+source "${colfile}"
+
+# Print each subdomain
+# e.g: foo.bar.baz.com = "foo.bar.baz.com bar.baz.com baz.com com"
+processWildcards() {
+    IFS="." read -r -a array <<< "${1}"
+    for (( i=${#array[@]}-1; i>=0; i-- )); do
+        ar=""
+        for (( j=${#array[@]}-1; j>${#array[@]}-i-2; j-- )); do
+            if [[ $j == $((${#array[@]}-1)) ]]; then
+                ar="${array[$j]}"
+            else
+                ar="${array[$j]}.${ar}"
+            fi
+        done
+        echo "${ar}"
+    done
+}
+
+# Scan an array of files for matching strings
+scanList(){
+    # Escape full stops
+    local domain="${1//./\\.}" lists="${2}" type="${3:-}"
+
+    # Prevent grep from printing file path
+    cd "$piholeDir" || exit 1
+
+    # Prevent grep -i matching slowly: http://bit.ly/2xFXtUX
+    export LC_CTYPE=C
+
+    # /dev/null forces filename to be printed when only one list has been generated
+    # shellcheck disable=SC2086
+    case "${type}" in
+        "exact" ) grep -i -E -l "(^|\\s)${domain}($|\\s|#)" ${lists} /dev/null 2>/dev/null;;
+        "wc"    ) grep -i -o -m 1 "/${domain}/" ${lists} 2>/dev/null;;
+        *       ) grep -i "${domain}" ${lists} /dev/null 2>/dev/null;;
+    esac
+}
+
+if [[ "${options}" == "-h" ]] || [[ "${options}" == "--help" ]]; then
+    echo "Usage: pihole -q [option] <domain>
+Example: 'pihole -q -exact domain.com'
+Query the adlists for a specified domain
+
+Options:
+  -adlist             Print the name of the block list URL
+  -exact              Search the block lists for exact domain matches
+  -all                Return all query matches within a block list
+  -h, --help          Show this help dialog"
+  exit 0
+fi
+
+if [[ ! -e "$adListsList" ]]; then
+    echo -e "${COL_LIGHT_RED}The file $adListsList was not found${COL_NC}"
+    exit 1
+fi
+
+# Handle valid options
+if [[ "${options}" == *"-bp"* ]]; then
+    exact="exact"; blockpage=true
+else
+    [[ "${options}" == *"-adlist"* ]] && adlist=true
+    [[ "${options}" == *"-all"* ]] && all=true
+    if [[ "${options}" == *"-exact"* ]]; then
+        exact="exact"; matchType="exact ${matchType}"
+    fi
+fi
+
+# Strip valid options, leaving only the domain and invalid options
+# This allows users to place the options before or after the domain
+options=$(sed -E 's/ ?-(bp|adlists?|all|exact) ?//g' <<< "${options}")
+
+# Handle remaining options
+# If $options contain non ASCII characters, convert to punycode
+case "${options}" in
+    ""             ) str="No domain specified";;
+    *" "*          ) str="Unknown query option specified";;
+    *[![:ascii:]]* ) domainQuery=$(idn2 "${options}");;
+    *              ) domainQuery="${options}";;
+esac
+
+if [[ -n "${str:-}" ]]; then
+    echo -e "${str}${COL_NC}\\nTry 'pihole -q --help' for more information."
+    exit 1
+fi
+
+# Scan Whitelist and Blacklist
+lists="whitelist.txt blacklist.txt"
+mapfile -t results <<< "$(scanList "${domainQuery}" "${lists}" "${exact}")"
+if [[ -n "${results[*]}" ]]; then
+    wbMatch=true
+    # Loop through each result in order to print unique file title once
+    for result in "${results[@]}"; do
+        fileName="${result%%.*}"
+        if [[ -n "${blockpage}" ]]; then
+            echo "Ï€ ${result}"
+            exit 0
+        elif [[ -n "${exact}" ]]; then
+            echo " ${matchType^} found in ${COL_BOLD}${fileName^}${COL_NC}"
+        else
+            # Only print filename title once per file
+            if [[ ! "${fileName}" == "${fileName_prev:-}" ]]; then
+                echo " ${matchType^} found in ${COL_BOLD}${fileName^}${COL_NC}"
+                fileName_prev="${fileName}"
+            fi
+        echo "   ${result#*:}"
+        fi
+    done
+fi
+
+# Scan Wildcards
+if [[ -e "${wildcardlist}" ]]; then
+    # Determine all subdomains, domain and TLDs
+    mapfile -t wildcards <<< "$(processWildcards "${domainQuery}")"
+    for match in "${wildcards[@]}"; do
+        # Search wildcard list for matches
+        mapfile -t results <<< "$(scanList "${match}" "${wildcardlist}" "wc")"
+        if [[ -n "${results[*]}" ]]; then
+            if [[ -z "${wcMatch:-}" ]] && [[ -z "${blockpage}" ]]; then
+                wcMatch=true
+                echo " ${matchType^} found in ${COL_BOLD}Wildcards${COL_NC}:"
+            fi
+            case "${blockpage}" in
+                true ) echo "Ï€ ${wildcardlist##*/}"; exit 0;;
+                *    ) echo "   *.${match}";;
+            esac
+        fi
+    done
+fi
+
+# Get version sorted *.domains filenames (without dir path)
+lists=("$(cd "$piholeDir" || exit 0; printf "%s\\n" -- *.domains | sort -V)")
+
+# Query blocklists for occurences of domain
+mapfile -t results <<< "$(scanList "${domainQuery}" "${lists[*]}" "${exact}")"
+
+# Handle notices
+if [[ -z "${wbMatch:-}" ]] && [[ -z "${wcMatch:-}" ]] && [[ -z "${results[*]}" ]]; then
+    echo -e "  ${INFO} No ${exact/t/t }results found for ${COL_BOLD}${domainQuery}${COL_NC} within the block lists"
+    exit 0
+elif [[ -z "${results[*]}" ]]; then
+    # Result found in WL/BL/Wildcards
+    exit 0
+elif [[ -z "${all}" ]] && [[ "${#results[*]}" -ge 100 ]]; then
+    echo -e "  ${INFO} Over 100 ${exact/t/t }results found for ${COL_BOLD}${domainQuery}${COL_NC}
+        This can be overridden using the -all option"
+    exit 0
+fi
+
+# Remove unwanted content from non-exact $results
+if [[ -z "${exact}" ]]; then
+    # Delete lines starting with #
+    # Remove comments after domain
+    # Remove hosts format IP address
+    mapfile -t results <<< "$(IFS=$'\n'; sed \
+        -e "/:#/d" \
+        -e "s/[ \\t]#.*//g" \
+        -e "s/:.*[ \\t]/:/g" \
+        <<< "${results[*]}")"
+    # Exit if result was in a comment
+    [[ -z "${results[*]}" ]] && exit 0
+fi
+
+# Get adlist file content as array
+if [[ -n "${adlist}" ]] || [[ -n "${blockpage}" ]]; then
+    for adlistUrl in $(< "${adListsList}"); do
+        if [[ "${adlistUrl:0:4}" =~ (http|www.) ]]; then
+            adlists+=("${adlistUrl}")
+        fi
+    done
+fi
+
+# Print "Exact matches for" title
+if [[ -n "${exact}" ]] && [[ -z "${blockpage}" ]]; then
+    plural=""; [[ "${#results[*]}" -gt 1 ]] && plural="es"
+    echo " ${matchType^}${plural} for ${COL_BOLD}${domainQuery}${COL_NC} found in:"
+fi
+
+for result in "${results[@]}"; do
+    fileName="${result/:*/}"
+
+    # Determine *.domains URL using filename's number
+    if [[ -n "${adlist}" ]] || [[ -n "${blockpage}" ]]; then
+        fileNum="${fileName/list./}"; fileNum="${fileNum%%.*}"
+        fileName="${adlists[$fileNum]}"
+
+        # Discrepency occurs when adlists has been modified, but Gravity has not been run
+        if [[ -z "${fileName}" ]]; then
+            fileName="${COL_LIGHT_RED}(no associated adlists URL found)${COL_NC}"
+        fi
+    fi
+
+    if [[ -n "${blockpage}" ]]; then
+        echo "${fileNum} ${fileName}"
+    elif [[ -n "${exact}" ]]; then
+        echo "   ${fileName}"
+    else
+        if [[ ! "${fileName}" == "${fileName_prev:-}" ]]; then
+            count=""
+            echo " ${matchType^} found in ${COL_BOLD}${fileName}${COL_NC}:"
+            fileName_prev="${fileName}"
+        fi
+        : $((count++))
+
+        # Print matching domain if $max_count has not been reached
+        [[ -z "${all}" ]] && max_count="50"
+        if [[ -z "${all}" ]] && [[ "${count}" -ge "${max_count}" ]]; then
+            [[ "${count}" -gt "${max_count}" ]] && continue
+            echo "   ${COL_GRAY}Over ${count} results found, skipping rest of file${COL_NC}"
+        else
+            echo "   ${result#*:}"
+        fi
+    fi
+done
+
+exit 0
diff --git a/advanced/Scripts/setupLCD.sh b/advanced/Scripts/setupLCD.sh
index d780da57603336446b09f510060cc4a923995156..00eb963f7a7499ae4168253d479efc88c4a45ddd 100755
--- a/advanced/Scripts/setupLCD.sh
+++ b/advanced/Scripts/setupLCD.sh
@@ -15,28 +15,28 @@
 # Borrowed from adafruit-pitft-helper < borrowed from raspi-config
 # https://github.com/adafruit/Adafruit-PiTFT-Helper/blob/master/adafruit-pitft-helper#L324-L334
 getInitSys() {
-	if command -v systemctl > /dev/null && systemctl | grep -q '\-\.mount'; then
-		SYSTEMD=1
-	elif [ -f /etc/init.d/cron ] && [ ! -h /etc/init.d/cron ]; then
-		SYSTEMD=0
-	else
-		echo "Unrecognised init system"
-		return 1
-	fi
+    if command -v systemctl > /dev/null && systemctl | grep -q '\-\.mount'; then
+        SYSTEMD=1
+    elif [ -f /etc/init.d/cron ] && [ ! -h /etc/init.d/cron ]; then
+        SYSTEMD=0
+    else
+        echo "Unrecognised init system"
+        return 1
+    fi
 }
 
 # Borrowed from adafruit-pitft-helper:
 # https://github.com/adafruit/Adafruit-PiTFT-Helper/blob/master/adafruit-pitft-helper#L274-L285
 autoLoginPiToConsole() {
-	if [ -e /etc/init.d/lightdm ]; then
-		if [ ${SYSTEMD} -eq 1 ]; then
-			systemctl set-default multi-user.target
-			ln -fs /etc/systemd/system/autologin@.service /etc/systemd/system/getty.target.wants/getty@tty1.service
-		else
-			update-rc.d lightdm disable 2
-			sed /etc/inittab -i -e "s/1:2345:respawn:\/sbin\/getty --noclear 38400 tty1/1:2345:respawn:\/bin\/login -f pi tty1 <\/dev\/tty1 >\/dev\/tty1 2>&1/"
-		fi
-	fi
+    if [ -e /etc/init.d/lightdm ]; then
+        if [ ${SYSTEMD} -eq 1 ]; then
+            systemctl set-default multi-user.target
+            ln -fs /etc/systemd/system/autologin@.service /etc/systemd/system/getty.target.wants/getty@tty1.service
+        else
+            update-rc.d lightdm disable 2
+            sed /etc/inittab -i -e "s/1:2345:respawn:\/sbin\/getty --noclear 38400 tty1/1:2345:respawn:\/bin\/login -f pi tty1 <\/dev\/tty1 >\/dev\/tty1 2>&1/"
+        fi
+    fi
 }
 
 ######### SCRIPT ###########
diff --git a/advanced/Scripts/update.sh b/advanced/Scripts/update.sh
index a4ada4c8f1545663c6a2909a18311c3ec850ef54..59212a948120453313188924067f9af7f541416a 100755
--- a/advanced/Scripts/update.sh
+++ b/advanced/Scripts/update.sh
@@ -19,6 +19,9 @@ readonly PI_HOLE_FILES_DIR="/etc/.pihole"
 # shellcheck disable=SC2034
 PH_TEST=true
 
+# when --check-only is passed to this script, it will not perform the actual update
+CHECK_ONLY=false
+
 # shellcheck disable=SC1090
 source "${PI_HOLE_FILES_DIR}/automated install/basic-install.sh"
 # shellcheck disable=SC1091
@@ -28,201 +31,161 @@ source "/opt/pihole/COL_TABLE"
 # make_repo() sourced from basic-install.sh
 # update_repo() source from basic-install.sh
 # getGitFiles() sourced from basic-install.sh
+# get_binary_name() sourced from basic-install.sh
+# FTLcheckUpdate() sourced from basic-install.sh
 
 GitCheckUpdateAvail() {
-  local directory="${1}"
-  curdir=$PWD
-  cd "${directory}" || return
-
-  # Fetch latest changes in this repo
-  git fetch --quiet origin
-
-  # @ alone is a shortcut for HEAD. Older versions of git
-  # need @{0}
-  LOCAL="$(git rev-parse "@{0}")"
-
-  # The suffix @{upstream} to a branchname
-  # (short form <branchname>@{u}) refers
-  # to the branch that the branch specified
-  # by branchname is set to build on top of#
-  # (configured with branch.<name>.remote and
-  # branch.<name>.merge). A missing branchname
-  # defaults to the current one.
-  REMOTE="$(git rev-parse "@{upstream}")"
-
-  if [[ "${#LOCAL}" == 0 ]]; then
-    echo -e "\\n  ${COL_LIGHT_RED}Error: Local revision could not be obtained, please contact Pi-hole Support
-  Additional debugging output:${COL_NC}"
-    git status
-    exit
-  fi
-  if [[ "${#REMOTE}" == 0 ]]; then
-    echo -e "\\n  ${COL_LIGHT_RED}Error: Remote revision could not be obtained, please contact Pi-hole Support
-  Additional debugging output:${COL_NC}"
-    git status
-    exit
-  fi
-
-  # Change back to original directory
-  cd "${curdir}" || exit
-
-  if [[ "${LOCAL}" != "${REMOTE}" ]]; then
-    # Local branch is behind remote branch -> Update
-    return 0
-  else
-    # Local branch is up-to-date or in a situation
-    # where this updater cannot be used (like on a
-    # branch that exists only locally)
-    return 1
-  fi
-}
+    local directory
+    directory="${1}"
+    curdir=$PWD
+    cd "${directory}" || return
+
+    # Fetch latest changes in this repo
+    git fetch --quiet origin
+
+    # @ alone is a shortcut for HEAD. Older versions of git
+    # need @{0}
+    LOCAL="$(git rev-parse "@{0}")"
+
+    # The suffix @{upstream} to a branchname
+    # (short form <branchname>@{u}) refers
+    # to the branch that the branch specified
+    # by branchname is set to build on top of#
+    # (configured with branch.<name>.remote and
+    # branch.<name>.merge). A missing branchname
+    # defaults to the current one.
+    REMOTE="$(git rev-parse "@{upstream}")"
+
+    if [[ "${#LOCAL}" == 0 ]]; then
+        echo -e "\\n  ${COL_LIGHT_RED}Error: Local revision could not be obtained, please contact Pi-hole Support"
+        echo -e "  Additional debugging output:${COL_NC}"
+        git status
+        exit
+    fi
+    if [[ "${#REMOTE}" == 0 ]]; then
+        echo -e "\\n  ${COL_LIGHT_RED}Error: Remote revision could not be obtained, please contact Pi-hole Support"
+        echo -e "  Additional debugging output:${COL_NC}"
+        git status
+        exit
+    fi
+
+    # Change back to original directory
+    cd "${curdir}" || exit
 
-FTLcheckUpdate() {
-	local FTLversion
-	FTLversion=$(/usr/bin/pihole-FTL tag)
-	local FTLlatesttag
-	FTLlatesttag=$(curl -sI https://github.com/pi-hole/FTL/releases/latest | grep 'Location' | awk -F '/' '{print $NF}' | tr -d '\r\n')
-
-	if [[ "${FTLversion}" != "${FTLlatesttag}" ]]; then
-		return 0
-	else
-		return 1
-	fi
+    if [[ "${LOCAL}" != "${REMOTE}" ]]; then
+        # Local branch is behind remote branch -> Update
+        return 0
+    else
+        # Local branch is up-to-date or in a situation
+        # where this updater cannot be used (like on a
+        # branch that exists only locally)
+        return 1
+    fi
 }
 
 main() {
-  local pihole_version_current
-  local web_version_current
-  local basicError="\\n  ${COL_LIGHT_RED}Unable to complete update, please contact Pi-hole Support${COL_NC}"
-  
-  # shellcheck disable=1090,2154
-  source "${setupVars}"
-
-  # This is unlikely
-  if ! is_repo "${PI_HOLE_FILES_DIR}" ; then
-    echo -e "\\n  ${COL_LIGHT_RED}Error: Core Pi-hole repo is missing from system!
-  Please re-run install script from https://pi-hole.net${COL_NC}"
-    exit 1;
-  fi
-
-  echo -e "  ${INFO} Checking for updates..."
-
-  if GitCheckUpdateAvail "${PI_HOLE_FILES_DIR}" ; then
-    core_update=true
-    echo -e "  ${INFO} Pi-hole Core:\\t${COL_YELLOW}update available${COL_NC}"
-  else
-    core_update=false
-    echo -e "  ${INFO} Pi-hole Core:\\t${COL_LIGHT_GREEN}up to date${COL_NC}"
-  fi
+    local basicError="\\n  ${COL_LIGHT_RED}Unable to complete update, please contact Pi-hole Support${COL_NC}"
+    local core_update
+    local web_update
+    local FTL_update
 
-  if FTLcheckUpdate ; then
-    FTL_update=true
-    echo -e "  ${INFO} FTL:\\t\\t${COL_YELLOW}update available${COL_NC}"
-  else
+    core_update=false
+    web_update=false
     FTL_update=false
-    echo -e "  ${INFO} FTL:\\t\\t${COL_LIGHT_GREEN}up to date${COL_NC}"
-  fi
 
-  # Logic: Don't update FTL when there is a core update available
-  # since the core update will run the installer which will itself
-  # re-install (i.e. update) FTL
-  if ${FTL_update} && ! ${core_update}; then
-    echo ""
-    echo -e "  ${INFO} FTL out of date"
-    FTLdetect
-    echo ""
-  fi
+    # shellcheck disable=1090,2154
+    source "${setupVars}"
 
-  if [[ "${INSTALL_WEB}" == true ]]; then
-    if ! is_repo "${ADMIN_INTERFACE_DIR}" ; then
-      echo -e "\\n  ${COL_LIGHT_RED}Error: Web Admin repo is missing from system!
-  Please re-run install script from https://pi-hole.net${COL_NC}"
-      exit 1;
+    # This is unlikely
+    if ! is_repo "${PI_HOLE_FILES_DIR}" ; then
+        echo -e "\\n  ${COL_LIGHT_RED}Error: Core Pi-hole repo is missing from system!"
+        echo -e "  Please re-run install script from https://pi-hole.net${COL_NC}"
+        exit 1;
     fi
 
-    if GitCheckUpdateAvail "${ADMIN_INTERFACE_DIR}" ; then
-      web_update=true
-      echo -e "  ${INFO} Web Interface:\\t${COL_YELLOW}update available${COL_NC}"
+    echo -e "  ${INFO} Checking for updates..."
+
+    if GitCheckUpdateAvail "${PI_HOLE_FILES_DIR}" ; then
+        core_update=true
+        echo -e "  ${INFO} Pi-hole Core:\\t${COL_YELLOW}update available${COL_NC}"
     else
-      web_update=false
-      echo -e "  ${INFO} Web Interface:\\t${COL_LIGHT_GREEN}up to date${COL_NC}"
+        core_update=false
+        echo -e "  ${INFO} Pi-hole Core:\\t${COL_LIGHT_GREEN}up to date${COL_NC}"
+    fi
+
+    if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
+        if ! is_repo "${ADMIN_INTERFACE_DIR}" ; then
+            echo -e "\\n  ${COL_LIGHT_RED}Error: Web Admin repo is missing from system!"
+            echo -e "  Please re-run install script from https://pi-hole.net${COL_NC}"
+            exit 1;
+        fi
+
+        if GitCheckUpdateAvail "${ADMIN_INTERFACE_DIR}" ; then
+            web_update=true
+            echo -e "  ${INFO} Web Interface:\\t${COL_YELLOW}update available${COL_NC}"
+        else
+            web_update=false
+            echo -e "  ${INFO} Web Interface:\\t${COL_LIGHT_GREEN}up to date${COL_NC}"
+        fi
     fi
 
-    # Logic
-    # If Core up to date AND web up to date:
-    #            Do nothing
-    # If Core up to date AND web NOT up to date:
-    #            Pull web repo
-    # If Core NOT up to date AND web up to date:
-    #            pull pihole repo, run install --unattended -- reconfigure
-    # if Core NOT up to date AND web NOT up to date:
-    #            pull pihole repo run install --unattended
-
-    if ! ${core_update} && ! ${web_update} ; then
-      if ! ${FTL_update} ; then
+    if FTLcheckUpdate > /dev/null; then
+        FTL_update=true
+        echo -e "  ${INFO} FTL:\\t\\t${COL_YELLOW}update available${COL_NC}"
+    else
+        case $? in
+            1)
+                echo -e "  ${INFO} FTL:\\t\\t${COL_LIGHT_GREEN}up to date${COL_NC}"
+                ;;
+            2)
+                echo -e "  ${INFO} FTL:\\t\\t${COL_LIGHT_RED}Branch is not available.${COL_NC}\\n\\t\\t\\tUse ${COL_LIGHT_GREEN}pihole checkout ftl [branchname]${COL_NC} to switch to a valid branch."
+                ;;
+            *)
+                echo -e "  ${INFO} FTL:\\t\\t${COL_LIGHT_RED}Something has gone wrong, contact support${COL_NC}"
+        esac
+        FTL_update=false
+    fi
+
+    if [[ "${core_update}" == false && "${web_update}" == false && "${FTL_update}" == false ]]; then
         echo ""
         echo -e "  ${TICK} Everything is up to date!"
         exit 0
-      fi
-    elif ! ${core_update} && ${web_update} ; then
-      echo ""
-      echo -e "  ${INFO} Pi-hole Web Admin files out of date"
-      getGitFiles "${ADMIN_INTERFACE_DIR}" "${ADMIN_INTERFACE_GIT_URL}"
-    elif ${core_update} && ! ${web_update} ; then
-      echo ""
-      echo -e "  ${INFO} Pi-hole core files out of date"
-      getGitFiles "${PI_HOLE_FILES_DIR}" "${PI_HOLE_GIT_URL}"
-      ${PI_HOLE_FILES_DIR}/automated\ install/basic-install.sh --reconfigure --unattended || \
-        echo -e "${basicError}" && exit 1
-    elif ${core_update} && ${web_update} ; then
-      echo ""
-      echo -e "  ${INFO} Updating Pi-hole core and web admin files"
-      getGitFiles "${PI_HOLE_FILES_DIR}" "${PI_HOLE_GIT_URL}"
-      ${PI_HOLE_FILES_DIR}/automated\ install/basic-install.sh --unattended || \
-        echo -e "${basicError}" && exit 1
-    else
-      echo -e "  ${COL_LIGHT_RED}Update script has malfunctioned, please contact Pi-hole Support${COL_NC}"
-      exit 1
     fi
-  else # Web Admin not installed, so only verify if core is up to date
-    if ! ${core_update}; then
-      if ! ${FTL_update} ; then
+
+    if [[ "${CHECK_ONLY}" == true ]]; then
         echo ""
-        echo -e "  ${INFO} Everything is up to date!"
         exit 0
-      fi
-    else
-      echo ""
-      echo -e "  ${INFO} Pi-hole Core files out of date"
-      getGitFiles "${PI_HOLE_FILES_DIR}" "${PI_HOLE_GIT_URL}"
-      ${PI_HOLE_FILES_DIR}/automated\ install/basic-install.sh --reconfigure --unattended || \
-        echo -e "${basicError}" && exit 1
     fi
-  fi
 
-  if [[ "${web_update}" == true ]]; then
-    web_version_current="$(/usr/local/bin/pihole version --admin --current)"
-    echo ""
-    echo -e "  ${INFO} Web Admin version is now at ${web_version_current/* v/v}
-  ${INFO} If you had made any changes in '/var/www/html/admin/', they have been stashed using 'git stash'"
-  fi
+    if [[ "${core_update}" == true ]]; then
+        echo ""
+        echo -e "  ${INFO} Pi-hole core files out of date, updating local repo."
+        getGitFiles "${PI_HOLE_FILES_DIR}" "${PI_HOLE_GIT_URL}"
+        echo -e "  ${INFO} If you had made any changes in '/etc/.pihole/', they have been stashed using 'git stash'"
+    fi
 
-  if [[ "${core_update}" == true ]]; then
-    pihole_version_current="$(/usr/local/bin/pihole version --pihole --current)"
+    if [[ "${web_update}" == true ]]; then
+        echo ""
+        echo -e "  ${INFO} Pi-hole Web Admin files out of date, updating local repo."
+        getGitFiles "${ADMIN_INTERFACE_DIR}" "${ADMIN_INTERFACE_GIT_URL}"
+        echo -e "  ${INFO} If you had made any changes in '/var/www/html/admin/', they have been stashed using 'git stash'"
+    fi
+
+    if [[ "${FTL_update}" == true ]]; then
+        echo ""
+        echo -e "  ${INFO} FTL out of date, it will be updated by the installer."
+    fi
+
+    if [[ "${FTL_update}" == true || "${core_update}" == true ]]; then
+        ${PI_HOLE_FILES_DIR}/automated\ install/basic-install.sh --reconfigure --unattended || \
+        echo -e "${basicError}" && exit 1
+    fi
     echo ""
-    echo -e "  ${INFO} Pi-hole version is now at ${pihole_version_current/* v/v}
-  ${INFO} If you had made any changes in '/etc/.pihole/', they have been stashed using 'git stash'"
-  fi
-
-  if [[ "${FTL_update}" == true ]]; then
-    FTL_version_current="$(/usr/bin/pihole-FTL tag)"
-    echo -e "\\n  ${INFO} FTL version is now at ${FTL_version_current/* v/v}"
-    start_service pihole-FTL
-    enable_service pihole-FTL
-  fi
-
-  echo ""
-  exit 0
+    exit 0
 }
 
+if [[ "$1" == "--check-only" ]]; then
+    CHECK_ONLY=true
+fi
+
 main
diff --git a/advanced/Scripts/updatecheck.sh b/advanced/Scripts/updatecheck.sh
index 16938a3adf88bbf0e1750f8d198ee0c556a58187..767c54613594caa8e174a84f08c53930eb0ea838 100755
--- a/advanced/Scripts/updatecheck.sh
+++ b/advanced/Scripts/updatecheck.sh
@@ -10,57 +10,57 @@
 
 # Credit: https://stackoverflow.com/a/46324904
 function json_extract() {
-  local key=$1
-  local json=$2
+    local key=$1
+    local json=$2
 
-  local string_regex='"([^"\]|\\.)*"'
-  local number_regex='-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?'
-  local value_regex="${string_regex}|${number_regex}|true|false|null"
-  local pair_regex="\"${key}\"[[:space:]]*:[[:space:]]*(${value_regex})"
+    local string_regex='"([^"\]|\\.)*"'
+    local number_regex='-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?'
+    local value_regex="${string_regex}|${number_regex}|true|false|null"
+    local pair_regex="\"${key}\"[[:space:]]*:[[:space:]]*(${value_regex})"
 
-  if [[ ${json} =~ ${pair_regex} ]]; then
-    echo $(sed 's/^"\|"$//g' <<< "${BASH_REMATCH[1]}")
-  else
-    return 1
-  fi
+    if [[ ${json} =~ ${pair_regex} ]]; then
+        echo $(sed 's/^"\|"$//g' <<< "${BASH_REMATCH[1]}")
+    else
+        return 1
+    fi
 }
 
 function get_local_branch() {
-  # Return active branch
-  cd "${1}" 2> /dev/null || return 1
-  git rev-parse --abbrev-ref HEAD || return 1
+    # Return active branch
+    cd "${1}" 2> /dev/null || return 1
+    git rev-parse --abbrev-ref HEAD || return 1
 }
 
 function get_local_version() {
-# Return active branch
-cd "${1}" 2> /dev/null || return 1
-git describe --long --dirty --tags || return 1
+    # Return active branch
+    cd "${1}" 2> /dev/null || return 1
+    git describe --long --dirty --tags || return 1
 }
 
 if [[ "$2" == "remote" ]]; then
 
-  if [[ "$3" == "reboot" ]]; then
-    sleep 30
-  fi
+    if [[ "$3" == "reboot" ]]; then
+        sleep 30
+    fi
 
-  GITHUB_CORE_VERSION="$(json_extract tag_name "$(curl -q 'https://api.github.com/repos/pi-hole/pi-hole/releases/latest' 2> /dev/null)")"
-  GITHUB_WEB_VERSION="$(json_extract tag_name "$(curl -q 'https://api.github.com/repos/pi-hole/AdminLTE/releases/latest' 2> /dev/null)")"
-  GITHUB_FTL_VERSION="$(json_extract tag_name "$(curl -q 'https://api.github.com/repos/pi-hole/FTL/releases/latest' 2> /dev/null)")"
+    GITHUB_CORE_VERSION="$(json_extract tag_name "$(curl -q 'https://api.github.com/repos/pi-hole/pi-hole/releases/latest' 2> /dev/null)")"
+    GITHUB_WEB_VERSION="$(json_extract tag_name "$(curl -q 'https://api.github.com/repos/pi-hole/AdminLTE/releases/latest' 2> /dev/null)")"
+    GITHUB_FTL_VERSION="$(json_extract tag_name "$(curl -q 'https://api.github.com/repos/pi-hole/FTL/releases/latest' 2> /dev/null)")"
 
-  echo -n "${GITHUB_CORE_VERSION} ${GITHUB_WEB_VERSION} ${GITHUB_FTL_VERSION}" > "/etc/pihole/GitHubVersions"
+    echo -n "${GITHUB_CORE_VERSION} ${GITHUB_WEB_VERSION} ${GITHUB_FTL_VERSION}" > "/etc/pihole/GitHubVersions"
 
 else
 
-  CORE_BRANCH="$(get_local_branch /etc/.pihole)"
-  WEB_BRANCH="$(get_local_branch /var/www/html/admin)"
-  FTL_BRANCH="$(pihole-FTL branch)"
+    CORE_BRANCH="$(get_local_branch /etc/.pihole)"
+    WEB_BRANCH="$(get_local_branch /var/www/html/admin)"
+    FTL_BRANCH="$(pihole-FTL branch)"
 
-  echo -n "${CORE_BRANCH} ${WEB_BRANCH} ${FTL_BRANCH}" > "/etc/pihole/localbranches"
+    echo -n "${CORE_BRANCH} ${WEB_BRANCH} ${FTL_BRANCH}" > "/etc/pihole/localbranches"
 
-  CORE_VERSION="$(get_local_version /etc/.pihole)"
-  WEB_VERSION="$(get_local_version /var/www/html/admin)"
-  FTL_VERSION="$(pihole-FTL version)"
+    CORE_VERSION="$(get_local_version /etc/.pihole)"
+    WEB_VERSION="$(get_local_version /var/www/html/admin)"
+    FTL_VERSION="$(pihole-FTL version)"
 
-  echo -n "${CORE_VERSION} ${WEB_VERSION} ${FTL_VERSION}" > "/etc/pihole/localversions"
+    echo -n "${CORE_VERSION} ${WEB_VERSION} ${FTL_VERSION}" > "/etc/pihole/localversions"
 
 fi
diff --git a/advanced/Scripts/version.sh b/advanced/Scripts/version.sh
index f5e0f51dbd906ec2a8a47f730089bdb4a61ccf22..7dfa1a90086a9166a176f5b478f0b75155f8acc7 100755
--- a/advanced/Scripts/version.sh
+++ b/advanced/Scripts/version.sh
@@ -14,135 +14,135 @@ COREGITDIR="/etc/.pihole/"
 WEBGITDIR="/var/www/html/admin/"
 
 getLocalVersion() {
-  # FTL requires a different method
-  if [[ "$1" == "FTL" ]]; then
-    pihole-FTL version
+    # FTL requires a different method
+    if [[ "$1" == "FTL" ]]; then
+        pihole-FTL version
+        return 0
+    fi
+
+    # Get the tagged version of the local repository
+    local directory="${1}"
+    local version
+
+    cd "${directory}" 2> /dev/null || { echo "${DEFAULT}"; return 1; }
+    version=$(git describe --tags --always || echo "$DEFAULT")
+    if [[ "${version}" =~ ^v ]]; then
+        echo "${version}"
+    elif [[ "${version}" == "${DEFAULT}" ]]; then
+        echo "ERROR"
+        return 1
+    else
+        echo "Untagged"
+    fi
     return 0
-  fi
-
-  # Get the tagged version of the local repository
-  local directory="${1}"
-  local version
-
-  cd "${directory}" 2> /dev/null || { echo "${DEFAULT}"; return 1; }
-  version=$(git describe --tags --always || echo "$DEFAULT")
-  if [[ "${version}" =~ ^v ]]; then
-    echo "${version}"
-  elif [[ "${version}" == "${DEFAULT}" ]]; then
-    echo "ERROR"
-    return 1
-  else
-    echo "Untagged"
-  fi
-  return 0
 }
 
 getLocalHash() {
-  # Local FTL hash does not exist on filesystem
-  if [[ "$1" == "FTL" ]]; then
-    echo "N/A"
+    # Local FTL hash does not exist on filesystem
+    if [[ "$1" == "FTL" ]]; then
+        echo "N/A"
+        return 0
+    fi
+
+    # Get the short hash of the local repository
+    local directory="${1}"
+    local hash
+
+    cd "${directory}" 2> /dev/null || { echo "${DEFAULT}"; return 1; }
+    hash=$(git rev-parse --short HEAD || echo "$DEFAULT")
+    if [[ "${hash}" == "${DEFAULT}" ]]; then
+        echo "ERROR"
+        return 1
+    else
+        echo "${hash}"
+    fi
     return 0
-  fi
-  
-  # Get the short hash of the local repository
-  local directory="${1}"
-  local hash
-
-  cd "${directory}" 2> /dev/null || { echo "${DEFAULT}"; return 1; }
-  hash=$(git rev-parse --short HEAD || echo "$DEFAULT")
-  if [[ "${hash}" == "${DEFAULT}" ]]; then
-    echo "ERROR"
-    return 1
-  else
-    echo "${hash}"
-  fi
-  return 0
 }
 
 getRemoteHash(){
-  # Remote FTL hash is not applicable
-  if [[ "$1" == "FTL" ]]; then
-    echo "N/A"
+    # Remote FTL hash is not applicable
+    if [[ "$1" == "FTL" ]]; then
+        echo "N/A"
+        return 0
+    fi
+
+    local daemon="${1}"
+    local branch="${2}"
+
+    hash=$(git ls-remote --heads "https://github.com/pi-hole/${daemon}" | \
+        awk -v bra="$branch" '$0~bra {print substr($0,0,8);exit}')
+    if [[ -n "$hash" ]]; then
+        echo "$hash"
+    else
+        echo "ERROR"
+        return 1
+    fi
     return 0
-  fi
-
-  local daemon="${1}"
-  local branch="${2}"
-
-  hash=$(git ls-remote --heads "https://github.com/pi-hole/${daemon}" | \
-         awk -v bra="$branch" '$0~bra {print substr($0,0,8);exit}')
-  if [[ -n "$hash" ]]; then
-    echo "$hash"
-  else
-    echo "ERROR"
-    return 1
-  fi
-  return 0
 }
 
 getRemoteVersion(){
-  # Get the version from the remote origin
-  local daemon="${1}"
-  local version
-
-  version=$(curl --silent --fail "https://api.github.com/repos/pi-hole/${daemon}/releases/latest" | \
-            awk -F: '$1 ~/tag_name/ { print $2 }' | \
-            tr -cd '[[:alnum:]]._-')
-  if [[ "${version}" =~ ^v ]]; then
-    echo "${version}"
-  else
-    echo "ERROR"
-    return 1
-  fi
-  return 0
+    # Get the version from the remote origin
+    local daemon="${1}"
+    local version
+
+    version=$(curl --silent --fail "https://api.github.com/repos/pi-hole/${daemon}/releases/latest" | \
+        awk -F: '$1 ~/tag_name/ { print $2 }' | \
+        tr -cd '[[:alnum:]]._-')
+    if [[ "${version}" =~ ^v ]]; then
+        echo "${version}"
+    else
+        echo "ERROR"
+        return 1
+    fi
+    return 0
 }
 
 versionOutput() {
-  [[ "$1" == "pi-hole" ]] && GITDIR=$COREGITDIR
-  [[ "$1" == "AdminLTE" ]] && GITDIR=$WEBGITDIR
-  [[ "$1" == "FTL" ]] && GITDIR="FTL"
-  
-  [[ "$2" == "-c" ]] || [[ "$2" == "--current" ]] || [[ -z "$2" ]] && current=$(getLocalVersion $GITDIR)
-  [[ "$2" == "-l" ]] || [[ "$2" == "--latest" ]] || [[ -z "$2" ]] && latest=$(getRemoteVersion "$1")
-  if [[ "$2" == "-h" ]] || [[ "$2" == "--hash" ]]; then
-    [[ "$3" == "-c" ]] || [[ "$3" == "--current" ]] || [[ -z "$3" ]] && curHash=$(getLocalHash "$GITDIR")
-    [[ "$3" == "-l" ]] || [[ "$3" == "--latest" ]] || [[ -z "$3" ]] && latHash=$(getRemoteHash "$1" "$(cd "$GITDIR" 2> /dev/null && git rev-parse --abbrev-ref HEAD)")
-  fi
-
-  if [[ -n "$current" ]] && [[ -n "$latest" ]]; then
-    output="${1^} version is $current (Latest: $latest)"
-  elif [[ -n "$current" ]] && [[ -z "$latest" ]]; then
-    output="Current ${1^} version is $current"
-  elif [[ -z "$current" ]] && [[ -n "$latest" ]]; then
-    output="Latest ${1^} version is $latest"
-  elif [[ "$curHash" == "N/A" ]] || [[ "$latHash" == "N/A" ]]; then
-    output="${1^} hash is not applicable"
-  elif [[ -n "$curHash" ]] && [[ -n "$latHash" ]]; then
-    output="${1^} hash is $curHash (Latest: $latHash)"
-  elif [[ -n "$curHash" ]] && [[ -z "$latHash" ]]; then
-    output="Current ${1^} hash is $curHash"
-  elif [[ -z "$curHash" ]] && [[ -n "$latHash" ]]; then
-    output="Latest ${1^} hash is $latHash"
-  else
-    errorOutput
-  fi
-
-  [[ -n "$output" ]] && echo "  $output"
+    [[ "$1" == "pi-hole" ]] && GITDIR=$COREGITDIR
+    [[ "$1" == "AdminLTE" ]] && GITDIR=$WEBGITDIR
+    [[ "$1" == "FTL" ]] && GITDIR="FTL"
+
+    [[ "$2" == "-c" ]] || [[ "$2" == "--current" ]] || [[ -z "$2" ]] && current=$(getLocalVersion $GITDIR)
+    [[ "$2" == "-l" ]] || [[ "$2" == "--latest" ]] || [[ -z "$2" ]] && latest=$(getRemoteVersion "$1")
+    if [[ "$2" == "-h" ]] || [[ "$2" == "--hash" ]]; then
+        [[ "$3" == "-c" ]] || [[ "$3" == "--current" ]] || [[ -z "$3" ]] && curHash=$(getLocalHash "$GITDIR")
+        [[ "$3" == "-l" ]] || [[ "$3" == "--latest" ]] || [[ -z "$3" ]] && latHash=$(getRemoteHash "$1" "$(cd "$GITDIR" 2> /dev/null && git rev-parse --abbrev-ref HEAD)")
+    fi
+
+    if [[ -n "$current" ]] && [[ -n "$latest" ]]; then
+        output="${1^} version is $current (Latest: $latest)"
+    elif [[ -n "$current" ]] && [[ -z "$latest" ]]; then
+        output="Current ${1^} version is $current"
+    elif [[ -z "$current" ]] && [[ -n "$latest" ]]; then
+        output="Latest ${1^} version is $latest"
+    elif [[ "$curHash" == "N/A" ]] || [[ "$latHash" == "N/A" ]]; then
+        output="${1^} hash is not applicable"
+    elif [[ -n "$curHash" ]] && [[ -n "$latHash" ]]; then
+        output="${1^} hash is $curHash (Latest: $latHash)"
+    elif [[ -n "$curHash" ]] && [[ -z "$latHash" ]]; then
+        output="Current ${1^} hash is $curHash"
+    elif [[ -z "$curHash" ]] && [[ -n "$latHash" ]]; then
+        output="Latest ${1^} hash is $latHash"
+    else
+        errorOutput
+    fi
+
+    [[ -n "$output" ]] && echo "  $output"
 }
 
 errorOutput() {
-  echo "  Invalid Option! Try 'pihole -v --help' for more information."
-  exit 1
+    echo "  Invalid Option! Try 'pihole -v --help' for more information."
+    exit 1
 }
-  
+
 defaultOutput() {
-  versionOutput "pi-hole" "$@"
-  versionOutput "AdminLTE" "$@"
-  versionOutput "FTL" "$@"
+    versionOutput "pi-hole" "$@"
+    versionOutput "AdminLTE" "$@"
+    versionOutput "FTL" "$@"
 }
 
 helpFunc() {
-  echo "Usage: pihole -v [repo | option] [option]
+    echo "Usage: pihole -v [repo | option] [option]
 Example: 'pihole -v -p -l'
 Show Pi-hole, Admin Console & FTL versions
 
@@ -150,7 +150,7 @@ Repositories:
   -p, --pihole         Only retrieve info regarding Pi-hole repository
   -a, --admin          Only retrieve info regarding AdminLTE repository
   -f, --ftl            Only retrieve info regarding FTL repository
-  
+
 Options:
   -c, --current        Return the current version
   -l, --latest         Return the latest version
@@ -160,9 +160,9 @@ Options:
 }
 
 case "${1}" in
-  "-p" | "--pihole"    ) shift; versionOutput "pi-hole" "$@";;
-  "-a" | "--admin"     ) shift; versionOutput "AdminLTE" "$@";;
-  "-f" | "--ftl"       ) shift; versionOutput "FTL" "$@";;
-  "-h" | "--help"      ) helpFunc;;
-  *                    ) defaultOutput "$@";;
+    "-p" | "--pihole"    ) shift; versionOutput "pi-hole" "$@";;
+    "-a" | "--admin"     ) shift; versionOutput "AdminLTE" "$@";;
+    "-f" | "--ftl"       ) shift; versionOutput "FTL" "$@";;
+    "-h" | "--help"      ) helpFunc;;
+    *                    ) defaultOutput "$@";;
 esac
diff --git a/advanced/Scripts/webpage.sh b/advanced/Scripts/webpage.sh
index 5eb35e97486f621f085316552f4b6f96589fb201..c3dede054c6a3e4c168681a44850b7391775af95 100755
--- a/advanced/Scripts/webpage.sh
+++ b/advanced/Scripts/webpage.sh
@@ -13,16 +13,17 @@
 readonly setupVars="/etc/pihole/setupVars.conf"
 readonly dnsmasqconfig="/etc/dnsmasq.d/01-pihole.conf"
 readonly dhcpconfig="/etc/dnsmasq.d/02-pihole-dhcp.conf"
+readonly FTLconf="/etc/pihole/pihole-FTL.conf"
 # 03 -> wildcards
 readonly dhcpstaticconfig="/etc/dnsmasq.d/04-pihole-static-dhcp.conf"
 
 coltable="/opt/pihole/COL_TABLE"
 if [[ -f ${coltable} ]]; then
-  source ${coltable}
+    source ${coltable}
 fi
 
 helpFunc() {
-  echo "Usage: pihole -a [options]
+    echo "Usage: pihole -a [options]
 Example: pihole -a -p password
 Set options for the Admin Console
 
@@ -35,256 +36,278 @@ Options:
   -e, email           Set an administrative contact address for the Block Page
   -h, --help          Show this help dialog
   -i, interface       Specify dnsmasq's interface listening behavior
-                        Add '-h' for more info on interface usage"
-	exit 0
+  -l, privacylevel    Set privacy level (0 = lowest, 3 = highest)"
+    exit 0
 }
 
 add_setting() {
-	echo "${1}=${2}" >> "${setupVars}"
+    echo "${1}=${2}" >> "${setupVars}"
 }
 
 delete_setting() {
-	sed -i "/${1}/d" "${setupVars}"
+    sed -i "/${1}/d" "${setupVars}"
 }
 
 change_setting() {
-	delete_setting "${1}"
-	add_setting "${1}" "${2}"
+    delete_setting "${1}"
+    add_setting "${1}" "${2}"
+}
+
+addFTLsetting() {
+    echo "${1}=${2}" >> "${FTLconf}"
+}
+
+deleteFTLsetting() {
+    sed -i "/${1}/d" "${FTLconf}"
+}
+
+changeFTLsetting() {
+    deleteFTLsetting "${1}"
+    addFTLsetting "${1}" "${2}"
 }
 
 add_dnsmasq_setting() {
-	if [[ "${2}" != "" ]]; then
-		echo "${1}=${2}" >> "${dnsmasqconfig}"
-	else
-		echo "${1}" >> "${dnsmasqconfig}"
-	fi
+    if [[ "${2}" != "" ]]; then
+        echo "${1}=${2}" >> "${dnsmasqconfig}"
+    else
+        echo "${1}" >> "${dnsmasqconfig}"
+    fi
 }
 
 delete_dnsmasq_setting() {
-	sed -i "/${1}/d" "${dnsmasqconfig}"
+    sed -i "/${1}/d" "${dnsmasqconfig}"
 }
 
 SetTemperatureUnit() {
-	change_setting "TEMPERATUREUNIT" "${unit}"
-  echo -e "  ${TICK} Set temperature unit to ${unit}"
+    change_setting "TEMPERATUREUNIT" "${unit}"
+    echo -e "  ${TICK} Set temperature unit to ${unit}"
 }
 
 HashPassword() {
-  # Compute password hash twice to avoid rainbow table vulnerability
-  return=$(echo -n ${1} | sha256sum | sed 's/\s.*$//')
-  return=$(echo -n ${return} | sha256sum | sed 's/\s.*$//')
-  echo ${return}
+    # Compute password hash twice to avoid rainbow table vulnerability
+    return=$(echo -n ${1} | sha256sum | sed 's/\s.*$//')
+    return=$(echo -n ${return} | sha256sum | sed 's/\s.*$//')
+    echo ${return}
 }
 
 SetWebPassword() {
-	if [ "${SUDO_USER}" == "www-data" ]; then
-		echo "Security measure: user www-data is not allowed to change webUI password!"
-		echo "Exiting"
-		exit 1
-	fi
-
-	if [ "${SUDO_USER}" == "lighttpd" ]; then
-		echo "Security measure: user lighttpd is not allowed to change webUI password!"
-		echo "Exiting"
-		exit 1
-	fi
-
-  if (( ${#args[2]} > 0 )) ; then
-    readonly PASSWORD="${args[2]}"
-    readonly CONFIRM="${PASSWORD}"
-  else
-    # Prevents a bug if the user presses Ctrl+C and it continues to hide the text typed.
-    # So we reset the terminal via stty if the user does press Ctrl+C
-    trap '{ echo -e "\nNo password will be set" ; stty sane ; exit 1; }' INT
-    read -s -p "Enter New Password (Blank for no password): " PASSWORD
-    echo ""
+    if [ "${SUDO_USER}" == "www-data" ]; then
+        echo "Security measure: user www-data is not allowed to change webUI password!"
+        echo "Exiting"
+        exit 1
+    fi
+
+    if [ "${SUDO_USER}" == "lighttpd" ]; then
+        echo "Security measure: user lighttpd is not allowed to change webUI password!"
+        echo "Exiting"
+        exit 1
+    fi
+
+    if (( ${#args[2]} > 0 )) ; then
+        readonly PASSWORD="${args[2]}"
+        readonly CONFIRM="${PASSWORD}"
+    else
+        # Prevents a bug if the user presses Ctrl+C and it continues to hide the text typed.
+        # So we reset the terminal via stty if the user does press Ctrl+C
+        trap '{ echo -e "\nNo password will be set" ; stty sane ; exit 1; }' INT
+        read -s -p "Enter New Password (Blank for no password): " PASSWORD
+        echo ""
 
     if [ "${PASSWORD}" == "" ]; then
-      change_setting "WEBPASSWORD" ""
-      echo -e "  ${TICK} Password Removed"
-      exit 0
+        change_setting "WEBPASSWORD" ""
+        echo -e "  ${TICK} Password Removed"
+        exit 0
     fi
 
     read -s -p "Confirm Password: " CONFIRM
     echo ""
-  fi
+    fi
 
-	if [ "${PASSWORD}" == "${CONFIRM}" ] ; then
-		hash=$(HashPassword "${PASSWORD}")
-		# Save hash to file
-		change_setting "WEBPASSWORD" "${hash}"
-		echo -e "  ${TICK} New password set"
-	else
-		echo -e "  ${CROSS} Passwords don't match. Your password has not been changed"
-		exit 1
-	fi
+    if [ "${PASSWORD}" == "${CONFIRM}" ] ; then
+        hash=$(HashPassword "${PASSWORD}")
+        # Save hash to file
+        change_setting "WEBPASSWORD" "${hash}"
+        echo -e "  ${TICK} New password set"
+    else
+        echo -e "  ${CROSS} Passwords don't match. Your password has not been changed"
+        exit 1
+    fi
 }
 
 ProcessDNSSettings() {
-	source "${setupVars}"
-
-	delete_dnsmasq_setting "server"
-
-	COUNTER=1
-	while [[ 1 ]]; do
-		var=PIHOLE_DNS_${COUNTER}
-		if [ -z "${!var}" ]; then
-			break;
-		fi
-		add_dnsmasq_setting "server" "${!var}"
-		let COUNTER=COUNTER+1
-	done
+    source "${setupVars}"
+
+    delete_dnsmasq_setting "server"
+
+    COUNTER=1
+    while [[ 1 ]]; do
+        var=PIHOLE_DNS_${COUNTER}
+        if [ -z "${!var}" ]; then
+            break;
+        fi
+        add_dnsmasq_setting "server" "${!var}"
+        let COUNTER=COUNTER+1
+    done
+
+    # The option LOCAL_DNS_PORT is deprecated
+    # We apply it once more, and then convert it into the current format
+    if [ ! -z "${LOCAL_DNS_PORT}" ]; then
+        add_dnsmasq_setting "server" "127.0.0.1#${LOCAL_DNS_PORT}"
+        add_setting "PIHOLE_DNS_${COUNTER}" "127.0.0.1#${LOCAL_DNS_PORT}"
+        delete_setting "LOCAL_DNS_PORT"
+    fi
 
-	delete_dnsmasq_setting "domain-needed"
+    delete_dnsmasq_setting "domain-needed"
 
-	if [[ "${DNS_FQDN_REQUIRED}" == true ]]; then
-		add_dnsmasq_setting "domain-needed"
-	fi
+    if [[ "${DNS_FQDN_REQUIRED}" == true ]]; then
+        add_dnsmasq_setting "domain-needed"
+    fi
 
-	delete_dnsmasq_setting "bogus-priv"
+    delete_dnsmasq_setting "bogus-priv"
 
-	if [[ "${DNS_BOGUS_PRIV}" == true ]]; then
-		add_dnsmasq_setting "bogus-priv"
-	fi
+    if [[ "${DNS_BOGUS_PRIV}" == true ]]; then
+        add_dnsmasq_setting "bogus-priv"
+    fi
 
-	delete_dnsmasq_setting "dnssec"
-	delete_dnsmasq_setting "trust-anchor="
+    delete_dnsmasq_setting "dnssec"
+    delete_dnsmasq_setting "trust-anchor="
 
-	if [[ "${DNSSEC}" == true ]]; then
-		echo "dnssec
+    if [[ "${DNSSEC}" == true ]]; then
+        echo "dnssec
 trust-anchor=.,19036,8,2,49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5
 trust-anchor=.,20326,8,2,E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D
 " >> "${dnsmasqconfig}"
-	fi
+    fi
 
-	delete_dnsmasq_setting "host-record"
+    delete_dnsmasq_setting "host-record"
 
-	if [ ! -z "${HOSTRECORD}" ]; then
-		add_dnsmasq_setting "host-record" "${HOSTRECORD}"
-	fi
+    if [ ! -z "${HOSTRECORD}" ]; then
+        add_dnsmasq_setting "host-record" "${HOSTRECORD}"
+    fi
 
-	# Setup interface listening behavior of dnsmasq
-	delete_dnsmasq_setting "interface"
-	delete_dnsmasq_setting "local-service"
+    # Setup interface listening behavior of dnsmasq
+    delete_dnsmasq_setting "interface"
+    delete_dnsmasq_setting "local-service"
 
-	if [[ "${DNSMASQ_LISTENING}" == "all" ]]; then
-		# Listen on all interfaces, permit all origins
-		add_dnsmasq_setting "except-interface" "nonexisting"
-	elif [[ "${DNSMASQ_LISTENING}" == "local" ]]; then
-		# Listen only on all interfaces, but only local subnets
-		add_dnsmasq_setting "local-service"
-	else
-		# Listen only on one interface
-		# Use eth0 as fallback interface if interface is missing in setupVars.conf
-		if [ -z "${PIHOLE_INTERFACE}" ]; then
-			PIHOLE_INTERFACE="eth0"
-		fi
+    if [[ "${DNSMASQ_LISTENING}" == "all" ]]; then
+        # Listen on all interfaces, permit all origins
+        add_dnsmasq_setting "except-interface" "nonexisting"
+    elif [[ "${DNSMASQ_LISTENING}" == "local" ]]; then
+        # Listen only on all interfaces, but only local subnets
+        add_dnsmasq_setting "local-service"
+    else
+        # Listen only on one interface
+        # Use eth0 as fallback interface if interface is missing in setupVars.conf
+        if [ -z "${PIHOLE_INTERFACE}" ]; then
+            PIHOLE_INTERFACE="eth0"
+        fi
 
-		add_dnsmasq_setting "interface" "${PIHOLE_INTERFACE}"
-	fi
-	if [[ "${CONDITIONAL_FORWARDING}" == true ]]; then
-		add_dnsmasq_setting "server=/${CONDITIONAL_FORWARDING_DOMAIN}/${CONDITIONAL_FORWARDING_IP}"
-		add_dnsmasq_setting "server=/${CONDITIONAL_FORWARDING_REVERSE}/${CONDITIONAL_FORWARDING_IP}"
-	fi
+        add_dnsmasq_setting "interface" "${PIHOLE_INTERFACE}"
+    fi
 
+    if [[ "${CONDITIONAL_FORWARDING}" == true ]]; then
+        add_dnsmasq_setting "server=/${CONDITIONAL_FORWARDING_DOMAIN}/${CONDITIONAL_FORWARDING_IP}"
+        add_dnsmasq_setting "server=/${CONDITIONAL_FORWARDING_REVERSE}/${CONDITIONAL_FORWARDING_IP}"
+    fi
 }
 
 SetDNSServers() {
-	# Save setting to file
-	delete_setting "PIHOLE_DNS"
-	IFS=',' read -r -a array <<< "${args[2]}"
-	for index in "${!array[@]}"
-	do
-		add_setting "PIHOLE_DNS_$((index+1))" "${array[index]}"
-	done
-
-	if [[ "${args[3]}" == "domain-needed" ]]; then
-		change_setting "DNS_FQDN_REQUIRED" "true"
-	else
-		change_setting "DNS_FQDN_REQUIRED" "false"
-	fi
-
-	if [[ "${args[4]}" == "bogus-priv" ]]; then
-		change_setting "DNS_BOGUS_PRIV" "true"
-	else
-		change_setting "DNS_BOGUS_PRIV" "false"
-	fi
-
-	if [[ "${args[5]}" == "dnssec" ]]; then
-		change_setting "DNSSEC" "true"
-	else
-		change_setting "DNSSEC" "false"
-	fi
-	if [[ "${args[6]}" == "conditional_forwarding" ]]; then
-		change_setting "CONDITIONAL_FORWARDING" "true"
-		change_setting "CONDITIONAL_FORWARDING_IP" "${args[7]}"
-		change_setting "CONDITIONAL_FORWARDING_DOMAIN" "${args[8]}"
-		change_setting "CONDITIONAL_FORWARDING_REVERSE" "${args[9]}"
-	else
-		change_setting "CONDITIONAL_FORWARDING" "false"
-		delete_setting "CONDITIONAL_FORWARDING_IP"
-		delete_setting "CONDITIONAL_FORWARDING_DOMAIN"
-		delete_setting "CONDITIONAL_FORWARDING_REVERSE"
-	fi
-
-	ProcessDNSSettings
-
-	# Restart dnsmasq to load new configuration
-	RestartDNS
+    # Save setting to file
+    delete_setting "PIHOLE_DNS"
+    IFS=',' read -r -a array <<< "${args[2]}"
+    for index in "${!array[@]}"
+    do
+        add_setting "PIHOLE_DNS_$((index+1))" "${array[index]}"
+    done
+
+    if [[ "${args[3]}" == "domain-needed" ]]; then
+        change_setting "DNS_FQDN_REQUIRED" "true"
+    else
+        change_setting "DNS_FQDN_REQUIRED" "false"
+    fi
+
+    if [[ "${args[4]}" == "bogus-priv" ]]; then
+        change_setting "DNS_BOGUS_PRIV" "true"
+    else
+        change_setting "DNS_BOGUS_PRIV" "false"
+    fi
+
+    if [[ "${args[5]}" == "dnssec" ]]; then
+        change_setting "DNSSEC" "true"
+    else
+        change_setting "DNSSEC" "false"
+    fi
+
+    if [[ "${args[6]}" == "conditional_forwarding" ]]; then
+        change_setting "CONDITIONAL_FORWARDING" "true"
+        change_setting "CONDITIONAL_FORWARDING_IP" "${args[7]}"
+        change_setting "CONDITIONAL_FORWARDING_DOMAIN" "${args[8]}"
+        change_setting "CONDITIONAL_FORWARDING_REVERSE" "${args[9]}"
+    else
+        change_setting "CONDITIONAL_FORWARDING" "false"
+        delete_setting "CONDITIONAL_FORWARDING_IP"
+        delete_setting "CONDITIONAL_FORWARDING_DOMAIN"
+        delete_setting "CONDITIONAL_FORWARDING_REVERSE"
+    fi
+
+    ProcessDNSSettings
+
+    # Restart dnsmasq to load new configuration
+    RestartDNS
 }
 
 SetExcludeDomains() {
-	change_setting "API_EXCLUDE_DOMAINS" "${args[2]}"
+    change_setting "API_EXCLUDE_DOMAINS" "${args[2]}"
 }
 
 SetExcludeClients() {
-	change_setting "API_EXCLUDE_CLIENTS" "${args[2]}"
+    change_setting "API_EXCLUDE_CLIENTS" "${args[2]}"
 }
 
 Poweroff(){
-        nohup bash -c "sleep 5; poweroff" &> /dev/null </dev/null &
+    nohup bash -c "sleep 5; poweroff" &> /dev/null </dev/null &
 }
 
 Reboot() {
-	nohup bash -c "sleep 5; reboot" &> /dev/null </dev/null &
+    nohup bash -c "sleep 5; reboot" &> /dev/null </dev/null &
 }
 
 RestartDNS() {
-  /usr/local/bin/pihole restartdns
+    /usr/local/bin/pihole restartdns
 }
 
 SetQueryLogOptions() {
-	change_setting "API_QUERY_LOG_SHOW" "${args[2]}"
+    change_setting "API_QUERY_LOG_SHOW" "${args[2]}"
 }
 
 ProcessDHCPSettings() {
-	source "${setupVars}"
+    source "${setupVars}"
 
-	if [[ "${DHCP_ACTIVE}" == "true" ]]; then
+    if [[ "${DHCP_ACTIVE}" == "true" ]]; then
     interface="${PIHOLE_INTERFACE}"
 
     # Use eth0 as fallback interface
     if [ -z ${interface} ]; then
-      interface="eth0"
+        interface="eth0"
     fi
 
     if [[ "${PIHOLE_DOMAIN}" == "" ]]; then
-      PIHOLE_DOMAIN="lan"
-      change_setting "PIHOLE_DOMAIN" "${PIHOLE_DOMAIN}"
+        PIHOLE_DOMAIN="lan"
+        change_setting "PIHOLE_DOMAIN" "${PIHOLE_DOMAIN}"
     fi
 
     if [[ "${DHCP_LEASETIME}" == "0" ]]; then
-      leasetime="infinite"
+        leasetime="infinite"
     elif [[ "${DHCP_LEASETIME}" == "" ]]; then
-      leasetime="24"
-      change_setting "DHCP_LEASETIME" "${leasetime}"
+        leasetime="24"
+        change_setting "DHCP_LEASETIME" "${leasetime}"
     elif [[ "${DHCP_LEASETIME}" == "24h" ]]; then
-      #Installation is affected by known bug, introduced in a previous version.
-      #This will automatically clean up setupVars.conf and remove the unnecessary "h"
-      leasetime="24"
-      change_setting "DHCP_LEASETIME" "${leasetime}"
+        #Installation is affected by known bug, introduced in a previous version.
+        #This will automatically clean up setupVars.conf and remove the unnecessary "h"
+        leasetime="24"
+        change_setting "DHCP_LEASETIME" "${leasetime}"
     else
-      leasetime="${DHCP_LEASETIME}h"
+        leasetime="${DHCP_LEASETIME}h"
     fi
 
     # Write settings to file
@@ -299,12 +322,12 @@ dhcp-leasefile=/etc/pihole/dhcp.leases
 #quiet-dhcp
 " > "${dhcpconfig}"
 
-  if [[ "${PIHOLE_DOMAIN}" != "none" ]]; then
-    echo "domain=${PIHOLE_DOMAIN}" >> "${dhcpconfig}"
-  fi
+    if [[ "${PIHOLE_DOMAIN}" != "none" ]]; then
+        echo "domain=${PIHOLE_DOMAIN}" >> "${dhcpconfig}"
+    fi
 
     if [[ "${DHCP_IPv6}" == "true" ]]; then
-  echo "#quiet-dhcp6
+        echo "#quiet-dhcp6
 #enable-ra
 dhcp-option=option6:dns-server,[::]
 dhcp-range=::100,::1ff,constructor:${interface},ra-names,slaac,${leasetime}
@@ -312,158 +335,160 @@ ra-param=*,0,0
 " >> "${dhcpconfig}"
     fi
 
-	else
-	  if [[ -f "${dhcpconfig}" ]]; then
-		  rm "${dhcpconfig}" &> /dev/null
-		fi
-	fi
+    else
+        if [[ -f "${dhcpconfig}" ]]; then
+            rm "${dhcpconfig}" &> /dev/null
+        fi
+    fi
 }
 
 EnableDHCP() {
-	change_setting "DHCP_ACTIVE" "true"
-	change_setting "DHCP_START" "${args[2]}"
-	change_setting "DHCP_END" "${args[3]}"
-	change_setting "DHCP_ROUTER" "${args[4]}"
-	change_setting "DHCP_LEASETIME" "${args[5]}"
-	change_setting "PIHOLE_DOMAIN" "${args[6]}"
-	change_setting "DHCP_IPv6" "${args[7]}"
+    change_setting "DHCP_ACTIVE" "true"
+    change_setting "DHCP_START" "${args[2]}"
+    change_setting "DHCP_END" "${args[3]}"
+    change_setting "DHCP_ROUTER" "${args[4]}"
+    change_setting "DHCP_LEASETIME" "${args[5]}"
+    change_setting "PIHOLE_DOMAIN" "${args[6]}"
+    change_setting "DHCP_IPv6" "${args[7]}"
 
-	# Remove possible old setting from file
-	delete_dnsmasq_setting "dhcp-"
-	delete_dnsmasq_setting "quiet-dhcp"
+    # Remove possible old setting from file
+    delete_dnsmasq_setting "dhcp-"
+    delete_dnsmasq_setting "quiet-dhcp"
 
-	ProcessDHCPSettings
+    ProcessDHCPSettings
 
-	RestartDNS
+    RestartDNS
 }
 
 DisableDHCP() {
-	change_setting "DHCP_ACTIVE" "false"
+    change_setting "DHCP_ACTIVE" "false"
 
-	# Remove possible old setting from file
-	delete_dnsmasq_setting "dhcp-"
-	delete_dnsmasq_setting "quiet-dhcp"
+    # Remove possible old setting from file
+    delete_dnsmasq_setting "dhcp-"
+    delete_dnsmasq_setting "quiet-dhcp"
 
-	ProcessDHCPSettings
+    ProcessDHCPSettings
 
-	RestartDNS
+    RestartDNS
 }
 
 SetWebUILayout() {
-	change_setting "WEBUIBOXEDLAYOUT" "${args[2]}"
+    change_setting "WEBUIBOXEDLAYOUT" "${args[2]}"
 }
 
 CustomizeAdLists() {
-  list="/etc/pihole/adlists.list"
-
-	if [[ "${args[2]}" == "enable" ]]; then
-		sed -i "\\@${args[3]}@s/^#http/http/g" "${list}"
-	elif [[ "${args[2]}" == "disable" ]]; then
-		sed -i "\\@${args[3]}@s/^http/#http/g" "${list}"
-	elif [[ "${args[2]}" == "add" ]]; then
-		echo "${args[3]}" >> ${list}
-	elif [[ "${args[2]}" == "del" ]]; then
-	  var=$(echo "${args[3]}" | sed 's/\//\\\//g')
-	  sed -i "/${var}/Id" "${list}"
-	else
-		echo "Not permitted"
-		return 1
-  fi
+    list="/etc/pihole/adlists.list"
+
+    if [[ "${args[2]}" == "enable" ]]; then
+        sed -i "\\@${args[3]}@s/^#http/http/g" "${list}"
+    elif [[ "${args[2]}" == "disable" ]]; then
+        sed -i "\\@${args[3]}@s/^http/#http/g" "${list}"
+    elif [[ "${args[2]}" == "add" ]]; then
+        if [[ $(grep -c "^${args[3]}$" "${list}") -eq 0 ]] ; then
+            echo "${args[3]}" >> ${list}
+        fi
+    elif [[ "${args[2]}" == "del" ]]; then
+        var=$(echo "${args[3]}" | sed 's/\//\\\//g')
+        sed -i "/${var}/Id" "${list}"
+    else
+        echo "Not permitted"
+        return 1
+    fi
 }
 
 SetPrivacyMode() {
-	if [[ "${args[2]}" == "true" ]]; then
-		change_setting "API_PRIVACY_MODE" "true"
-	else
-		change_setting "API_PRIVACY_MODE" "false"
-	fi
+    if [[ "${args[2]}" == "true" ]]; then
+        change_setting "API_PRIVACY_MODE" "true"
+    else
+        change_setting "API_PRIVACY_MODE" "false"
+    fi
 }
 
 ResolutionSettings() {
-	typ="${args[2]}"
-	state="${args[3]}"
+    typ="${args[2]}"
+    state="${args[3]}"
 
-	if [[ "${typ}" == "forward" ]]; then
-		change_setting "API_GET_UPSTREAM_DNS_HOSTNAME" "${state}"
-	elif [[ "${typ}" == "clients" ]]; then
-		change_setting "API_GET_CLIENT_HOSTNAME" "${state}"
-	fi
+    if [[ "${typ}" == "forward" ]]; then
+        change_setting "API_GET_UPSTREAM_DNS_HOSTNAME" "${state}"
+    elif [[ "${typ}" == "clients" ]]; then
+        change_setting "API_GET_CLIENT_HOSTNAME" "${state}"
+    fi
 }
 
 AddDHCPStaticAddress() {
-	mac="${args[2]}"
-	ip="${args[3]}"
-	host="${args[4]}"
-
-	if [[ "${ip}" == "noip" ]]; then
-		# Static host name
-		echo "dhcp-host=${mac},${host}" >> "${dhcpstaticconfig}"
-	elif [[ "${host}" == "nohost" ]]; then
-		# Static IP
-		echo "dhcp-host=${mac},${ip}" >> "${dhcpstaticconfig}"
-	else
-		# Full info given
-		echo "dhcp-host=${mac},${ip},${host}" >> "${dhcpstaticconfig}"
-	fi
+    mac="${args[2]}"
+    ip="${args[3]}"
+    host="${args[4]}"
+
+    if [[ "${ip}" == "noip" ]]; then
+        # Static host name
+        echo "dhcp-host=${mac},${host}" >> "${dhcpstaticconfig}"
+    elif [[ "${host}" == "nohost" ]]; then
+        # Static IP
+        echo "dhcp-host=${mac},${ip}" >> "${dhcpstaticconfig}"
+    else
+        # Full info given
+        echo "dhcp-host=${mac},${ip},${host}" >> "${dhcpstaticconfig}"
+    fi
 }
 
 RemoveDHCPStaticAddress() {
-	mac="${args[2]}"
-	sed -i "/dhcp-host=${mac}.*/d" "${dhcpstaticconfig}"
+    mac="${args[2]}"
+    sed -i "/dhcp-host=${mac}.*/d" "${dhcpstaticconfig}"
 }
 
 SetHostRecord() {
-  if [[ "${1}" == "-h" ]] || [[ "${1}" == "--help" ]]; then
-    echo "Usage: pihole -a hostrecord <domain> [IPv4-address],[IPv6-address]
+    if [[ "${1}" == "-h" ]] || [[ "${1}" == "--help" ]]; then
+        echo "Usage: pihole -a hostrecord <domain> [IPv4-address],[IPv6-address]
 Example: 'pihole -a hostrecord home.domain.com 192.168.1.1,2001:db8:a0b:12f0::1'
 Add a name to the DNS associated to an IPv4/IPv6 address
 
 Options:
   \"\"                  Empty: Remove host record
   -h, --help          Show this help dialog"
-    exit 0
-  fi
+        exit 0
+    fi
 
-	if [[ -n "${args[3]}" ]]; then
-		change_setting "HOSTRECORD" "${args[2]},${args[3]}"
-		echo -e "  ${TICK} Setting host record for ${args[2]} to ${args[3]}"
-	else
-		change_setting "HOSTRECORD" ""
-		echo -e "  ${TICK} Removing host record"
-	fi
+    if [[ -n "${args[3]}" ]]; then
+        change_setting "HOSTRECORD" "${args[2]},${args[3]}"
+        echo -e "  ${TICK} Setting host record for ${args[2]} to ${args[3]}"
+    else
+        change_setting "HOSTRECORD" ""
+        echo -e "  ${TICK} Removing host record"
+    fi
 
-	ProcessDNSSettings
+    ProcessDNSSettings
 
-	# Restart dnsmasq to load new configuration
-	RestartDNS
+    # Restart dnsmasq to load new configuration
+    RestartDNS
 }
 
 SetAdminEmail() {
-  if [[ "${1}" == "-h" ]] || [[ "${1}" == "--help" ]]; then
-    echo "Usage: pihole -a email <address>
+    if [[ "${1}" == "-h" ]] || [[ "${1}" == "--help" ]]; then
+        echo "Usage: pihole -a email <address>
 Example: 'pihole -a email admin@address.com'
 Set an administrative contact address for the Block Page
 
 Options:
   \"\"                  Empty: Remove admin contact
   -h, --help          Show this help dialog"
-    exit 0
-  fi
+        exit 0
+    fi
 
-	if [[ -n "${args[2]}" ]]; then
-		change_setting "ADMIN_EMAIL" "${args[2]}"
-		echo -e "  ${TICK} Setting admin contact to ${args[2]}"
-	else
-		change_setting "ADMIN_EMAIL" ""
-		echo -e "  ${TICK} Removing admin contact"
-	fi
+    if [[ -n "${args[2]}" ]]; then
+        change_setting "ADMIN_EMAIL" "${args[2]}"
+        echo -e "  ${TICK} Setting admin contact to ${args[2]}"
+    else
+        change_setting "ADMIN_EMAIL" ""
+        echo -e "  ${TICK} Removing admin contact"
+    fi
 }
 
 SetListeningMode() {
-	source "${setupVars}"
+    source "${setupVars}"
 
-  if [[ "$3" == "-h" ]] || [[ "$3" == "--help" ]]; then
-    echo "Usage: pihole -a -i [interface]
+    if [[ "$3" == "-h" ]] || [[ "$3" == "--help" ]]; then
+        echo "Usage: pihole -a -i [interface]
 Example: 'pihole -a -i local'
 Specify dnsmasq's network interface listening behavior
 
@@ -472,74 +497,82 @@ Interfaces:
                       devices that are at most one hop away (local devices)
   single              Listen only on ${PIHOLE_INTERFACE} interface
   all                 Listen on all interfaces, permit all origins"
-    exit 0
+        exit 0
   fi
 
-	if [[ "${args[2]}" == "all" ]]; then
-    echo -e "  ${INFO} Listening on all interfaces, permiting all origins. Please use a firewall!"
-		change_setting "DNSMASQ_LISTENING" "all"
-	elif [[ "${args[2]}" == "local" ]]; then
-    echo -e "  ${INFO} Listening on all interfaces, permiting origins from one hop away (LAN)"
-		change_setting "DNSMASQ_LISTENING" "local"
-	else
-		echo -e "  ${INFO} Listening only on interface ${PIHOLE_INTERFACE}"
-		change_setting "DNSMASQ_LISTENING" "single"
-	fi
-
-	# Don't restart DNS server yet because other settings
-	# will be applied afterwards if "-web" is set
-	if [[ "${args[3]}" != "-web" ]]; then
-		ProcessDNSSettings
-		# Restart dnsmasq to load new configuration
-		RestartDNS
-	fi
+    if [[ "${args[2]}" == "all" ]]; then
+        echo -e "  ${INFO} Listening on all interfaces, permiting all origins. Please use a firewall!"
+        change_setting "DNSMASQ_LISTENING" "all"
+    elif [[ "${args[2]}" == "local" ]]; then
+        echo -e "  ${INFO} Listening on all interfaces, permiting origins from one hop away (LAN)"
+        change_setting "DNSMASQ_LISTENING" "local"
+    else
+        echo -e "  ${INFO} Listening only on interface ${PIHOLE_INTERFACE}"
+        change_setting "DNSMASQ_LISTENING" "single"
+    fi
+
+    # Don't restart DNS server yet because other settings
+    # will be applied afterwards if "-web" is set
+    if [[ "${args[3]}" != "-web" ]]; then
+        ProcessDNSSettings
+        # Restart dnsmasq to load new configuration
+        RestartDNS
+    fi
 }
 
 Teleporter() {
-	local datetimestamp=$(date "+%Y-%m-%d_%H-%M-%S")
-	php /var/www/html/admin/scripts/pi-hole/php/teleporter.php > "pi-hole-teleporter_${datetimestamp}.zip"
+    local datetimestamp=$(date "+%Y-%m-%d_%H-%M-%S")
+    php /var/www/html/admin/scripts/pi-hole/php/teleporter.php > "pi-hole-teleporter_${datetimestamp}.zip"
 }
 
 audit()
 {
-	echo "${args[2]}" >> /etc/pihole/auditlog.list
+    echo "${args[2]}" >> /etc/pihole/auditlog.list
+}
+
+SetPrivacyLevel() {
+    # Set privacy level. Minimum is 0, maximum is 3
+    if [ "${args[2]}" -ge 0 ] && [ "${args[2]}" -le 3 ]; then
+        changeFTLsetting "PRIVACYLEVEL" "${args[2]}"
+    fi
 }
 
 main() {
-	args=("$@")
-
-	case "${args[1]}" in
-		"-p" | "password"   ) SetWebPassword;;
-		"-c" | "celsius"    ) unit="C"; SetTemperatureUnit;;
-		"-f" | "fahrenheit" ) unit="F"; SetTemperatureUnit;;
-		"-k" | "kelvin"     ) unit="K"; SetTemperatureUnit;;
-		"setdns"            ) SetDNSServers;;
-		"setexcludedomains" ) SetExcludeDomains;;
-		"setexcludeclients" ) SetExcludeClients;;
-		"poweroff"          ) Poweroff;;
-		"reboot"            ) Reboot;;
-		"restartdns"        ) RestartDNS;;
-		"setquerylog"       ) SetQueryLogOptions;;
-		"enabledhcp"        ) EnableDHCP;;
-		"disabledhcp"       ) DisableDHCP;;
-		"layout"            ) SetWebUILayout;;
-		"-h" | "--help"     ) helpFunc;;
-		"privacymode"       ) SetPrivacyMode;;
-		"resolve"           ) ResolutionSettings;;
-		"addstaticdhcp"     ) AddDHCPStaticAddress;;
-		"removestaticdhcp"  ) RemoveDHCPStaticAddress;;
-		"-r" | "hostrecord" ) SetHostRecord "$3";;
-		"-e" | "email"      ) SetAdminEmail "$3";;
-		"-i" | "interface"  ) SetListeningMode "$@";;
-		"-t" | "teleporter" ) Teleporter;;
-		"adlist"            ) CustomizeAdLists;;
-		"audit"             ) audit;;
-		*                   ) helpFunc;;
-	esac
-
-	shift
-
-	if [[ $# = 0 ]]; then
-		helpFunc
-	fi
+    args=("$@")
+
+    case "${args[1]}" in
+        "-p" | "password"     ) SetWebPassword;;
+        "-c" | "celsius"      ) unit="C"; SetTemperatureUnit;;
+        "-f" | "fahrenheit"   ) unit="F"; SetTemperatureUnit;;
+        "-k" | "kelvin"       ) unit="K"; SetTemperatureUnit;;
+        "setdns"              ) SetDNSServers;;
+        "setexcludedomains"   ) SetExcludeDomains;;
+        "setexcludeclients"   ) SetExcludeClients;;
+        "poweroff"            ) Poweroff;;
+        "reboot"              ) Reboot;;
+        "restartdns"          ) RestartDNS;;
+        "setquerylog"         ) SetQueryLogOptions;;
+        "enabledhcp"          ) EnableDHCP;;
+        "disabledhcp"         ) DisableDHCP;;
+        "layout"              ) SetWebUILayout;;
+        "-h" | "--help"       ) helpFunc;;
+        "privacymode"         ) SetPrivacyMode;;
+        "resolve"             ) ResolutionSettings;;
+        "addstaticdhcp"       ) AddDHCPStaticAddress;;
+        "removestaticdhcp"    ) RemoveDHCPStaticAddress;;
+        "-r" | "hostrecord"   ) SetHostRecord "$3";;
+        "-e" | "email"        ) SetAdminEmail "$3";;
+        "-i" | "interface"    ) SetListeningMode "$@";;
+        "-t" | "teleporter"   ) Teleporter;;
+        "adlist"              ) CustomizeAdLists;;
+        "audit"               ) audit;;
+        "-l" | "privacylevel" ) SetPrivacyLevel;;
+        *                     ) helpFunc;;
+    esac
+
+    shift
+
+    if [[ $# = 0 ]]; then
+        helpFunc
+    fi
 }
diff --git a/advanced/Scripts/wildcard_regex_converter.sh b/advanced/Scripts/wildcard_regex_converter.sh
new file mode 100644
index 0000000000000000000000000000000000000000..8c9578a304afea88d0d3dde60dd0047cc922134c
--- /dev/null
+++ b/advanced/Scripts/wildcard_regex_converter.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+# Pi-hole: A black hole for Internet advertisements
+# (c) 2017 Pi-hole, LLC (https://pi-hole.net)
+# Network-wide ad blocking via your own hardware.
+#
+# Provides an automated migration subroutine to convert Pi-hole v3.x wildcard domains to Pi-hole v4.x regex filters
+#
+# This file is copyright under the latest version of the EUPL.
+# Please see LICENSE file for your rights under this license.
+
+# regexFile set in gravity.sh
+
+wildcardFile="/etc/dnsmasq.d/03-pihole-wildcard.conf"
+
+convert_wildcard_to_regex() {
+    if [ ! -f "${wildcardFile}" ]; then
+        return
+    fi
+    local addrlines domains uniquedomains
+    # Obtain wildcard domains from old file
+    addrlines="$(grep -oE "/.*/" ${wildcardFile})"
+    # Strip "/" from domain names and convert "." to regex-compatible "\."
+    domains="$(sed 's/\///g;s/\./\\./g' <<< "${addrlines}")"
+    # Remove repeated domains (may have been inserted two times due to A and AAAA blocking)
+    uniquedomains="$(uniq <<< "${domains}")"
+    # Automatically generate regex filters and remove old wildcards file
+    awk '{print "(^|\\.)"$0"$"}' <<< "${uniquedomains}" >> "${regexFile:?}" && rm "${wildcardFile}"
+}
diff --git a/advanced/logrotate b/advanced/Templates/logrotate
similarity index 100%
rename from advanced/logrotate
rename to advanced/Templates/logrotate
diff --git a/advanced/Templates/pihole-FTL.conf b/advanced/Templates/pihole-FTL.conf
new file mode 100644
index 0000000000000000000000000000000000000000..03f429324cc0c2ccedde51012f337cf217e4b88a
--- /dev/null
+++ b/advanced/Templates/pihole-FTL.conf
@@ -0,0 +1,84 @@
+### This file contains parameters for FTL behavior.
+### At install, all parameters are commented out. The user can select desired options.
+### Options shown are the default configuration. No modification is needed for most
+### installations.
+### Visit https://docs.pi-hole.net/ftldns/configfile/ for more detailed parameter explanations
+
+## Socket Listening
+## Listen only for local socket connections or permit all connections
+## Options: localonly, all
+#SOCKET_LISTENING=localonly
+
+## Query Display
+## Display all queries? Set to no to hide query display
+## Options: yes, no
+#QUERY_DISPLAY=yes
+
+## AAA Query Analysis
+## Allow FTL to analyze AAAA queries from pihole.log?
+## Options: yes, no
+#AAAA_QUERY_ANALYSIS=yes
+
+## Resolve IPv6
+## Should FTL try to resolve IPv6 addresses to host names?
+## Options: yes, no
+#RESOLVE_IPV6=yes
+
+## Resolve IPv4
+## Should FTL try to resolve IPv4 addresses to host names?
+## Options: yes, no
+#RESOLVE_IPV4=yes
+
+## Max Database Days
+## How long should queries be stored in the database (days)?
+## Setting this to 0 disables the database
+## See: https://docs.pi-hole.net/ftldns/database/
+## Options: number of days
+#MAXDBDAYS=365
+
+## Database Interval
+## How often do we store queries in FTL's database (minutes)?
+## See: https://docs.pi-hole.net/ftldns/database/
+## Options: number of minutes
+#DBINTERVAL=1.0
+
+## Database File
+## Specify path and filename of FTL's SQLite3 long-term database.
+## Setting this to DBFILE= disables the database altogether
+## See: https://docs.pi-hole.net/ftldns/database/
+## Option: path to db file
+#DBFILE=/etc/pihole/pihole-FTL.db
+
+## Max Log Age
+## Up to how many hours of queries should be imported from the database and logs (hours)?
+## Maximum is 744 (31 days)
+## Options: number of days
+#MAXLOGAGE=24.0
+
+## FTL Port
+## On which port should FTL be listening?
+## Options: tcp port
+#FTLPORT=4711
+
+## Privacy Level
+## Which privacy level is used?
+## See: https://docs.pi-hole.net/ftldns/privacylevels/
+## Options: 0, 1, 2, 3
+#PRIVACYLEVEL=0
+
+## Ignore Localhost
+## Should FTL ignore queries coming from the local machine?
+## Options: yes, no
+#IGNORE_LOCALHOST=no
+
+## Blocking Mode
+## How should FTL reply to blocked queries?
+## See: https://docs.pi-hole.net/ftldns/blockingmode/
+## Options: NULL, IP-AAAA-NODATA, IP, NXDOMAIN
+#BLOCKINGMODE=NULL
+
+## Regex Debug Mode
+## Controls if FTLDNS should print extended details about regex matching into pihole-FTL.log.
+## See: https://docs.pi-hole.net/ftldns/regex/overview/
+## Options: true, false
+#REGEX_DEBUGMODE=false
diff --git a/advanced/pihole-FTL.service b/advanced/Templates/pihole-FTL.service
similarity index 75%
rename from advanced/pihole-FTL.service
rename to advanced/Templates/pihole-FTL.service
index 959b7794045822e8c1e55798fdec0bcddc9283b6..ecc7a52ac41faa10c050ec70b5117c2ac360db90 100644
--- a/advanced/pihole-FTL.service
+++ b/advanced/Templates/pihole-FTL.service
@@ -20,6 +20,7 @@ is_running() {
     ps "$(get_pid)" > /dev/null 2>&1
 }
 
+
 # Start the service
 start() {
   if is_running; then
@@ -29,9 +30,12 @@ start() {
     mkdir -p /var/run/pihole
     mkdir -p /var/log/pihole
     chown pihole:pihole /var/run/pihole /var/log/pihole
-    rm /var/run/pihole/FTL.sock
-    chown pihole:pihole /var/log/pihole-FTL.log /run/pihole-FTL.pid /run/pihole-FTL.port /etc/pihole
+    rm /var/run/pihole/FTL.sock 2> /dev/null
+    chown pihole:pihole /var/log/pihole-FTL.log /run/pihole-FTL.pid /run/pihole-FTL.port
+    chown pihole:pihole /etc/pihole /etc/pihole/dhcp.leases /var/log/pihole.log
     chmod 0644 /var/log/pihole-FTL.log /run/pihole-FTL.pid /run/pihole-FTL.port /var/log/pihole.log
+    setcap CAP_NET_BIND_SERVICE,CAP_NET_RAW,CAP_NET_ADMIN+eip "$(which pihole-FTL)"
+    echo "nameserver 127.0.0.1" | /sbin/resolvconf -a lo.piholeFTL
     su -s /bin/sh -c "/usr/bin/pihole-FTL" "$FTLUSER"
     echo
   fi
@@ -40,6 +44,7 @@ start() {
 # Stop the service
 stop() {
   if is_running; then
+    /sbin/resolvconf -d lo.piholeFTL
     kill "$(get_pid)"
     for i in {1..5}; do
       if ! is_running; then
@@ -64,13 +69,25 @@ stop() {
   echo
 }
 
+# Indicate the service status
+status() {
+  if is_running; then
+    echo "[ ok ] pihole-FTL is running"
+    exit 0
+  else
+    echo "[    ] pihole-FTL is not running"
+    exit 1
+  fi
+}  
+
+
 ### main logic ###
 case "$1" in
   stop)
         stop
         ;;
   status)
-        status pihole-FTL
+        status
         ;;
   start|restart|reload|condrestart)
         stop
diff --git a/advanced/pihole.cron b/advanced/Templates/pihole.cron
similarity index 100%
rename from advanced/pihole.cron
rename to advanced/Templates/pihole.cron
diff --git a/advanced/pihole.sudo b/advanced/Templates/pihole.sudo
similarity index 100%
rename from advanced/pihole.sudo
rename to advanced/Templates/pihole.sudo
diff --git a/advanced/bash-completion/pihole b/advanced/bash-completion/pihole
index fc8f2162aba40943c3c472a48e4f08dcb5586a7d..c25c570bc916160cab0c0b7144ce612462844ccd 100644
--- a/advanced/bash-completion/pihole
+++ b/advanced/bash-completion/pihole
@@ -1,11 +1,79 @@
 _pihole() {
-	local cur prev opts
+	local cur prev opts opts_admin opts_checkout opts_chronometer opts_debug opts_interface  opts_logging opts_privacy opts_query opts_update opts_version
 	COMPREPLY=()
 	cur="${COMP_WORDS[COMP_CWORD]}"
 	prev="${COMP_WORDS[COMP_CWORD-1]}"
-	opts="admin blacklist chronometer debug disable enable flush help logging query reconfigure restartdns setupLCD status tail uninstall updateGravity updatePihole version whitelist checkout"
+	prev2="${COMP_WORDS[COMP_CWORD-2]}"
 
-	COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+	case "${prev}" in
+		"pihole")
+			opts="admin blacklist checkout chronometer debug disable enable flush help logging query reconfigure regex restartdns status tail uninstall updateGravity updatePihole version wildcard whitelist"
+			COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+		;;
+		"whitelist"|"blacklist"|"wildcard"|"regex")
+			opts_lists="\--delmode \--noreload \--quiet \--list \--nuke"
+			COMPREPLY=( $(compgen -W "${opts_lists}" -- ${cur}) )
+		;;
+		"admin")
+			opts_admin="celsius email fahrenheit hostrecord interface kelvin password privacylevel"
+			COMPREPLY=( $(compgen -W "${opts_admin}" -- ${cur}) )
+		;;
+		"checkout")
+			opts_checkout="core ftl web master dev"
+			COMPREPLY=( $(compgen -W "${opts_checkout}" -- ${cur}) )
+		;;
+		"chronometer")
+			opts_chronometer="\--exit \--json \--refresh"
+			COMPREPLY=( $(compgen -W "${opts_chronometer}" -- ${cur}) )
+		;;
+		"debug")
+			opts_debug="-a"
+			COMPREPLY=( $(compgen -W "${opts_debug}" -- ${cur}) )
+		;;
+		"logging")
+			opts_logging="on off 'off noflush'"
+			COMPREPLY=( $(compgen -W "${opts_logging}" -- ${cur}) )
+		;;
+		"query")
+			opts_query="-adlist -all -exact"
+			COMPREPLY=( $(compgen -W "${opts_query}" -- ${cur}) )
+		;;
+		"updatePihole"|"-up")
+			opts_update="--check-only"
+			COMPREPLY=( $(compgen -W "${opts_update}" -- ${cur}) )
+		;;
+		"version")
+			opts_version="\--admin \--current \--ftl \--hash \--latest \--pihole"
+			COMPREPLY=( $(compgen -W "${opts_version}" -- ${cur}) )
+		;;
+		"interface")
+			if ( [[ "$prev2" == "admin" ]] || [[ "$prev2" == "-a" ]] ); then
+				opts_interface="$(cat /proc/net/dev | cut -d: -s -f1)"
+				COMPREPLY=( $(compgen -W "${opts_interface}" -- ${cur}) )
+			else
+				return 1
+			fi
+		;;
+		"privacylevel")
+			if ( [[ "$prev2" == "admin" ]] || [[ "$prev2" == "-a" ]] ); then
+				opts_privacy="0 1 2 3"
+				COMPREPLY=( $(compgen -W "${opts_privacy}" -- ${cur}) )
+			else 
+				return 1
+			fi	
+		;;
+		"core"|"admin"|"ftl")
+			if [[ "$prev2" == "checkout" ]]; then
+				opts_checkout="master dev"
+				COMPREPLY=( $(compgen -W "${opts_checkout}" -- ${cur}) )
+			else
+				return 1
+			fi
+		;;
+		*)
+		return 1
+		;;
+	esac
 	return 0
 }
 complete -F _pihole pihole
diff --git a/advanced/index.php b/advanced/index.php
index 14da9ecfff1c13d84a6732c813c18232f526e500..cad59ec731d4b1051efbe12d78ce27e0b6a9b9d9 100644
--- a/advanced/index.php
+++ b/advanced/index.php
@@ -64,7 +64,7 @@ if ($serverName === "pi.hole") {
     <html><head>
         $viewPort
         <link rel='stylesheet' href='/pihole/blockingpage.css' type='text/css'/>
-    </head><body id='splashpage'><img src='/admin/img/logo.svg'/><br/>Pi-<b>hole</b>: Your black hole for Internet advertisements</body></html>
+    </head><body id='splashpage'><img src='/admin/img/logo.svg'/><br/>Pi-<b>hole</b>: Your black hole for Internet advertisements<br><a href='/admin'>Did you mean to go to the admin panel?</a></body></html>
     ";
 
     // Set splash/landing page based off presence of $landPage
@@ -102,8 +102,10 @@ if ($serverName === "pi.hole") {
 $bpAskAdmin = !empty($svEmail) ? '<a href="mailto:'.$svEmail.'?subject=Site Blocked: '.$serverName.'"></a>' : "<span/>";
 
 // Determine if at least one block list has been generated
-if (empty(glob("/etc/pihole/list.0.*.domains")))
+$blocklistglob = glob("/etc/pihole/list.0.*.domains");
+if ($blocklistglob === array()) {
     die("[ERROR] There are no domain lists generated lists within <code>/etc/pihole/</code>! Please update gravity by running <code>pihole -g</code>, or repair Pi-hole using <code>pihole -r</code>.");
+}
 
 // Set location of adlists file
 if (is_file("/etc/pihole/adlists.list")) {
@@ -327,6 +329,7 @@ setHeader();
           setTimeout(function(){window.location.reload(1);}, 10000);
           $("#bpOutput").removeClass("add");
           $("#bpOutput").addClass("success");
+          $("#bpOutput").html("");
         } else {
           $("#bpOutput").removeClass("add");
           $("#bpOutput").addClass("error");
@@ -336,6 +339,7 @@ setHeader();
       error: function(jqXHR, exception) {
         $("#bpOutput").removeClass("add");
         $("#bpOutput").addClass("exception");
+        $("#bpOutput").html("");
       }
     });
   }
diff --git a/automated install/basic-install.sh b/automated install/basic-install.sh
index 3152d9c14856c41f9b70422a566c96beaa4868a8..10e43b6c88cbcc747221cdc6786f345289fff06e 100755
--- a/automated install/basic-install.sh	
+++ b/automated install/basic-install.sh	
@@ -47,15 +47,22 @@ PI_HOLE_LOCAL_REPO="/etc/.pihole"
 PI_HOLE_FILES=(chronometer list piholeDebug piholeLogFlush setupLCD update version gravity uninstall webpage)
 # This folder is where the Pi-hole scripts will be installed
 PI_HOLE_INSTALL_DIR="/opt/pihole"
+PI_HOLE_CONFIG_DIR="/etc/pihole"
 useUpdateVars=false
 
+adlistFile="/etc/pihole/adlists.list"
+regexFile="/etc/pihole/regex.list"
 # Pi-hole needs an IP address; to begin, these variables are empty since we don't know what the IP is until
 # this script can run
 IPV4_ADDRESS=""
 IPV6_ADDRESS=""
 # By default, query logging is enabled and the dashboard is set to be installed
 QUERY_LOGGING=true
-INSTALL_WEB=true
+INSTALL_WEB_INTERFACE=true
+
+if [ -z "${USER}" ]; then
+  USER="$(id -un)"
+fi
 
 
 # Find the rows and columns will default to 80x24 if it can not be detected
@@ -76,23 +83,33 @@ c=$(( c < 70 ? 70 : c ))
 skipSpaceCheck=false
 reconfigure=false
 runUnattended=false
+INSTALL_WEB_SERVER=true
+# Check arguments for the undocumented flags
+for var in "$@"; do
+    case "$var" in
+        "--reconfigure" ) reconfigure=true;;
+        "--i_do_not_follow_recommendations" ) skipSpaceCheck=true;;
+        "--unattended" ) runUnattended=true;;
+        "--disable-install-webserver" ) INSTALL_WEB_SERVER=false;;
+    esac
+done
 
 # If the color table file exists,
 if [[ -f "${coltable}" ]]; then
-  # source it
-  source ${coltable}
+    # source it
+    source ${coltable}
 # Otherwise,
 else
-  # Set these values so the installer can still run in color
-  COL_NC='\e[0m' # No Color
-  COL_LIGHT_GREEN='\e[1;32m'
-  COL_LIGHT_RED='\e[1;31m'
-  TICK="[${COL_LIGHT_GREEN}✓${COL_NC}]"
-  CROSS="[${COL_LIGHT_RED}✗${COL_NC}]"
-  INFO="[i]"
-  # shellcheck disable=SC2034
-  DONE="${COL_LIGHT_GREEN} done!${COL_NC}"
-  OVER="\\r\\033[K"
+    # Set these values so the installer can still run in color
+    COL_NC='\e[0m' # No Color
+    COL_LIGHT_GREEN='\e[1;32m'
+    COL_LIGHT_RED='\e[1;31m'
+    TICK="[${COL_LIGHT_GREEN}✓${COL_NC}]"
+    CROSS="[${COL_LIGHT_RED}✗${COL_NC}]"
+    INFO="[i]"
+    # shellcheck disable=SC2034
+    DONE="${COL_LIGHT_GREEN} done!${COL_NC}"
+    OVER="\\r\\033[K"
 fi
 
 # A simple function that just echoes out our logo in ASCII format
@@ -126,896 +143,1031 @@ show_ascii_berry() {
 distro_check() {
 # If apt-get is installed, then we know it's part of the Debian family
 if command -v apt-get &> /dev/null; then
-  # Set some global variables here
-  # We don't set them earlier since the family might be Red Hat, so these values would be different
-  PKG_MANAGER="apt-get"
-  # A variable to store the command used to update the package cache
-  UPDATE_PKG_CACHE="${PKG_MANAGER} update"
-  # An array for something...
-  PKG_INSTALL=(${PKG_MANAGER} --yes --no-install-recommends install)
-  # grep -c will return 1 retVal on 0 matches, block this throwing the set -e with an OR TRUE
-  PKG_COUNT="${PKG_MANAGER} -s -o Debug::NoLocking=true upgrade | grep -c ^Inst || true"
-  # Some distros vary slightly so these fixes for dependencies may apply
-  # Debian 7 doesn't have iproute2 so if the dry run install is successful,
-  if ${PKG_MANAGER} install --dry-run iproute2 > /dev/null 2>&1; then
-    # we can install it
-    iproute_pkg="iproute2"
-  # Otherwise,
-  else
-    # use iproute
-    iproute_pkg="iproute"
-  fi
-  # We prefer the php metapackage if it's there
-  if ${PKG_MANAGER} install --dry-run php > /dev/null 2>&1; then
-    phpVer="php"
-  # If not,
-  else
-    # fall back on the php5 packages
-    phpVer="php5"
-  fi
-  # We also need the correct version for `php-sqlite` (which differs across distros)
-  if ${PKG_MANAGER} install --dry-run ${phpVer}-sqlite3 > /dev/null 2>&1; then
-    phpSqlite="sqlite3"
-  else
-    phpSqlite="sqlite"
-  fi
-  # Since our install script is so large, we need several other programs to successfuly get a machine provisioned
-  # These programs are stored in an array so they can be looped through later
-  INSTALLER_DEPS=(apt-utils dialog debconf dhcpcd5 git ${iproute_pkg} whiptail)
-  # Pi-hole itself has several dependencies that also need to be installed
-  PIHOLE_DEPS=(bc cron curl dnsmasq dnsutils iputils-ping lsof netcat sudo unzip wget idn2 sqlite3)
-  # The Web dashboard has some that also need to be installed
-  # It's useful to separate the two since our repos are also setup as "Core" code and "Web" code
-  PIHOLE_WEB_DEPS=(lighttpd ${phpVer}-common ${phpVer}-cgi ${phpVer}-${phpSqlite})
-  # The Web server user,
-  LIGHTTPD_USER="www-data"
-  # group,
-  LIGHTTPD_GROUP="www-data"
-  # and config file
-  LIGHTTPD_CFG="lighttpd.conf.debian"
-  # The DNS server user
-  DNSMASQ_USER="dnsmasq"
-
-  # A function to check...
-  test_dpkg_lock() {
-      # An iterator used for counting loop iterations
-      i=0
-      # fuser is a program to show which processes use the named files, sockets, or filesystems
-      # So while the command is true
-      while fuser /var/lib/dpkg/lock >/dev/null 2>&1 ; do
-        # Wait half a second
-        sleep 0.5
-        # and increase the iterator
-        ((i=i+1))
-      done
-      # Always return success, since we only return if there is no
-      # lock (anymore)
-      return 0
+    # Set some global variables here
+    # We don't set them earlier since the family might be Red Hat, so these values would be different
+    PKG_MANAGER="apt-get"
+    # A variable to store the command used to update the package cache
+    UPDATE_PKG_CACHE="${PKG_MANAGER} update"
+    # An array for something...
+    PKG_INSTALL=(${PKG_MANAGER} --yes --no-install-recommends install)
+    # grep -c will return 1 retVal on 0 matches, block this throwing the set -e with an OR TRUE
+    PKG_COUNT="${PKG_MANAGER} -s -o Debug::NoLocking=true upgrade | grep -c ^Inst || true"
+    # Some distros vary slightly so these fixes for dependencies may apply
+    # Debian 7 doesn't have iproute2 so if the dry run install is successful,
+    if ${PKG_MANAGER} install --dry-run iproute2 > /dev/null 2>&1; then
+        # we can install it
+        iproute_pkg="iproute2"
+    # Otherwise,
+    else
+        # use iproute
+        iproute_pkg="iproute"
+    fi
+    # Check for and determine version number (major and minor) of current php install
+    if command -v php &> /dev/null; then
+        phpInsVersion="$(php -v | head -n1 | grep -Po '(?<=PHP )[^ ]+')"
+        echo -e "  ${INFO} Existing PHP installation detected : PHP version $phpInsVersion"
+        phpInsMajor="$(echo "$phpInsVersion" | cut -d\. -f1)"
+        phpInsMinor="$(echo "$phpInsVersion" | cut -d\. -f2)"
+        # Is installed php version 7.0 or greater
+        if [ "$(echo "$phpInsMajor.$phpInsMinor < 7.0" | bc )" == 0 ]; then
+            phpInsNewer=true
+        fi
+    fi
+    # Check if installed php is v 7.0, or newer to determine packages to install
+    if [[ "$phpInsNewer" != true ]]; then
+        # Prefer the php metapackage if it's there
+        if ${PKG_MANAGER} install --dry-run php > /dev/null 2>&1; then
+            phpVer="php"
+        # fall back on the php5 packages
+        else
+            phpVer="php5"
+        fi
+    else
+        # Newer php is installed, its common, cgi & sqlite counterparts are deps
+        phpVer="php$phpInsMajor.$phpInsMinor"
+    fi
+    # We also need the correct version for `php-sqlite` (which differs across distros)
+    if ${PKG_MANAGER} install --dry-run ${phpVer}-sqlite3 > /dev/null 2>&1; then
+        phpSqlite="sqlite3"
+    else
+        phpSqlite="sqlite"
+    fi
+    # Since our install script is so large, we need several other programs to successfully get a machine provisioned
+    # These programs are stored in an array so they can be looped through later
+    INSTALLER_DEPS=(apt-utils dialog debconf dhcpcd5 git ${iproute_pkg} whiptail)
+    # Pi-hole itself has several dependencies that also need to be installed
+    PIHOLE_DEPS=(bc cron curl dnsutils iputils-ping lsof netcat psmisc sudo unzip wget idn2 sqlite3 libcap2-bin dns-root-data resolvconf)
+    # The Web dashboard has some that also need to be installed
+    # It's useful to separate the two since our repos are also setup as "Core" code and "Web" code
+    PIHOLE_WEB_DEPS=(lighttpd ${phpVer}-common ${phpVer}-cgi ${phpVer}-${phpSqlite})
+    # The Web server user,
+    LIGHTTPD_USER="www-data"
+    # group,
+    LIGHTTPD_GROUP="www-data"
+    # and config file
+    LIGHTTPD_CFG="lighttpd.conf.debian"
+
+    # A function to check...
+    test_dpkg_lock() {
+        # An iterator used for counting loop iterations
+        i=0
+        # fuser is a program to show which processes use the named files, sockets, or filesystems
+        # So while the command is true
+        while fuser /var/lib/dpkg/lock >/dev/null 2>&1 ; do
+            # Wait half a second
+            sleep 0.5
+            # and increase the iterator
+            ((i=i+1))
+        done
+        # Always return success, since we only return if there is no
+        # lock (anymore)
+        return 0
     }
 
 # If apt-get is not found, check for rpm to see if it's a Red Hat family OS
 elif command -v rpm &> /dev/null; then
-  # Then check if dnf or yum is the package manager
-  if command -v dnf &> /dev/null; then
-    PKG_MANAGER="dnf"
-  else
-    PKG_MANAGER="yum"
-  fi
-
-  # Fedora and family update cache on every PKG_INSTALL call, no need for a separate update.
-  UPDATE_PKG_CACHE=":"
-  PKG_INSTALL=(${PKG_MANAGER} install -y)
-  PKG_COUNT="${PKG_MANAGER} check-update | egrep '(.i686|.x86|.noarch|.arm|.src)' | wc -l"
-  INSTALLER_DEPS=(dialog git iproute net-tools newt procps-ng)
-  PIHOLE_DEPS=(bc bind-utils cronie curl dnsmasq findutils nmap-ncat sudo unzip wget libidn2 psmisc)
-  PIHOLE_WEB_DEPS=(lighttpd lighttpd-fastcgi php php-common php-cli php-pdo)
-  # EPEL (https://fedoraproject.org/wiki/EPEL) is required for lighttpd on CentOS
-  if grep -qi 'centos' /etc/redhat-release; then
-    INSTALLER_DEPS=("${INSTALLER_DEPS[@]}" "epel-release");
-  fi
+    # Then check if dnf or yum is the package manager
+    if command -v dnf &> /dev/null; then
+        PKG_MANAGER="dnf"
+    else
+        PKG_MANAGER="yum"
+    fi
+
+    # Fedora and family update cache on every PKG_INSTALL call, no need for a separate update.
+    UPDATE_PKG_CACHE=":"
+    PKG_INSTALL=(${PKG_MANAGER} install -y)
+    PKG_COUNT="${PKG_MANAGER} check-update | egrep '(.i686|.x86|.noarch|.arm|.src)' | wc -l"
+    INSTALLER_DEPS=(dialog git iproute net-tools newt procps-ng which)
+    PIHOLE_DEPS=(bc bind-utils cronie curl findutils nmap-ncat sudo unzip wget libidn2 psmisc)
+    PIHOLE_WEB_DEPS=(lighttpd lighttpd-fastcgi php-common php-cli php-pdo)
     LIGHTTPD_USER="lighttpd"
     LIGHTTPD_GROUP="lighttpd"
     LIGHTTPD_CFG="lighttpd.conf.fedora"
-    DNSMASQ_USER="nobody"
+    # If the host OS is Fedora,
+    if grep -qi 'fedora' /etc/redhat-release; then
+        # all required packages should be available by default with the latest fedora release
+        # ensure 'php-json' is installed on Fedora (installed as dependency on CentOS7 + Remi repository)
+        PIHOLE_WEB_DEPS+=('php-json')
+    # or if host OS is CentOS,
+    elif grep -qi 'centos' /etc/redhat-release; then
+        # Pi-Hole currently supports CentOS 7+ with PHP7+
+        SUPPORTED_CENTOS_VERSION=7
+        SUPPORTED_CENTOS_PHP_VERSION=7
+        # Check current CentOS major release version
+        CURRENT_CENTOS_VERSION=$(rpm -q --queryformat '%{VERSION}' centos-release)
+        # Check if CentOS version is supported
+        if [[ $CURRENT_CENTOS_VERSION -lt $SUPPORTED_CENTOS_VERSION ]]; then
+            echo -e "  ${CROSS} CentOS $CURRENT_CENTOS_VERSION is not suported."
+            echo -e "      Please update to CentOS release $SUPPORTED_CENTOS_VERSION or later"
+            # exit the installer
+            exit
+        fi
+        # on CentOS we need to add the EPEL repository to gain access to Fedora packages
+        EPEL_PKG="epel-release"
+        rpm -q ${EPEL_PKG} &> /dev/null || rc=$?
+        if [[ $rc -ne 0 ]]; then
+            echo -e "  ${INFO} Enabling EPEL package repository (https://fedoraproject.org/wiki/EPEL)"
+            "${PKG_INSTALL[@]}" ${EPEL_PKG} &> /dev/null
+            echo -e "  ${TICK} Installed ${EPEL_PKG}"
+        fi
+
+        # The default php on CentOS 7.x is 5.4 which is EOL
+        # Check if the version of PHP available via installed repositories is >= to PHP 7
+        AVAILABLE_PHP_VERSION=$(${PKG_MANAGER} info php | grep -i version | grep -o '[0-9]\+' | head -1)
+        if [[ $AVAILABLE_PHP_VERSION -ge $SUPPORTED_CENTOS_PHP_VERSION ]]; then
+            # Since PHP 7 is available by default, install via default PHP package names
+            : # do nothing as PHP is current
+        else
+            REMI_PKG="remi-release"
+            REMI_REPO="remi-php72"
+            rpm -q ${REMI_PKG} &> /dev/null || rc=$?
+        if [[ $rc -ne 0 ]]; then
+            # The PHP version available via default repositories is older than version 7
+            if ! whiptail --defaultno --title "PHP 7 Update (recommended)" --yesno "PHP 7.x is recommended for both security and language features.\\nWould you like to install PHP7 via Remi's RPM repository?\\n\\nSee: https://rpms.remirepo.net for more information" ${r} ${c}; then
+                # User decided to NOT update PHP from REMI, attempt to install the default available PHP version
+                echo -e "  ${INFO} User opt-out of PHP 7 upgrade on CentOS. Deprecated PHP may be in use."
+                : # continue with unsupported php version
+            else
+                echo -e "  ${INFO} Enabling Remi's RPM repository (https://rpms.remirepo.net)"
+                "${PKG_INSTALL[@]}" "https://rpms.remirepo.net/enterprise/${REMI_PKG}-$(rpm -E '%{rhel}').rpm" &> /dev/null
+                # enable the PHP 7 repository via yum-config-manager (provided by yum-utils)
+                "${PKG_INSTALL[@]}" "yum-utils" &> /dev/null
+                yum-config-manager --enable ${REMI_REPO} &> /dev/null
+                echo -e "  ${TICK} Remi's RPM repository has been enabled for PHP7"
+                # trigger an install/update of PHP to ensure previous version of PHP is updated from REMI
+                if "${PKG_INSTALL[@]}" "php-cli" &> /dev/null; then
+                    echo -e "  ${TICK} PHP7 installed/updated via Remi's RPM repository"
+                else
+                    echo -e "  ${CROSS} There was a problem updating to PHP7 via Remi's RPM repository"
+                    exit 1
+                fi
+            fi
+        fi
+    fi
+    else
+        # If not a supported version of Fedora or CentOS,
+        echo -e "  ${CROSS} Unsupported RPM based distribution"
+        # exit the installer
+        exit
+    fi
 
 # If neither apt-get or rmp/dnf are found
 else
-  # it's not an OS we can support,
-  echo -e "  ${CROSS} OS distribution not supported"
-  # so exit the installer
-  exit
+    # it's not an OS we can support,
+    echo -e "  ${CROSS} OS distribution not supported"
+    # so exit the installer
+    exit
 fi
 }
 
 # A function for checking if a folder is a git repository
 is_repo() {
-  # Use a named, local variable instead of the vague $1, which is the first arguement passed to this function
-  # These local variables should always be lowercase
-  local directory="${1}"
-  # A local variable for the current directory
-  local curdir
-  # A variable to store the return code
-  local rc
-  # Assign the current directory variable by using pwd
-  curdir="${PWD}"
-  # If the first argument passed to this function is a directory,
-  if [[ -d "${directory}" ]]; then
-    # move into the directory
-    cd "${directory}"
-    # Use git to check if the folder is a repo
-    # git -C is not used here to support git versions older than 1.8.4
-    git status --short &> /dev/null || rc=$?
-  # If the command was not successful,
-  else
-    # Set a non-zero return code if directory does not exist
-    rc=1
-  fi
-  # Move back into the directory the user started in
-  cd "${curdir}"
-  # Return the code; if one is not set, return 0
-  return "${rc:-0}"
+    # Use a named, local variable instead of the vague $1, which is the first argument passed to this function
+    # These local variables should always be lowercase
+    local directory="${1}"
+    # A local variable for the current directory
+    local curdir
+    # A variable to store the return code
+    local rc
+    # Assign the current directory variable by using pwd
+    curdir="${PWD}"
+    # If the first argument passed to this function is a directory,
+    if [[ -d "${directory}" ]]; then
+        # move into the directory
+        cd "${directory}"
+        # Use git to check if the folder is a repo
+        # git -C is not used here to support git versions older than 1.8.4
+        git status --short &> /dev/null || rc=$?
+    # If the command was not successful,
+    else
+        # Set a non-zero return code if directory does not exist
+        rc=1
+    fi
+    # Move back into the directory the user started in
+    cd "${curdir}"
+    # Return the code; if one is not set, return 0
+    return "${rc:-0}"
 }
 
 # A function to clone a repo
 make_repo() {
-  # Set named variables for better readability
-  local directory="${1}"
-  local remoteRepo="${2}"
-  # The message to display when this function is running
-  str="Clone ${remoteRepo} into ${directory}"
-  # Display the message and use the color table to preface the message with an "info" indicator
-  echo -ne "  ${INFO} ${str}..."
-  # If the directory exists,
-  if [[ -d "${directory}" ]]; then
-    # delete everything in it so git can clone into it
-    rm -rf "${directory}"
-  fi
-  # Clone the repo and return the return code from this command
-  git clone -q --depth 1 "${remoteRepo}" "${directory}" &> /dev/null || return $?
-  # Show a colored message showing it's status
-  echo -e "${OVER}  ${TICK} ${str}"
-  # Always return 0? Not sure this is correct
-  return 0
+    # Set named variables for better readability
+    local directory="${1}"
+    local remoteRepo="${2}"
+    # The message to display when this function is running
+    str="Clone ${remoteRepo} into ${directory}"
+    # Display the message and use the color table to preface the message with an "info" indicator
+    echo -ne "  ${INFO} ${str}..."
+    # If the directory exists,
+    if [[ -d "${directory}" ]]; then
+        # delete everything in it so git can clone into it
+        rm -rf "${directory}"
+    fi
+    # Clone the repo and return the return code from this command
+    git clone -q --depth 1 "${remoteRepo}" "${directory}" &> /dev/null || return $?
+    # Show a colored message showing it's status
+    echo -e "${OVER}  ${TICK} ${str}"
+    # Always return 0? Not sure this is correct
+    return 0
 }
 
 # We need to make sure the repos are up-to-date so we can effectively install Clean out the directory if it exists for git to clone into
 update_repo() {
-  # Use named, local variables
-  # As you can see, these are the same variable names used in the last function,
-  # but since they are local, their scope does not go beyond this function
-  # This helps prevent the wrong value from being assigned if you were to set the variable as a GLOBAL one
-  local directory="${1}"
-  local curdir
-
-  # A variable to store the message we want to display;
-  # Again, it's useful to store these in variables in case we need to reuse or change the message;
-  # we only need to make one change here
-  local str="Update repo in ${1}"
-
-  # Make sure we know what directory we are in so we can move back into it
-  curdir="${PWD}"
-  # Move into the directory that was passed as an argument
-  cd "${directory}" &> /dev/null || return 1
-  # Let the user know what's happening
-  echo -ne "  ${INFO} ${str}..."
-  # Stash any local commits as they conflict with our working code
-  git stash --all --quiet &> /dev/null || true # Okay for stash failure
-  git clean --quiet --force -d || true # Okay for already clean directory
-  # Pull the latest commits
-  git pull --quiet &> /dev/null || return $?
-  # Show a completion message
-  echo -e "${OVER}  ${TICK} ${str}"
-  # Move back into the oiginal directory
-  cd "${curdir}" &> /dev/null || return 1
-  return 0
+    # Use named, local variables
+    # As you can see, these are the same variable names used in the last function,
+    # but since they are local, their scope does not go beyond this function
+    # This helps prevent the wrong value from being assigned if you were to set the variable as a GLOBAL one
+    local directory="${1}"
+    local curdir
+
+    # A variable to store the message we want to display;
+    # Again, it's useful to store these in variables in case we need to reuse or change the message;
+    # we only need to make one change here
+    local str="Update repo in ${1}"
+
+    # Make sure we know what directory we are in so we can move back into it
+    curdir="${PWD}"
+    # Move into the directory that was passed as an argument
+    cd "${directory}" &> /dev/null || return 1
+    # Let the user know what's happening
+    echo -ne "  ${INFO} ${str}..."
+    # Stash any local commits as they conflict with our working code
+    git stash --all --quiet &> /dev/null || true # Okay for stash failure
+    git clean --quiet --force -d || true # Okay for already clean directory
+    # Pull the latest commits
+    git pull --quiet &> /dev/null || return $?
+    # Show a completion message
+    echo -e "${OVER}  ${TICK} ${str}"
+    # Move back into the original directory
+    cd "${curdir}" &> /dev/null || return 1
+    return 0
 }
 
 # A function that combines the functions previously made
 getGitFiles() {
-  # Setup named variables for the git repos
-  # We need the directory
-  local directory="${1}"
-  # as well as the repo URL
-  local remoteRepo="${2}"
-  # A local varible containing the message to be displayed
-  local str="Check for existing repository in ${1}"
-  # Show the message
-  echo -ne "  ${INFO} ${str}..."
-  # Check if the directory is a repository
-  if is_repo "${directory}"; then
-    # Show that we're checking it
-    echo -e "${OVER}  ${TICK} ${str}"
-    # Update the repo, returning an error message on failure
-    update_repo "${directory}" || { echo -e "\\n  ${COL_LIGHT_RED}Error: Could not update local repository. Contact support.${COL_NC}"; exit 1; }
-  # If it's not a .git repo,
-  else
-    # Show an error
-    echo -e "${OVER}  ${CROSS} ${str}"
-    # Attempt to make the repository, showing an error on falure
-    make_repo "${directory}" "${remoteRepo}" || { echo -e "\\n  ${COL_LIGHT_RED}Error: Could not update local repository. Contact support.${COL_NC}"; exit 1; }
-  fi
-  # echo a blank line
-  echo ""
-  # and return success?
-  return 0
+    # Setup named variables for the git repos
+    # We need the directory
+    local directory="${1}"
+    # as well as the repo URL
+    local remoteRepo="${2}"
+    # A local variable containing the message to be displayed
+    local str="Check for existing repository in ${1}"
+    # Show the message
+    echo -ne "  ${INFO} ${str}..."
+    # Check if the directory is a repository
+    if is_repo "${directory}"; then
+        # Show that we're checking it
+        echo -e "${OVER}  ${TICK} ${str}"
+        # Update the repo, returning an error message on failure
+        update_repo "${directory}" || { echo -e "\\n  ${COL_LIGHT_RED}Error: Could not update local repository. Contact support.${COL_NC}"; exit 1; }
+    # If it's not a .git repo,
+    else
+        # Show an error
+        echo -e "${OVER}  ${CROSS} ${str}"
+        # Attempt to make the repository, showing an error on failure
+        make_repo "${directory}" "${remoteRepo}" || { echo -e "\\n  ${COL_LIGHT_RED}Error: Could not update local repository. Contact support.${COL_NC}"; exit 1; }
+    fi
+    # echo a blank line
+    echo ""
+    # and return success?
+    return 0
 }
 
 # Reset a repo to get rid of any local changed
 resetRepo() {
-  # Use named varibles for arguments
-  local directory="${1}"
-  # Move into the directory
-  cd "${directory}" &> /dev/null || return 1
-  # Store the message in a varible
-  str="Resetting repository within ${1}..."
-  # Show the message
-  echo -ne "  ${INFO} ${str}"
-  # Use git to remove the local changes
-  git reset --hard &> /dev/null || return $?
-  # And show the status
-  echo -e "${OVER}  ${TICK} ${str}"
-  # Returning success anyway?
-  return 0
+    # Use named variables for arguments
+    local directory="${1}"
+    # Move into the directory
+    cd "${directory}" &> /dev/null || return 1
+    # Store the message in a variable
+    str="Resetting repository within ${1}..."
+    # Show the message
+    echo -ne "  ${INFO} ${str}"
+    # Use git to remove the local changes
+    git reset --hard &> /dev/null || return $?
+    # And show the status
+    echo -e "${OVER}  ${TICK} ${str}"
+    # Returning success anyway?
+    return 0
 }
 
 # We need to know the IPv4 information so we can effectively setup the DNS server
 # Without this information, we won't know where to Pi-hole will be found
 find_IPv4_information() {
-  # Named, local variables
-  local route
-  # Find IP used to route to outside world by checking the the route to Google's public DNS server
-  route=$(ip route get 8.8.8.8)
-  # Use awk to strip out just the interface device as it is used in future commands
-  IPv4dev=$(awk '{for (i=1; i<=NF; i++) if ($i~/dev/) print $(i+1)}' <<< "${route}")
-  # Get just the IP address
-  IPv4bare=$(awk '{print $7}' <<< "${route}")
-  # Append the CIDR notation to the IP address
-  IPV4_ADDRESS=$(ip -o -f inet addr show | grep "${IPv4bare}" |  awk '{print $4}' | awk 'END {print}')
-  # Get the default gateway (the way to reach the Internet)
-  IPv4gw=$(awk '{print $3}' <<< "${route}")
-
+    # Named, local variables
+    local route
+    # Find IP used to route to outside world by checking the the route to Google's public DNS server
+    route=$(ip route get 8.8.8.8)
+    # Use awk to strip out just the interface device as it is used in future commands
+    IPv4dev=$(awk '{for (i=1; i<=NF; i++) if ($i~/dev/) print $(i+1)}' <<< "${route}")
+    # Get just the IP address
+    IPv4bare=$(awk '{print $7}' <<< "${route}")
+    # Append the CIDR notation to the IP address
+    IPV4_ADDRESS=$(ip -o -f inet addr show | grep "${IPv4bare}" |  awk '{print $4}' | awk 'END {print}')
+    # Get the default gateway (the way to reach the Internet)
+    IPv4gw=$(awk '{print $3}' <<< "${route}")
 }
 
 # Get available interfaces that are UP
 get_available_interfaces() {
-  # There may be more than one so it's all stored in a variable
-  availableInterfaces=$(ip --oneline link show up | grep -v "lo" | awk '{print $2}' | cut -d':' -f1 | cut -d'@' -f1)
+    # There may be more than one so it's all stored in a variable
+    availableInterfaces=$(ip --oneline link show up | grep -v "lo" | awk '{print $2}' | cut -d':' -f1 | cut -d'@' -f1)
 }
 
 # A function for displaying the dialogs the user sees when first running the installer
 welcomeDialogs() {
-  # Display the welcome dialog using an approriately sized window via the calculation conducted earlier in the script
-  whiptail --msgbox --backtitle "Welcome" --title "Pi-hole automated installer" "\\n\\nThis installer will transform your device into a network-wide ad blocker!" ${r} ${c}
+    # Display the welcome dialog using an appropriately sized window via the calculation conducted earlier in the script
+    whiptail --msgbox --backtitle "Welcome" --title "Pi-hole automated installer" "\\n\\nThis installer will transform your device into a network-wide ad blocker!" ${r} ${c}
 
-  # Request that users donate if they enjoy the software since we all work on it in our free time
-  whiptail --msgbox --backtitle "Plea" --title "Free and open source" "\\n\\nThe Pi-hole is free, but powered by your donations:  http://pi-hole.net/donate" ${r} ${c}
+    # Request that users donate if they enjoy the software since we all work on it in our free time
+    whiptail --msgbox --backtitle "Plea" --title "Free and open source" "\\n\\nThe Pi-hole is free, but powered by your donations:  http://pi-hole.net/donate" ${r} ${c}
 
-  # Explain the need for a static address
-  whiptail --msgbox --backtitle "Initiating network interface" --title "Static IP Needed" "\\n\\nThe Pi-hole is a SERVER so it needs a STATIC IP ADDRESS to function properly.
+    # Explain the need for a static address
+    whiptail --msgbox --backtitle "Initiating network interface" --title "Static IP Needed" "\\n\\nThe Pi-hole is a SERVER so it needs a STATIC IP ADDRESS to function properly.
 
 In the next section, you can choose to use your current network settings (DHCP) or to manually edit them." ${r} ${c}
 }
 
 # We need to make sure there is enough space before installing, so there is a function to check this
 verifyFreeDiskSpace() {
-
-  # 50MB is the minimum space needed (45MB install (includes web admin bootstrap/jquery libraries etc) + 5MB one day of logs.)
-  # - Fourdee: Local ensures the variable is only created, and accessible within this function/void. Generally considered a "good" coding practice for non-global variables.
-  local str="Disk space check"
-  # Reqired space in KB
-  local required_free_kilobytes=51200
-  # Calculate existing free space on this machine
-  local existing_free_kilobytes
-  existing_free_kilobytes=$(df -Pk | grep -m1 '\/$' | awk '{print $4}')
-
-  # If the existing space is not an integer,
-  if ! [[ "${existing_free_kilobytes}" =~ ^([0-9])+$ ]]; then
-    # show an error that we can't determine the free space
-    echo -e "  ${CROSS} ${str}
-      Unknown free disk space!
-      We were unable to determine available free disk space on this system.
-      You may override this check, however, it is not recommended
-      The option '${COL_LIGHT_RED}--i_do_not_follow_recommendations${COL_NC}' can override this
-      e.g: curl -L https://install.pi-hole.net | bash /dev/stdin ${COL_LIGHT_RED}<option>${COL_NC}"
-    # exit with an error code
-    exit 1
-  # If there is insufficient free disk space,
-  elif [[ "${existing_free_kilobytes}" -lt "${required_free_kilobytes}" ]]; then
-    # show an error message
-    echo -e "  ${CROSS} ${str}
-      Your system disk appears to only have ${existing_free_kilobytes} KB free
-      It is recommended to have a minimum of ${required_free_kilobytes} KB to run the Pi-hole"
-    # if the vcgencmd command exists,
-    if command -v vcgencmd &> /dev/null; then
-      # it's probably a Raspbian install, so show a message about expanding the filesystem
-      echo "      If this is a new install you may need to expand your disk
-      Run 'sudo raspi-config', and choose the 'expand file system' option
-      After rebooting, run this installation again
-      e.g: curl -L https://install.pi-hole.net | bash"
-    fi
-    # Show there is not enough free space
-    echo -e "\\n      ${COL_LIGHT_RED}Insufficient free space, exiting...${COL_NC}"
-    # and exit with an error
-    exit 1
-  # Otherwise,
-  else
-    # Show that we're running a disk space check
-    echo -e "  ${TICK} ${str}"
-  fi
+    # 50MB is the minimum space needed (45MB install (includes web admin bootstrap/jquery libraries etc) + 5MB one day of logs.)
+    # - Fourdee: Local ensures the variable is only created, and accessible within this function/void. Generally considered a "good" coding practice for non-global variables.
+    local str="Disk space check"
+    # Required space in KB
+    local required_free_kilobytes=51200
+    # Calculate existing free space on this machine
+    local existing_free_kilobytes
+    existing_free_kilobytes=$(df -Pk | grep -m1 '\/$' | awk '{print $4}')
+
+    # If the existing space is not an integer,
+    if ! [[ "${existing_free_kilobytes}" =~ ^([0-9])+$ ]]; then
+        # show an error that we can't determine the free space
+        echo -e "  ${CROSS} ${str}"
+        echo -e "  ${INFO} Unknown free disk space!"
+        echo -e "  ${INFO} We were unable to determine available free disk space on this system."
+        echo -e "  ${INFO} You may override this check, however, it is not recommended"
+        echo -e "  ${INFO} The option '${COL_LIGHT_RED}--i_do_not_follow_recommendations${COL_NC}' can override this"
+        echo -e "  ${INFO} e.g: curl -L https://install.pi-hole.net | bash /dev/stdin ${COL_LIGHT_RED}<option>${COL_NC}"
+        # exit with an error code
+        exit 1
+    # If there is insufficient free disk space,
+    elif [[ "${existing_free_kilobytes}" -lt "${required_free_kilobytes}" ]]; then
+        # show an error message
+        echo -e "  ${CROSS} ${str}"
+        echo -e "  ${INFO} Your system disk appears to only have ${existing_free_kilobytes} KB free"
+        echo -e "  ${INFO} It is recommended to have a minimum of ${required_free_kilobytes} KB to run the Pi-hole"
+        # if the vcgencmd command exists,
+        if command -v vcgencmd &> /dev/null; then
+            # it's probably a Raspbian install, so show a message about expanding the filesystem
+            echo -e "  ${INFO} If this is a new install you may need to expand your disk"
+            echo -e "  ${INFO} Run 'sudo raspi-config', and choose the 'expand file system' option"
+            echo -e "  ${INFO} After rebooting, run this installation again"
+            echo -e "  ${INFO} e.g: curl -L https://install.pi-hole.net | bash"
+        fi
+        # Show there is not enough free space
+        echo -e "\\n      ${COL_LIGHT_RED}Insufficient free space, exiting...${COL_NC}"
+        # and exit with an error
+        exit 1
+    # Otherwise,
+    else
+        # Show that we're running a disk space check
+        echo -e "  ${TICK} ${str}"
+    fi
 }
 
 # A function that let's the user pick an interface to use with Pi-hole
 chooseInterface() {
-  # Turn the available interfaces into an array so it can be used with a whiptail dialog
-  local interfacesArray=()
-  # Number of available interfaces
-  local interfaceCount
-  # Whiptail variable storage
-  local chooseInterfaceCmd
-  # Temporary Whiptail options storage
-  local chooseInterfaceOptions
-  # Loop sentinel variable
-  local firstLoop=1
-
-  # Find out how many interfaces are available to choose from
-  interfaceCount=$(echo "${availableInterfaces}" | wc -l)
-
-  # If there is one interface,
-  if [[ "${interfaceCount}" -eq 1 ]]; then
-      # Set it as the interface to use since there is no other option
-      PIHOLE_INTERFACE="${availableInterfaces}"
-  # Otherwise,
-  else
-      # While reading through the available interfaces
-      while read -r line; do
-        # use a variable to set the option as OFF to begin with
-        mode="OFF"
-        # If it's the first loop,
-        if [[ "${firstLoop}" -eq 1 ]]; then
-          # set this as the interface to use (ON)
-          firstLoop=0
-          mode="ON"
-        fi
-        # Put all these interfaces into an array
-        interfacesArray+=("${line}" "available" "${mode}")
-      # Feed the available interfaces into this while loop
-      done <<< "${availableInterfaces}"
-      # The whiptail command that will be run, stored in a variable
-      chooseInterfaceCmd=(whiptail --separate-output --radiolist "Choose An Interface (press space to select)" ${r} ${c} ${interfaceCount})
-      # Now run the command using the interfaces saved into the array
-      chooseInterfaceOptions=$("${chooseInterfaceCmd[@]}" "${interfacesArray[@]}" 2>&1 >/dev/tty) || \
-      # If the user chooses Canel, exit
-      { echo -e "  ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; }
-      # For each interface
-      for desiredInterface in ${chooseInterfaceOptions}; do
-        # Set the one the user selected as the interface to use
-        PIHOLE_INTERFACE=${desiredInterface}
-        # and show this information to the user
-        echo -e "  ${INFO} Using interface: $PIHOLE_INTERFACE"
-      done
-  fi
+    # Turn the available interfaces into an array so it can be used with a whiptail dialog
+    local interfacesArray=()
+    # Number of available interfaces
+    local interfaceCount
+    # Whiptail variable storage
+    local chooseInterfaceCmd
+    # Temporary Whiptail options storage
+    local chooseInterfaceOptions
+    # Loop sentinel variable
+    local firstLoop=1
+
+    # Find out how many interfaces are available to choose from
+    interfaceCount=$(echo "${availableInterfaces}" | wc -l)
+
+    # If there is one interface,
+    if [[ "${interfaceCount}" -eq 1 ]]; then
+        # Set it as the interface to use since there is no other option
+        PIHOLE_INTERFACE="${availableInterfaces}"
+    # Otherwise,
+    else
+        # While reading through the available interfaces
+        while read -r line; do
+            # use a variable to set the option as OFF to begin with
+            mode="OFF"
+            # If it's the first loop,
+            if [[ "${firstLoop}" -eq 1 ]]; then
+                # set this as the interface to use (ON)
+                firstLoop=0
+                mode="ON"
+            fi
+            # Put all these interfaces into an array
+            interfacesArray+=("${line}" "available" "${mode}")
+        # Feed the available interfaces into this while loop
+        done <<< "${availableInterfaces}"
+        # The whiptail command that will be run, stored in a variable
+        chooseInterfaceCmd=(whiptail --separate-output --radiolist "Choose An Interface (press space to select)" ${r} ${c} ${interfaceCount})
+        # Now run the command using the interfaces saved into the array
+        chooseInterfaceOptions=$("${chooseInterfaceCmd[@]}" "${interfacesArray[@]}" 2>&1 >/dev/tty) || \
+        # If the user chooses Cancel, exit
+        { echo -e "  ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; }
+        # For each interface
+        for desiredInterface in ${chooseInterfaceOptions}; do
+            # Set the one the user selected as the interface to use
+            PIHOLE_INTERFACE=${desiredInterface}
+            # and show this information to the user
+            echo -e "  ${INFO} Using interface: $PIHOLE_INTERFACE"
+        done
+    fi
 }
 
 # This lets us prefer ULA addresses over GUA
 # This caused problems for some users when their ISP changed their IPv6 addresses
 # See https://github.com/pi-hole/pi-hole/issues/1473#issuecomment-301745953
 testIPv6() {
-  # first will contain fda2 (ULA)
-  first="$(cut -f1 -d":" <<< "$1")"
-  # value1 will contain 253 which is the decimal value corresponding to 0xfd
-  value1=$(( (0x$first)/256 ))
-  # will contain 162 which is the decimal value corresponding to 0xa2
-  value2=$(( (0x$first)%256 ))
-  # the ULA test is testing for fc00::/7 according to RFC 4193
-  if (( (value1&254)==252 )); then
-    echo "ULA"
-  fi
-  # the GUA test is testing for 2000::/3 according to RFC 4291
-  if (( (value1&112)==32 )); then
-    echo "GUA"
-  fi
-  # the LL test is testing for fe80::/10 according to RFC 4193
-  if (( (value1)==254 )) && (( (value2&192)==128 )); then
-    echo "Link-local"
-  fi
+    # first will contain fda2 (ULA)
+    first="$(cut -f1 -d":" <<< "$1")"
+    # value1 will contain 253 which is the decimal value corresponding to 0xfd
+    value1=$(( (0x$first)/256 ))
+    # will contain 162 which is the decimal value corresponding to 0xa2
+    value2=$(( (0x$first)%256 ))
+    # the ULA test is testing for fc00::/7 according to RFC 4193
+    if (( (value1&254)==252 )); then
+        echo "ULA"
+    fi
+    # the GUA test is testing for 2000::/3 according to RFC 4291
+    if (( (value1&112)==32 )); then
+        echo "GUA"
+    fi
+    # the LL test is testing for fe80::/10 according to RFC 4193
+    if (( (value1)==254 )) && (( (value2&192)==128 )); then
+        echo "Link-local"
+    fi
 }
 
 # A dialog for showing the user about IPv6 blocking
 useIPv6dialog() {
-  # Determine the IPv6 address used for blocking
-  IPV6_ADDRESSES=($(ip -6 address | grep 'scope global' | awk '{print $2}'))
-
-  # For each address in the array above, determine the type of IPv6 address it is
-  for i in "${IPV6_ADDRESSES[@]}"; do
-    # Check if it's ULA, GUA, or LL by using the function created earlier
-    result=$(testIPv6 "$i")
-    # If it's a ULA address, use it and store it as a global variable
-    [[ "${result}" == "ULA" ]] && ULA_ADDRESS="${i%/*}"
-    # If it's a GUA address, we can still use it si store it as a global variable
-    [[ "${result}" == "GUA" ]] && GUA_ADDRESS="${i%/*}"
-  done
-
-  # Determine which address to be used: Prefer ULA over GUA or don't use any if none found
-  # If the ULA_ADDRESS contains a value,
-  if [[ ! -z "${ULA_ADDRESS}" ]]; then
-    # set the IPv6 address to the ULA address
-    IPV6_ADDRESS="${ULA_ADDRESS}"
-    # Show this info to the user
-    echo -e "  ${INFO} Found IPv6 ULA address, using it for blocking IPv6 ads"
-  # Otherwise, if the GUA_ADDRESS has a value,
-  elif [[ ! -z "${GUA_ADDRESS}" ]]; then
-    # Let the user know
-    echo -e "  ${INFO} Found IPv6 GUA address, using it for blocking IPv6 ads"
-    # And assign it to the global variable
-    IPV6_ADDRESS="${GUA_ADDRESS}"
-  # If none of those work,
-  else
-    # explain that IPv6 blocking will not be used
-    echo -e "  ${INFO} Unable to find IPv6 ULA/GUA address, IPv6 adblocking will not be enabled"
-    # So set the variable to be empty
-    IPV6_ADDRESS=""
-  fi
-
-  # If the IPV6_ADDRESS contains a value
-  if [[ ! -z "${IPV6_ADDRESS}" ]]; then
-    # Display that IPv6 is supported and will be used
-    whiptail --msgbox --backtitle "IPv6..." --title "IPv6 Supported" "$IPV6_ADDRESS will be used to block ads." ${r} ${c}
-  fi
+    # Determine the IPv6 address used for blocking
+    IPV6_ADDRESSES=($(ip -6 address | grep 'scope global' | awk '{print $2}'))
+
+    # For each address in the array above, determine the type of IPv6 address it is
+    for i in "${IPV6_ADDRESSES[@]}"; do
+        # Check if it's ULA, GUA, or LL by using the function created earlier
+        result=$(testIPv6 "$i")
+        # If it's a ULA address, use it and store it as a global variable
+        [[ "${result}" == "ULA" ]] && ULA_ADDRESS="${i%/*}"
+        # If it's a GUA address, we can still use it si store it as a global variable
+        [[ "${result}" == "GUA" ]] && GUA_ADDRESS="${i%/*}"
+    done
+
+    # Determine which address to be used: Prefer ULA over GUA or don't use any if none found
+    # If the ULA_ADDRESS contains a value,
+    if [[ ! -z "${ULA_ADDRESS}" ]]; then
+        # set the IPv6 address to the ULA address
+        IPV6_ADDRESS="${ULA_ADDRESS}"
+        # Show this info to the user
+        echo -e "  ${INFO} Found IPv6 ULA address, using it for blocking IPv6 ads"
+    # Otherwise, if the GUA_ADDRESS has a value,
+    elif [[ ! -z "${GUA_ADDRESS}" ]]; then
+        # Let the user know
+        echo -e "  ${INFO} Found IPv6 GUA address, using it for blocking IPv6 ads"
+        # And assign it to the global variable
+        IPV6_ADDRESS="${GUA_ADDRESS}"
+    # If none of those work,
+    else
+        # explain that IPv6 blocking will not be used
+        echo -e "  ${INFO} Unable to find IPv6 ULA/GUA address, IPv6 adblocking will not be enabled"
+        # So set the variable to be empty
+        IPV6_ADDRESS=""
+    fi
+
+    # If the IPV6_ADDRESS contains a value
+    if [[ ! -z "${IPV6_ADDRESS}" ]]; then
+        # Display that IPv6 is supported and will be used
+        whiptail --msgbox --backtitle "IPv6..." --title "IPv6 Supported" "$IPV6_ADDRESS will be used to block ads." ${r} ${c}
+    fi
 }
 
 # A function to check if we should use IPv4 and/or IPv6 for blocking ads
 use4andor6() {
-  # Named local variables
-  local useIPv4
-  local useIPv6
-  # Let use select IPv4 and/or IPv6 via a checklist
-  cmd=(whiptail --separate-output --checklist "Select Protocols (press space to select)" ${r} ${c} 2)
-  # In an array, show the options available:
-  # IPv4 (on by default)
-  options=(IPv4 "Block ads over IPv4" on
-  # or IPv6 (on by default if available)
-  IPv6 "Block ads over IPv6" on)
-  # In a variable, show the choices available; exit if Cancel is selected
-  choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) || { echo -e "  ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; }
-  # For each choice available,
-  for choice in ${choices}
-  do
-    # Set the values to true
-    case ${choice} in
-    IPv4  )   useIPv4=true;;
-    IPv6  )   useIPv6=true;;
-    esac
-  done
-  # If IPv4 is to be used,
-  if [[ "${useIPv4}" ]]; then
-    # Run our function to get the information we need
-    find_IPv4_information
-    getStaticIPv4Settings
-    setStaticIPv4
-  fi
-  # If IPv6 is to be used,
-  if [[ "${useIPv6}" ]]; then
-    # Run our function to get this information
-    useIPv6dialog
-  fi
-  # Echo the information to the user
+    # Named local variables
+    local useIPv4
+    local useIPv6
+    # Let use select IPv4 and/or IPv6 via a checklist
+    cmd=(whiptail --separate-output --checklist "Select Protocols (press space to select)" ${r} ${c} 2)
+    # In an array, show the options available:
+    # IPv4 (on by default)
+    options=(IPv4 "Block ads over IPv4" on
+    # or IPv6 (on by default if available)
+    IPv6 "Block ads over IPv6" on)
+    # In a variable, show the choices available; exit if Cancel is selected
+    choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) || { echo -e "  ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; }
+    # For each choice available,
+    for choice in ${choices}
+    do
+        # Set the values to true
+        case ${choice} in
+        IPv4  )   useIPv4=true;;
+        IPv6  )   useIPv6=true;;
+        esac
+    done
+    # If IPv4 is to be used,
+    if [[ "${useIPv4}" ]]; then
+        # Run our function to get the information we need
+        find_IPv4_information
+        getStaticIPv4Settings
+        setStaticIPv4
+    fi
+    # If IPv6 is to be used,
+    if [[ "${useIPv6}" ]]; then
+        # Run our function to get this information
+        useIPv6dialog
+    fi
+    # Echo the information to the user
     echo -e "  ${INFO} IPv4 address: ${IPV4_ADDRESS}"
     echo -e "  ${INFO} IPv6 address: ${IPV6_ADDRESS}"
-  # If neither protocol is selected,
-  if [[ ! "${useIPv4}" ]] && [[ ! "${useIPv6}" ]]; then
-    # Show an error in red
-    echo -e "  ${COL_LIGHT_RED}Error: Neither IPv4 or IPv6 selected${COL_NC}"
-    # and exit with an error
-    exit 1
-  fi
+    # If neither protocol is selected,
+    if [[ ! "${useIPv4}" ]] && [[ ! "${useIPv6}" ]]; then
+        # Show an error in red
+        echo -e "  ${COL_LIGHT_RED}Error: Neither IPv4 or IPv6 selected${COL_NC}"
+        # and exit with an error
+        exit 1
+    fi
 }
 
 #
 getStaticIPv4Settings() {
-  # Local, named variables
-  local ipSettingsCorrect
-  # Ask if the user wants to use DHCP settings as their static IP
-  # This is useful for users that are using DHCP reservations; then we can just use the information gathered via our functions
-  if whiptail --backtitle "Calibrating network interface" --title "Static IP Address" --yesno "Do you want to use your current network settings as a static address?
+    # Local, named variables
+    local ipSettingsCorrect
+    # Ask if the user wants to use DHCP settings as their static IP
+    # This is useful for users that are using DHCP reservations; then we can just use the information gathered via our functions
+    if whiptail --backtitle "Calibrating network interface" --title "Static IP Address" --yesno "Do you want to use your current network settings as a static address?
           IP address:    ${IPV4_ADDRESS}
           Gateway:       ${IPv4gw}" ${r} ${c}; then
-    # If they choose yes, let the user know that the IP address will not be available via DHCP and may cause a conflict.
-    whiptail --msgbox --backtitle "IP information" --title "FYI: IP Conflict" "It is possible your router could still try to assign this IP to a device, which would cause a conflict.  But in most cases the router is smart enough to not do that.
+        # If they choose yes, let the user know that the IP address will not be available via DHCP and may cause a conflict.
+        whiptail --msgbox --backtitle "IP information" --title "FYI: IP Conflict" "It is possible your router could still try to assign this IP to a device, which would cause a conflict.  But in most cases the router is smart enough to not do that.
 If you are worried, either manually set the address, or modify the DHCP reservation pool so it does not include the IP you want.
 It is also possible to use a DHCP reservation, but if you are going to do that, you might as well set a static address." ${r} ${c}
-  # Nothing else to do since the variables are already set above
-  else
+    # Nothing else to do since the variables are already set above
+    else
     # Otherwise, we need to ask the user to input their desired settings.
     # Start by getting the IPv4 address (pre-filling it with info gathered from DHCP)
     # Start a loop to let the user enter their information with the chance to go back and edit it if necessary
     until [[ "${ipSettingsCorrect}" = True ]]; do
 
-      # Ask for the IPv4 address
-      IPV4_ADDRESS=$(whiptail --backtitle "Calibrating network interface" --title "IPv4 address" --inputbox "Enter your desired IPv4 address" ${r} ${c} "${IPV4_ADDRESS}" 3>&1 1>&2 2>&3) || \
-      # Cancelling IPv4 settings window
-      { ipSettingsCorrect=False; echo -e "  ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; }
-      echo -e "  ${INFO} Your static IPv4 address: ${IPV4_ADDRESS}"
-
-      # Ask for the gateway
-      IPv4gw=$(whiptail --backtitle "Calibrating network interface" --title "IPv4 gateway (router)" --inputbox "Enter your desired IPv4 default gateway" ${r} ${c} "${IPv4gw}" 3>&1 1>&2 2>&3) || \
-      # Cancelling gateway settings window
-      { ipSettingsCorrect=False; echo -e "  ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; }
-      echo -e "  ${INFO} Your static IPv4 gateway: ${IPv4gw}"
-
-      # Give the user a chance to review their settings before moving on
-      if whiptail --backtitle "Calibrating network interface" --title "Static IP Address" --yesno "Are these settings correct?
-        IP address: ${IPV4_ADDRESS}
-        Gateway:    ${IPv4gw}" ${r} ${c}; then
-        # After that's done, the loop ends and we move on
-        ipSettingsCorrect=True
+        # Ask for the IPv4 address
+        IPV4_ADDRESS=$(whiptail --backtitle "Calibrating network interface" --title "IPv4 address" --inputbox "Enter your desired IPv4 address" ${r} ${c} "${IPV4_ADDRESS}" 3>&1 1>&2 2>&3) || \
+        # Cancelling IPv4 settings window
+        { ipSettingsCorrect=False; echo -e "  ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; }
+        echo -e "  ${INFO} Your static IPv4 address: ${IPV4_ADDRESS}"
+
+        # Ask for the gateway
+        IPv4gw=$(whiptail --backtitle "Calibrating network interface" --title "IPv4 gateway (router)" --inputbox "Enter your desired IPv4 default gateway" ${r} ${c} "${IPv4gw}" 3>&1 1>&2 2>&3) || \
+        # Cancelling gateway settings window
+        { ipSettingsCorrect=False; echo -e "  ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; }
+        echo -e "  ${INFO} Your static IPv4 gateway: ${IPv4gw}"
+
+        # Give the user a chance to review their settings before moving on
+        if whiptail --backtitle "Calibrating network interface" --title "Static IP Address" --yesno "Are these settings correct?
+            IP address: ${IPV4_ADDRESS}
+            Gateway:    ${IPv4gw}" ${r} ${c}; then
+                # After that's done, the loop ends and we move on
+                ipSettingsCorrect=True
         else
-        # If the settings are wrong, the loop continues
-        ipSettingsCorrect=False
-      fi
+            # If the settings are wrong, the loop continues
+            ipSettingsCorrect=False
+        fi
     done
     # End the if statement for DHCP vs. static
-  fi
+    fi
 }
 
 # dhcpcd is very annoying,
 setDHCPCD() {
-  # but we can append these lines to dhcpcd.conf to enable a static IP
-  echo "interface ${PIHOLE_INTERFACE}
-  static ip_address=${IPV4_ADDRESS}
-  static routers=${IPv4gw}
-  static domain_name_servers=127.0.0.1" | tee -a /etc/dhcpcd.conf >/dev/null
+    # but we can append these lines to dhcpcd.conf to enable a static IP
+    echo "interface ${PIHOLE_INTERFACE}
+    static ip_address=${IPV4_ADDRESS}
+    static routers=${IPv4gw}
+    static domain_name_servers=127.0.0.1" | tee -a /etc/dhcpcd.conf >/dev/null
 }
 
 setStaticIPv4() {
-  # Local, named variables
-  local IFCFG_FILE
-  local IPADDR
-  local CIDR
-  # For the Debian family, if dhcpcd.conf exists,
-  if [[ -f "/etc/dhcpcd.conf" ]]; then
-    # check if the IP is already in the file
-    if grep -q "${IPV4_ADDRESS}" /etc/dhcpcd.conf; then
-      echo -e "  ${INFO} Static IP already configured"
-    # If it's not,
-    else
-      # set it using our function
-      setDHCPCD
-      # Then use the ip command to immediately set the new address
-      ip addr replace dev "${PIHOLE_INTERFACE}" "${IPV4_ADDRESS}"
-      # Also give a warning that the user may need to reboot their system
-      echo -e "  ${TICK} Set IP address to ${IPV4_ADDRESS%/*}
-      You may need to restart after the install is complete"
-    fi
-  # If it's not Debian, check if it's the Fedora family by checking for the file below
-  elif [[ -f "/etc/sysconfig/network-scripts/ifcfg-${PIHOLE_INTERFACE}" ]];then
-    # If it exists,
-    IFCFG_FILE=/etc/sysconfig/network-scripts/ifcfg-${PIHOLE_INTERFACE}
-    # check if the desired IP is already set
-    if grep -q "${IPV4_ADDRESS}" "${IFCFG_FILE}"; then
-      echo -e "  ${INFO} Static IP already configured"
-    # Otherwise,
+    # Local, named variables
+    local IFCFG_FILE
+    local IPADDR
+    local CIDR
+    # For the Debian family, if dhcpcd.conf exists,
+    if [[ -f "/etc/dhcpcd.conf" ]]; then
+        # check if the IP is already in the file
+        if grep -q "${IPV4_ADDRESS}" /etc/dhcpcd.conf; then
+            echo -e "  ${INFO} Static IP already configured"
+        # If it's not,
+        else
+            # set it using our function
+            setDHCPCD
+            # Then use the ip command to immediately set the new address
+            ip addr replace dev "${PIHOLE_INTERFACE}" "${IPV4_ADDRESS}"
+            # Also give a warning that the user may need to reboot their system
+            echo -e "  ${TICK} Set IP address to ${IPV4_ADDRESS%/*}
+            You may need to restart after the install is complete"
+        fi
+    # If it's not Debian, check if it's the Fedora family by checking for the file below
+    elif [[ -f "/etc/sysconfig/network-scripts/ifcfg-${PIHOLE_INTERFACE}" ]];then
+        # If it exists,
+        IFCFG_FILE=/etc/sysconfig/network-scripts/ifcfg-${PIHOLE_INTERFACE}
+        IPADDR=$(echo "${IPV4_ADDRESS}" | cut -f1 -d/)
+        # check if the desired IP is already set
+        if grep -Eq "${IPADDR}(\\b|\\/)" "${IFCFG_FILE}"; then
+            echo -e "  ${INFO} Static IP already configured"
+        # Otherwise,
+        else
+            # Put the IP in variables without the CIDR notation
+            CIDR=$(echo "${IPV4_ADDRESS}" | cut -f2 -d/)
+            # Backup existing interface configuration:
+            cp "${IFCFG_FILE}" "${IFCFG_FILE}".pihole.orig
+            # Build Interface configuration file using the GLOBAL variables we have
+            {
+            echo "# Configured via Pi-hole installer"
+            echo "DEVICE=$PIHOLE_INTERFACE"
+            echo "BOOTPROTO=none"
+            echo "ONBOOT=yes"
+            echo "IPADDR=$IPADDR"
+            echo "PREFIX=$CIDR"
+            echo "GATEWAY=$IPv4gw"
+            echo "DNS1=$PIHOLE_DNS_1"
+            echo "DNS2=$PIHOLE_DNS_2"
+            echo "USERCTL=no"
+            }> "${IFCFG_FILE}"
+            # Use ip to immediately set the new address
+            ip addr replace dev "${PIHOLE_INTERFACE}" "${IPV4_ADDRESS}"
+            # If NetworkMangler command line interface exists and ready to mangle,
+            if command -v nmcli &> /dev/null && nmcli general status &> /dev/null; then
+                # Tell NetworkManagler to read our new sysconfig file
+                nmcli con load "${IFCFG_FILE}" > /dev/null
+            fi
+            # Show a warning that the user may need to restart
+            echo -e "  ${TICK} Set IP address to ${IPV4_ADDRESS%/*}
+            You may need to restart after the install is complete"
+        fi
+    # If all that fails,
     else
-      # Put the IP in variables without the CIDR notation
-      IPADDR=$(echo "${IPV4_ADDRESS}" | cut -f1 -d/)
-      CIDR=$(echo "${IPV4_ADDRESS}" | cut -f2 -d/)
-      # Backup existing interface configuration:
-      cp "${IFCFG_FILE}" "${IFCFG_FILE}".pihole.orig
-      # Build Interface configuration file using the GLOBAL variables we have
-      {
-        echo "# Configured via Pi-hole installer"
-        echo "DEVICE=$PIHOLE_INTERFACE"
-        echo "BOOTPROTO=none"
-        echo "ONBOOT=yes"
-        echo "IPADDR=$IPADDR"
-        echo "PREFIX=$CIDR"
-        echo "GATEWAY=$IPv4gw"
-        echo "DNS1=$PIHOLE_DNS_1"
-        echo "DNS2=$PIHOLE_DNS_2"
-        echo "USERCTL=no"
-      }> "${IFCFG_FILE}"
-      # Use ip to immediately set the new address
-      ip addr replace dev "${PIHOLE_INTERFACE}" "${IPV4_ADDRESS}"
-      # If NetworkMangler command line interface exists and ready to mangle,
-      if command -v nmcli &> /dev/null && nmcli general status &> /dev/null; then
-        # Tell NetworkManagler to read our new sysconfig file
-        nmcli con load "${IFCFG_FILE}" > /dev/null
-      fi
-      # Show a warning that the user may need to restart
-      echo -e "  ${TICK} Set IP address to ${IPV4_ADDRESS%/*}
-      You may need to restart after the install is complete"
-    fi
-  # If all that fails,
-  else
-    # show an error and exit
-    echo -e "  ${INFO} Warning: Unable to locate configuration file to set static IPv4 address"
-    exit 1
-  fi
+        # show an error and exit
+        echo -e "  ${INFO} Warning: Unable to locate configuration file to set static IPv4 address"
+        exit 1
+    fi
 }
 
 # Check an IP address to see if it is a valid one
 valid_ip() {
-  # Local, named variables
-  local ip=${1}
-  local stat=1
-
-  # If the IP matches the format xxx.xxx.xxx.xxx,
-  if [[ "${ip}" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
-    # Save the old Interfal Field Separator in a variable
-    OIFS=$IFS
-    # and set the new one to a dot (period)
-    IFS='.'
-    # Put the IP into an array
-    ip=(${ip})
-    # Restore the IFS to what it was
-    IFS=${OIFS}
-    ## Evaluate each octet by checking if it's less than or equal to 255 (the max for each octet)
-    [[ "${ip[0]}" -le 255 && "${ip[1]}" -le 255 \
-    && "${ip[2]}" -le 255 && "${ip[3]}" -le 255 ]]
-    # Save the exit code
-    stat=$?
-  fi
-  # Return the exit code
-  return ${stat}
+    # Local, named variables
+    local ip=${1}
+    local stat=1
+
+    # If the IP matches the format xxx.xxx.xxx.xxx,
+    if [[ "${ip}" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
+        # Save the old Internal Field Separator in a variable
+        OIFS=$IFS
+        # and set the new one to a dot (period)
+        IFS='.'
+        # Put the IP into an array
+        ip=(${ip})
+        # Restore the IFS to what it was
+        IFS=${OIFS}
+        ## Evaluate each octet by checking if it's less than or equal to 255 (the max for each octet)
+        [[ "${ip[0]}" -le 255 && "${ip[1]}" -le 255 \
+        && "${ip[2]}" -le 255 && "${ip[3]}" -le 255 ]]
+        # Save the exit code
+        stat=$?
+    fi
+    # Return the exit code
+    return ${stat}
 }
 
 # A function to choose the upstream DNS provider(s)
 setDNS() {
-  # Local, named variables
-  local DNSSettingsCorrect
-
-  # In an array, list the available upstream providers
-  DNSChooseOptions=(Google ""
-      OpenDNS ""
-      Level3 ""
-      Norton ""
-      Comodo ""
-      DNSWatch ""
-      Quad9 ""
-      FamilyShield ""
-      Custom "")
-  # In a whiptail dialog, show the options
-  DNSchoices=$(whiptail --separate-output --menu "Select Upstream DNS Provider. To use your own, select Custom." ${r} ${c} 7 \
+    # Local, named variables
+    local DNSSettingsCorrect
+
+    # In an array, list the available upstream providers
+    DNSChooseOptions=(Google ""
+        OpenDNS ""
+        Level3 ""
+        Norton ""
+        Comodo ""
+        DNSWatch ""
+        Quad9 ""
+        FamilyShield ""
+        Cloudflare ""
+        Custom "")
+    # In a whiptail dialog, show the options
+    DNSchoices=$(whiptail --separate-output --menu "Select Upstream DNS Provider. To use your own, select Custom." ${r} ${c} 7 \
     "${DNSChooseOptions[@]}" 2>&1 >/dev/tty) || \
     # exit if Cancel is selected
     { echo -e "  ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; }
 
-  # Display the selection
-  echo -ne "  ${INFO} Using "
-  # Depending on the user's choice, set the GLOBAl variables to the IP of the respective provider
-  case ${DNSchoices} in
-    Google)
-      echo "Google DNS servers"
-      PIHOLE_DNS_1="8.8.8.8"
-      PIHOLE_DNS_2="8.8.4.4"
-      ;;
-    OpenDNS)
-      echo "OpenDNS servers"
-      PIHOLE_DNS_1="208.67.222.222"
-      PIHOLE_DNS_2="208.67.220.220"
-      ;;
-    Level3)
-      echo "Level3 servers"
-      PIHOLE_DNS_1="4.2.2.1"
-      PIHOLE_DNS_2="4.2.2.2"
-      ;;
-    Norton)
-      echo "Norton ConnectSafe servers"
-      PIHOLE_DNS_1="199.85.126.10"
-      PIHOLE_DNS_2="199.85.127.10"
-      ;;
-    Comodo)
-      echo "Comodo Secure servers"
-      PIHOLE_DNS_1="8.26.56.26"
-      PIHOLE_DNS_2="8.20.247.20"
-      ;;
-    DNSWatch)
-      echo "DNS.WATCH servers"
-      PIHOLE_DNS_1="84.200.69.80"
-      PIHOLE_DNS_2="84.200.70.40"
-      ;;
-    Quad9)
-      echo "Quad9 servers"
-      PIHOLE_DNS_1="9.9.9.9"
-      PIHOLE_DNS_2="149.112.112.112"
-      ;;
-    FamilyShield)
-      echo "FamilyShield servers"
-      PIHOLE_DNS_1="208.67.222.123"
-      PIHOLE_DNS_2="208.67.220.123"
-      ;;
-    Custom)
-      # Until the DNS settings are selected,
-      until [[ "${DNSSettingsCorrect}" = True ]]; do
-      #
-      strInvalid="Invalid"
-      # If the first
-      if [[ ! "${PIHOLE_DNS_1}" ]]; then
-        # and second upstream servers do not exist
-        if [[ ! "${PIHOLE_DNS_2}" ]]; then
-          #
-          prePopulate=""
-        # Otherwise,
-        else
-          #
-          prePopulate=", ${PIHOLE_DNS_2}"
-        fi
-      #
-      elif  [[ "${PIHOLE_DNS_1}" ]] && [[ ! "${PIHOLE_DNS_2}" ]]; then
-        #
-        prePopulate="${PIHOLE_DNS_1}"
-      #
-      elif [[ "${PIHOLE_DNS_1}" ]] && [[ "${PIHOLE_DNS_2}" ]]; then
-        #
-        prePopulate="${PIHOLE_DNS_1}, ${PIHOLE_DNS_2}"
-      fi
-
-      # Dialog for the user to enter custom upstream servers
-      piholeDNS=$(whiptail --backtitle "Specify Upstream DNS Provider(s)"  --inputbox "Enter your desired upstream DNS provider(s), seperated by a comma.\\n\\nFor example '8.8.8.8, 8.8.4.4'" ${r} ${c} "${prePopulate}" 3>&1 1>&2 2>&3) || \
-      { echo -e "  ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; }
-      #
-      PIHOLE_DNS_1=$(echo "${piholeDNS}" | sed 's/[, \t]\+/,/g' | awk -F, '{print$1}')
-      PIHOLE_DNS_2=$(echo "${piholeDNS}" | sed 's/[, \t]\+/,/g' | awk -F, '{print$2}')
-      # If the IP is valid,
-      if ! valid_ip "${PIHOLE_DNS_1}" || [[ ! "${PIHOLE_DNS_1}" ]]; then
-        # store it in the variable so we can use it
-        PIHOLE_DNS_1=${strInvalid}
-      fi
-      # Do the same for the secondary server
-      if ! valid_ip "${PIHOLE_DNS_2}" && [[ "${PIHOLE_DNS_2}" ]]; then
-        PIHOLE_DNS_2=${strInvalid}
-      fi
-      # If either of the DNS servers are invalid,
-      if [[ "${PIHOLE_DNS_1}" == "${strInvalid}" ]] || [[ "${PIHOLE_DNS_2}" == "${strInvalid}" ]]; then
-        # explain this to the user
-        whiptail --msgbox --backtitle "Invalid IP" --title "Invalid IP" "One or both entered IP addresses were invalid. Please try again.\\n\\n    DNS Server 1:   $PIHOLE_DNS_1\\n    DNS Server 2:   ${PIHOLE_DNS_2}" ${r} ${c}
-        # and set the variables back to nothing
-        if [[ "${PIHOLE_DNS_1}" == "${strInvalid}" ]]; then
-          PIHOLE_DNS_1=""
-        fi
-        if [[ "${PIHOLE_DNS_2}" == "${strInvalid}" ]]; then
-          PIHOLE_DNS_2=""
-        fi
-        # Since the settings will not work, stay in the loop
-        DNSSettingsCorrect=False
-      # Othwerise,
-      else
-        # Show the settings
-        if (whiptail --backtitle "Specify Upstream DNS Provider(s)" --title "Upstream DNS Provider(s)" --yesno "Are these settings correct?\\n    DNS Server 1:   $PIHOLE_DNS_1\\n    DNS Server 2:   ${PIHOLE_DNS_2}" ${r} ${c}); then
-        # and break from the loop since the servers are vaid
-        DNSSettingsCorrect=True
-      # Otherwise,
-      else
-        # If the settings are wrong, the loop continues
-        DNSSettingsCorrect=False
-        fi
-      fi
-      done
-      ;;
-  esac
+    # Display the selection
+    echo -ne "  ${INFO} Using "
+    # Depending on the user's choice, set the GLOBAl variables to the IP of the respective provider
+    case ${DNSchoices} in
+        Google)
+            echo "Google DNS servers"
+            PIHOLE_DNS_1="8.8.8.8"
+            PIHOLE_DNS_2="8.8.4.4"
+            ;;
+        OpenDNS)
+            echo "OpenDNS servers"
+            PIHOLE_DNS_1="208.67.222.222"
+            PIHOLE_DNS_2="208.67.220.220"
+            ;;
+        Level3)
+            echo "Level3 servers"
+            PIHOLE_DNS_1="4.2.2.1"
+            PIHOLE_DNS_2="4.2.2.2"
+            ;;
+        Norton)
+            echo "Norton ConnectSafe servers"
+            PIHOLE_DNS_1="199.85.126.10"
+            PIHOLE_DNS_2="199.85.127.10"
+            ;;
+        Comodo)
+            echo "Comodo Secure servers"
+            PIHOLE_DNS_1="8.26.56.26"
+            PIHOLE_DNS_2="8.20.247.20"
+            ;;
+        DNSWatch)
+            echo "DNS.WATCH servers"
+            PIHOLE_DNS_1="84.200.69.80"
+            PIHOLE_DNS_2="84.200.70.40"
+            ;;
+        Quad9)
+            echo "Quad9 servers"
+            PIHOLE_DNS_1="9.9.9.9"
+            PIHOLE_DNS_2="149.112.112.112"
+            ;;
+        FamilyShield)
+            echo "FamilyShield servers"
+            PIHOLE_DNS_1="208.67.222.123"
+            PIHOLE_DNS_2="208.67.220.123"
+            ;;
+        Cloudflare)
+            echo "Cloudflare servers"
+            PIHOLE_DNS_1="1.1.1.1"
+            PIHOLE_DNS_2="1.0.0.1"
+            ;;
+        Custom)
+            # Until the DNS settings are selected,
+            until [[ "${DNSSettingsCorrect}" = True ]]; do
+                #
+                strInvalid="Invalid"
+                # If the first
+                if [[ ! "${PIHOLE_DNS_1}" ]]; then
+                    # and second upstream servers do not exist
+                    if [[ ! "${PIHOLE_DNS_2}" ]]; then
+                        prePopulate=""
+                    # Otherwise,
+                    else
+                        prePopulate=", ${PIHOLE_DNS_2}"
+                    fi
+                elif  [[ "${PIHOLE_DNS_1}" ]] && [[ ! "${PIHOLE_DNS_2}" ]]; then
+                    prePopulate="${PIHOLE_DNS_1}"
+                elif [[ "${PIHOLE_DNS_1}" ]] && [[ "${PIHOLE_DNS_2}" ]]; then
+                    prePopulate="${PIHOLE_DNS_1}, ${PIHOLE_DNS_2}"
+                fi
+
+                # Dialog for the user to enter custom upstream servers
+                piholeDNS=$(whiptail --backtitle "Specify Upstream DNS Provider(s)"  --inputbox "Enter your desired upstream DNS provider(s), separated by a comma.\\n\\nFor example '8.8.8.8, 8.8.4.4'" ${r} ${c} "${prePopulate}" 3>&1 1>&2 2>&3) || \
+                { echo -e "  ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; }
+                #
+                PIHOLE_DNS_1=$(echo "${piholeDNS}" | sed 's/[, \t]\+/,/g' | awk -F, '{print$1}')
+                PIHOLE_DNS_2=$(echo "${piholeDNS}" | sed 's/[, \t]\+/,/g' | awk -F, '{print$2}')
+                # If the IP is valid,
+                if ! valid_ip "${PIHOLE_DNS_1}" || [[ ! "${PIHOLE_DNS_1}" ]]; then
+                    # store it in the variable so we can use it
+                    PIHOLE_DNS_1=${strInvalid}
+                fi
+                # Do the same for the secondary server
+                if ! valid_ip "${PIHOLE_DNS_2}" && [[ "${PIHOLE_DNS_2}" ]]; then
+                    PIHOLE_DNS_2=${strInvalid}
+                fi
+                # If either of the DNS servers are invalid,
+                if [[ "${PIHOLE_DNS_1}" == "${strInvalid}" ]] || [[ "${PIHOLE_DNS_2}" == "${strInvalid}" ]]; then
+                    # explain this to the user
+                    whiptail --msgbox --backtitle "Invalid IP" --title "Invalid IP" "One or both entered IP addresses were invalid. Please try again.\\n\\n    DNS Server 1:   $PIHOLE_DNS_1\\n    DNS Server 2:   ${PIHOLE_DNS_2}" ${r} ${c}
+                    # and set the variables back to nothing
+                    if [[ "${PIHOLE_DNS_1}" == "${strInvalid}" ]]; then
+                        PIHOLE_DNS_1=""
+                    fi
+                    if [[ "${PIHOLE_DNS_2}" == "${strInvalid}" ]]; then
+                        PIHOLE_DNS_2=""
+                    fi
+                # Since the settings will not work, stay in the loop
+                DNSSettingsCorrect=False
+                # Otherwise,
+                else
+                    # Show the settings
+                    if (whiptail --backtitle "Specify Upstream DNS Provider(s)" --title "Upstream DNS Provider(s)" --yesno "Are these settings correct?\\n    DNS Server 1:   $PIHOLE_DNS_1\\n    DNS Server 2:   ${PIHOLE_DNS_2}" ${r} ${c}); then
+                    # and break from the loop since the servers are valid
+                    DNSSettingsCorrect=True
+                    # Otherwise,
+                    else
+                        # If the settings are wrong, the loop continues
+                        DNSSettingsCorrect=False
+                    fi
+                fi
+            done
+            ;;
+    esac
 }
 
 # Allow the user to enable/disable logging
 setLogging() {
-  # Local, named variables
-  local LogToggleCommand
-  local LogChooseOptions
-  local LogChoices
-
-  # Ask if the user wants to log queries
-  LogToggleCommand=(whiptail --separate-output --radiolist "Do you want to log queries?\\n (Disabling will render graphs on the Admin page useless):" ${r} ${c} 6)
-  # The default selection is on
-  LogChooseOptions=("On (Recommended)" "" on
-      Off "" off)
-  # Get the user's choice
-  LogChoices=$("${LogToggleCommand[@]}" "${LogChooseOptions[@]}" 2>&1 >/dev/tty) || (echo -e "  ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}" && exit 1)
+    # Local, named variables
+    local LogToggleCommand
+    local LogChooseOptions
+    local LogChoices
+
+    # Ask if the user wants to log queries
+    LogToggleCommand=(whiptail --separate-output --radiolist "Do you want to log queries?" "${r}" "${c}" 6)
+    # The default selection is on
+    LogChooseOptions=("On (Recommended)" "" on
+        Off "" off)
+    # Get the user's choice
+    LogChoices=$("${LogToggleCommand[@]}" "${LogChooseOptions[@]}" 2>&1 >/dev/tty) || (echo -e "  ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}" && exit 1)
     case ${LogChoices} in
-      # If it's on
-      "On (Recommended)")
-        echo -e "  ${INFO} Logging On."
-        # Set the GLOBAL variable to true so we know what they selected
-        QUERY_LOGGING=true
-        ;;
-      # Othwerise, it's off,
-      Off)
-        echo -e "  ${INFO} Logging Off."
-        # So set it to false
-        QUERY_LOGGING=false
-        ;;
+        # If it's on
+        "On (Recommended)")
+            echo -e "  ${INFO} Logging On."
+            # Set the GLOBAL variable to true so we know what they selected
+            QUERY_LOGGING=true
+            ;;
+        # Otherwise, it's off,
+        Off)
+            echo -e "  ${INFO} Logging Off."
+            # So set it to false
+            QUERY_LOGGING=false
+            ;;
     esac
 }
 
 # Function to ask the user if they want to install the dashboard
 setAdminFlag() {
-  # Local, named variables
-  local WebToggleCommand
-  local WebChooseOptions
-  local WebChoices
-
-  # Similar to the logging function, ask what the user wants
-  WebToggleCommand=(whiptail --separate-output --radiolist "Do you wish to install the web admin interface?" ${r} ${c} 6)
-  # with the default being enabled
-  WebChooseOptions=("On (Recommended)" "" on
-      Off "" off)
-  WebChoices=$("${WebToggleCommand[@]}" "${WebChooseOptions[@]}" 2>&1 >/dev/tty) || (echo -e "  ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}" && exit 1)
+    # Local, named variables
+    local WebToggleCommand
+    local WebChooseOptions
+    local WebChoices
+
+    # Similar to the logging function, ask what the user wants
+    WebToggleCommand=(whiptail --separate-output --radiolist "Do you wish to install the web admin interface?" ${r} ${c} 6)
+    # with the default being enabled
+    WebChooseOptions=("On (Recommended)" "" on
+        Off "" off)
+    WebChoices=$("${WebToggleCommand[@]}" "${WebChooseOptions[@]}" 2>&1 >/dev/tty) || (echo -e "  ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}" && exit 1)
     # Depending on their choice
     case ${WebChoices} in
-      "On (Recommended)")
-        echo -e "  ${INFO} Web Interface On"
-        # Set it to true
-        INSTALL_WEB=true
-        ;;
-      Off)
-        echo -e "  ${INFO} Web Interface Off"
-        # or false
-        INSTALL_WEB=false
-        ;;
+        "On (Recommended)")
+            echo -e "  ${INFO} Web Interface On"
+            # Set it to true
+            INSTALL_WEB_INTERFACE=true
+            ;;
+        Off)
+            echo -e "  ${INFO} Web Interface Off"
+            # or false
+            INSTALL_WEB_INTERFACE=false
+            ;;
     esac
+
+    # Request user to install web server, if --disable-install-webserver has not been used (INSTALL_WEB_SERVER=true is default).
+    if [[ "${INSTALL_WEB_SERVER}" == true ]]; then
+        WebToggleCommand=(whiptail --separate-output --radiolist "Do you wish to install the web server (lighttpd)?\\n\\nNB: If you disable this, and, do not have an existing webserver installed, the web interface will not function." "${r}" "${c}" 6)
+        # with the default being enabled
+        WebChooseOptions=("On (Recommended)" "" on
+            Off "" off)
+        WebChoices=$("${WebToggleCommand[@]}" "${WebChooseOptions[@]}" 2>&1 >/dev/tty) || (echo -e "  ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}" && exit 1)
+        # Depending on their choice
+        case ${WebChoices} in
+            "On (Recommended)")
+                echo -e "  ${INFO} Web Server On"
+                # set it to true, as clearly seen below.
+                INSTALL_WEB_SERVER=true
+                ;;
+            Off)
+                echo -e "  ${INFO} Web Server Off"
+                # or false
+                INSTALL_WEB_SERVER=false
+                ;;
+        esac
+    fi
+}
+
+# A function to display a list of example blocklists for users to select
+chooseBlocklists() {
+    # Back up any existing adlist file, on the off chance that it exists. Useful in case of a reconfigure.
+    if [[ -f "${adlistFile}" ]]; then
+        mv "${adlistFile}" "${adlistFile}.old"
+    fi
+    # Let user select (or not) blocklists via a checklist
+    cmd=(whiptail --separate-output --checklist "Pi-hole relies on third party lists in order to block ads.\\n\\nYou can use the suggestions below, and/or add your own after installation\\n\\nTo deselect any list, use the arrow keys and spacebar" "${r}" "${c}" 7)
+    # In an array, show the options available (all off by default):
+    options=(StevenBlack "StevenBlack's Unified Hosts List" on
+        MalwareDom "MalwareDomains" on
+        Cameleon "Cameleon" on
+        ZeusTracker "ZeusTracker" on
+        DisconTrack "Disconnect.me Tracking" on
+        DisconAd "Disconnect.me Ads" on
+        HostsFile "Hosts-file.net Ads" on)
+
+    # In a variable, show the choices available; exit if Cancel is selected
+    choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) || { echo -e "  ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; rm "${adlistFile}" ;exit 1; }
+    # For each choice available,
+    for choice in ${choices}
+    do
+        # Set the values to true
+        case ${choice} in
+            StevenBlack  )  echo "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts" >> "${adlistFile}";;
+            MalwareDom   )  echo "https://mirror1.malwaredomains.com/files/justdomains" >> "${adlistFile}";;
+            Cameleon     )  echo "http://sysctl.org/cameleon/hosts" >> "${adlistFile}";;
+            ZeusTracker  )  echo "https://zeustracker.abuse.ch/blocklist.php?download=domainblocklist" >> "${adlistFile}";;
+            DisconTrack  )  echo "https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt" >> "${adlistFile}";;
+            DisconAd     )  echo "https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt" >> "${adlistFile}";;
+            HostsFile    )  echo "https://hosts-file.net/ad_servers.txt" >> "${adlistFile}";;
+        esac
+    done
 }
 
 # Check if /etc/dnsmasq.conf is from pi-hole.  If so replace with an original and install new in .d directory
 version_check_dnsmasq() {
-  # Local, named variables
-  local dnsmasq_conf="/etc/dnsmasq.conf"
-  local dnsmasq_conf_orig="/etc/dnsmasq.conf.orig"
-  local dnsmasq_pihole_id_string="addn-hosts=/etc/pihole/gravity.list"
-  local dnsmasq_original_config="${PI_HOLE_LOCAL_REPO}/advanced/dnsmasq.conf.original"
-  local dnsmasq_pihole_01_snippet="${PI_HOLE_LOCAL_REPO}/advanced/01-pihole.conf"
-  local dnsmasq_pihole_01_location="/etc/dnsmasq.d/01-pihole.conf"
-
-  # If the dnsmasq config file exists
-  if [[ -f "${dnsmasq_conf}" ]]; then
-    echo -ne "  ${INFO} Existing dnsmasq.conf found..."
-    # If gravity.list is found within this file, we presume it's from older versions on Pi-hole,
-    if grep -q ${dnsmasq_pihole_id_string} ${dnsmasq_conf}; then
-      echo " it is from a previous Pi-hole install."
-      echo -ne "  ${INFO} Backing up dnsmasq.conf to dnsmasq.conf.orig..."
-      # so backup the original file
-      mv -f ${dnsmasq_conf} ${dnsmasq_conf_orig}
-      echo -e "${OVER}  ${TICK} Backing up dnsmasq.conf to dnsmasq.conf.orig..."
-      echo -ne "  ${INFO} Restoring default dnsmasq.conf..."
-      # and replace it with the default
-      cp ${dnsmasq_original_config} ${dnsmasq_conf}
-      echo -e "${OVER}  ${TICK} Restoring default dnsmasq.conf..."
-    # Otherwise,
+    # Local, named variables
+    local dnsmasq_conf="/etc/dnsmasq.conf"
+    local dnsmasq_conf_orig="/etc/dnsmasq.conf.orig"
+    local dnsmasq_pihole_id_string="addn-hosts=/etc/pihole/gravity.list"
+    local dnsmasq_original_config="${PI_HOLE_LOCAL_REPO}/advanced/dnsmasq.conf.original"
+    local dnsmasq_pihole_01_snippet="${PI_HOLE_LOCAL_REPO}/advanced/01-pihole.conf"
+    local dnsmasq_pihole_01_location="/etc/dnsmasq.d/01-pihole.conf"
+
+    # If the dnsmasq config file exists
+    if [[ -f "${dnsmasq_conf}" ]]; then
+        echo -ne "  ${INFO} Existing dnsmasq.conf found..."
+        # If gravity.list is found within this file, we presume it's from older versions on Pi-hole,
+        if grep -q ${dnsmasq_pihole_id_string} ${dnsmasq_conf}; then
+            echo " it is from a previous Pi-hole install."
+            echo -ne "  ${INFO} Backing up dnsmasq.conf to dnsmasq.conf.orig..."
+            # so backup the original file
+            mv -f ${dnsmasq_conf} ${dnsmasq_conf_orig}
+            echo -e "${OVER}  ${TICK} Backing up dnsmasq.conf to dnsmasq.conf.orig..."
+            echo -ne "  ${INFO} Restoring default dnsmasq.conf..."
+            # and replace it with the default
+            cp ${dnsmasq_original_config} ${dnsmasq_conf}
+            echo -e "${OVER}  ${TICK} Restoring default dnsmasq.conf..."
+        # Otherwise,
+        else
+        # Don't to anything
+        echo " it is not a Pi-hole file, leaving alone!"
+        fi
     else
-      # Don't to anything
-      echo " it is not a Pi-hole file, leaving alone!"
-    fi
-  else
-    # If a file cannot be found,
-    echo -ne "  ${INFO} No dnsmasq.conf found... restoring default dnsmasq.conf..."
-    # restore the default one
-    cp ${dnsmasq_original_config} ${dnsmasq_conf}
-    echo -e "${OVER}  ${TICK} No dnsmasq.conf found... restoring default dnsmasq.conf..."
-  fi
-
-  echo -en "  ${INFO} Copying 01-pihole.conf to /etc/dnsmasq.d/01-pihole.conf..."
-  # Copy the new Pi-hole DNS config file into the dnsmasq.d directory
-  cp ${dnsmasq_pihole_01_snippet} ${dnsmasq_pihole_01_location}
-  echo -e "${OVER}  ${TICK} Copying 01-pihole.conf to /etc/dnsmasq.d/01-pihole.conf"
-  # Replace our placeholder values with the GLOBAL DNS variables that we populated earlier
-  # First, swap in the interface to listen on
-  sed -i "s/@INT@/$PIHOLE_INTERFACE/" ${dnsmasq_pihole_01_location}
-  if [[ "${PIHOLE_DNS_1}" != "" ]]; then
-    # Then swap in the primary DNS server
-    sed -i "s/@DNS1@/$PIHOLE_DNS_1/" ${dnsmasq_pihole_01_location}
-  else
-    #
-    sed -i '/^server=@DNS1@/d' ${dnsmasq_pihole_01_location}
-  fi
-  if [[ "${PIHOLE_DNS_2}" != "" ]]; then
-    # Then swap in the primary DNS server
-    sed -i "s/@DNS2@/$PIHOLE_DNS_2/" ${dnsmasq_pihole_01_location}
-  else
-    #
-    sed -i '/^server=@DNS2@/d' ${dnsmasq_pihole_01_location}
-  fi
+        # If a file cannot be found,
+        echo -ne "  ${INFO} No dnsmasq.conf found... restoring default dnsmasq.conf..."
+        # restore the default one
+        cp ${dnsmasq_original_config} ${dnsmasq_conf}
+        echo -e "${OVER}  ${TICK} No dnsmasq.conf found... restoring default dnsmasq.conf..."
+    fi
 
-  #
-  sed -i 's/^#conf-dir=\/etc\/dnsmasq.d$/conf-dir=\/etc\/dnsmasq.d/' ${dnsmasq_conf}
+    echo -en "  ${INFO} Copying 01-pihole.conf to /etc/dnsmasq.d/01-pihole.conf..."
+    # Check to see if dnsmasq directory exists (it may not due to being a fresh install and dnsmasq no longer being a dependency)
+    if [[ ! -d "/etc/dnsmasq.d"  ]];then
+        mkdir "/etc/dnsmasq.d"
+    fi
+    # Copy the new Pi-hole DNS config file into the dnsmasq.d directory
+    cp ${dnsmasq_pihole_01_snippet} ${dnsmasq_pihole_01_location}
+    echo -e "${OVER}  ${TICK} Copying 01-pihole.conf to /etc/dnsmasq.d/01-pihole.conf"
+    # Replace our placeholder values with the GLOBAL DNS variables that we populated earlier
+    # First, swap in the interface to listen on
+    sed -i "s/@INT@/$PIHOLE_INTERFACE/" ${dnsmasq_pihole_01_location}
+    if [[ "${PIHOLE_DNS_1}" != "" ]]; then
+        # Then swap in the primary DNS server
+        sed -i "s/@DNS1@/$PIHOLE_DNS_1/" ${dnsmasq_pihole_01_location}
+    else
+        #
+        sed -i '/^server=@DNS1@/d' ${dnsmasq_pihole_01_location}
+    fi
+    if [[ "${PIHOLE_DNS_2}" != "" ]]; then
+        # Then swap in the primary DNS server
+        sed -i "s/@DNS2@/$PIHOLE_DNS_2/" ${dnsmasq_pihole_01_location}
+    else
+        #
+        sed -i '/^server=@DNS2@/d' ${dnsmasq_pihole_01_location}
+    fi
 
-  # If the user does not want to enable logging,
-  if [[ "${QUERY_LOGGING}" == false ]] ; then
+    #
+    sed -i 's/^#conf-dir=\/etc\/dnsmasq.d$/conf-dir=\/etc\/dnsmasq.d/' ${dnsmasq_conf}
+
+    # If the user does not want to enable logging,
+    if [[ "${QUERY_LOGGING}" == false ]] ; then
         # Disable it by commenting out the directive in the DNS config file
         sed -i 's/^log-queries/#log-queries/' ${dnsmasq_pihole_01_location}
     # Otherwise,
@@ -1027,635 +1179,685 @@ version_check_dnsmasq() {
 
 # Clean an existing installation to prepare for upgrade/reinstall
 clean_existing() {
-  # Local, named variables
-  # ${1} Directory to clean
-  local clean_directory="${1}"
-  # Make ${2} the new one?
-  shift
-  # ${2} Array of files to remove
-  local old_files=( "$@" )
-
-  # For each script found in the old files array
-  for script in "${old_files[@]}"; do
-    # Remove them
-    rm -f "${clean_directory}/${script}.sh"
-  done
+    # Local, named variables
+    # ${1} Directory to clean
+    local clean_directory="${1}"
+    # Make ${2} the new one?
+    shift
+    # ${2} Array of files to remove
+    local old_files=( "$@" )
+
+    # For each script found in the old files array
+    for script in "${old_files[@]}"; do
+        # Remove them
+        rm -f "${clean_directory}/${script}.sh"
+    done
 }
 
 # Install the scripts from repository to their various locations
 installScripts() {
-  # Local, named variables
-  local str="Installing scripts from ${PI_HOLE_LOCAL_REPO}"
-  echo -ne "  ${INFO} ${str}..."
-
-  # Clear out script files from Pi-hole scripts directory.
-  clean_existing "${PI_HOLE_INSTALL_DIR}" "${PI_HOLE_FILES[@]}"
-
-  # Install files from local core repository
-  if is_repo "${PI_HOLE_LOCAL_REPO}"; then
-    # move into the directory
-    cd "${PI_HOLE_LOCAL_REPO}"
-    # Install the scripts by:
-    #  -o setting the owner to the user
-    #  -Dm755 create all leading components of destiantion except the last, then copy the source to the destiantion and setting the permissions to 755
-    #
-    # This first one is the directory
-    install -o "${USER}" -Dm755 -d "${PI_HOLE_INSTALL_DIR}"
-    # The rest are the scripts Pi-hole needs
-    install -o "${USER}" -Dm755 -t "${PI_HOLE_INSTALL_DIR}" gravity.sh
-    install -o "${USER}" -Dm755 -t "${PI_HOLE_INSTALL_DIR}" ./advanced/Scripts/*.sh
-    install -o "${USER}" -Dm755 -t "${PI_HOLE_INSTALL_DIR}" ./automated\ install/uninstall.sh
-    install -o "${USER}" -Dm755 -t "${PI_HOLE_INSTALL_DIR}" ./advanced/Scripts/COL_TABLE
-    install -o "${USER}" -Dm755 -t /usr/local/bin/ pihole
-    install -Dm644 ./advanced/bash-completion/pihole /etc/bash_completion.d/pihole
-    echo -e "${OVER}  ${TICK} ${str}"
- # Otherwise,
-  else
-    # Show an error and exit
-    echo -e "${OVER}  ${CROSS} ${str}
-  ${COL_LIGHT_RED}Error: Local repo ${PI_HOLE_LOCAL_REPO} not found, exiting installer${COL_NC}"
-    exit 1
-  fi
+    # Local, named variables
+    local str="Installing scripts from ${PI_HOLE_LOCAL_REPO}"
+    echo -ne "  ${INFO} ${str}..."
+
+    # Clear out script files from Pi-hole scripts directory.
+    clean_existing "${PI_HOLE_INSTALL_DIR}" "${PI_HOLE_FILES[@]}"
+
+    # Install files from local core repository
+    if is_repo "${PI_HOLE_LOCAL_REPO}"; then
+        # move into the directory
+        cd "${PI_HOLE_LOCAL_REPO}"
+        # Install the scripts by:
+        #  -o setting the owner to the user
+        #  -Dm755 create all leading components of destination except the last, then copy the source to the destination and setting the permissions to 755
+        #
+        # This first one is the directory
+        install -o "${USER}" -Dm755 -d "${PI_HOLE_INSTALL_DIR}"
+        # The rest are the scripts Pi-hole needs
+        install -o "${USER}" -Dm755 -t "${PI_HOLE_INSTALL_DIR}" gravity.sh
+        install -o "${USER}" -Dm755 -t "${PI_HOLE_INSTALL_DIR}" ./advanced/Scripts/*.sh
+        install -o "${USER}" -Dm755 -t "${PI_HOLE_INSTALL_DIR}" ./automated\ install/uninstall.sh
+        install -o "${USER}" -Dm755 -t "${PI_HOLE_INSTALL_DIR}" ./advanced/Scripts/COL_TABLE
+        install -o "${USER}" -Dm755 -t /usr/local/bin/ pihole
+        install -Dm644 ./advanced/bash-completion/pihole /etc/bash_completion.d/pihole
+        echo -e "${OVER}  ${TICK} ${str}"
+
+    # Otherwise,
+    else
+        # Show an error and exit
+        echo -e "${OVER}  ${CROSS} ${str}
+        ${COL_LIGHT_RED}Error: Local repo ${PI_HOLE_LOCAL_REPO} not found, exiting installer${COL_NC}"
+        return 1
+    fi
 }
 
 # Install the configs from PI_HOLE_LOCAL_REPO to their various locations
 installConfigs() {
-  echo ""
-  echo -e "  ${INFO} Installing configs from ${PI_HOLE_LOCAL_REPO}..."
-  # Make sure Pi-hole's config files are in place
-  version_check_dnsmasq
-
-  # If the user chose to install the dashboard,
-  if [[ "${INSTALL_WEB}" == true ]]; then
-    # and if the Web server conf directory does not exist,
-    if [[ ! -d "/etc/lighttpd" ]]; then
-      # make it
-      mkdir /etc/lighttpd
-      # and set the owners
-      chown "${USER}":root /etc/lighttpd
-    # Otherwise, if the config file already exists
-    elif [[ -f "/etc/lighttpd/lighttpd.conf" ]]; then
-      # back up the original
-      mv /etc/lighttpd/lighttpd.conf /etc/lighttpd/lighttpd.conf.orig
-    fi
-    # and copy in the config file Pi-hole needs
-    cp ${PI_HOLE_LOCAL_REPO}/advanced/${LIGHTTPD_CFG} /etc/lighttpd/lighttpd.conf
-    # if there is a custom block page in the html/pihole directory, replace 404 handler in lighttpd config
-    if [[ -f "/var/www/html/pihole/custom.php" ]]; then
-      sed -i 's/^\(server\.error-handler-404\s*=\s*\).*$/\1"pihole\/custom\.php"/' /etc/lighttpd/lighttpd.conf
-    fi
-    # Make the directories if they do not exist and set the owners
-    mkdir -p /var/run/lighttpd
-    chown ${LIGHTTPD_USER}:${LIGHTTPD_GROUP} /var/run/lighttpd
-    mkdir -p /var/cache/lighttpd/compress
-    chown ${LIGHTTPD_USER}:${LIGHTTPD_GROUP} /var/cache/lighttpd/compress
-    mkdir -p /var/cache/lighttpd/uploads
-    chown ${LIGHTTPD_USER}:${LIGHTTPD_GROUP} /var/cache/lighttpd/uploads
-  fi
+    echo ""
+    echo -e "  ${INFO} Installing configs from ${PI_HOLE_LOCAL_REPO}..."
+    # Make sure Pi-hole's config files are in place
+    version_check_dnsmasq
+    # Install empty file if it does not exist
+    if [[ ! -f "${PI_HOLE_CONFIG_DIR}/pihole-FTL.conf" ]]; then
+        if ! install -o pihole -g pihole -m 664 /dev/null "${PI_HOLE_CONFIG_DIR}/pihole-FTL.conf" &>/dev/nul; then
+            echo -e "  ${COL_LIGHT_RED}Error: Unable to initialize configuration file ${PI_HOLE_CONFIG_DIR}/pihole-FTL.conf"
+            return 1
+        fi
+    fi
+    # Install an empty regex file
+    if [[ ! -f "${regexFile}" ]]; then
+        # Let PHP edit the regex file, if installed
+        install -o pihole -g "${LIGHTTPD_GROUP:-pihole}" -m 664 /dev/null "${regexFile}"
+    fi
+    # If the user chose to install the dashboard,
+    if [[ "${INSTALL_WEB_SERVER}" == true ]]; then
+        # and if the Web server conf directory does not exist,
+        if [[ ! -d "/etc/lighttpd" ]]; then
+            # make it
+            mkdir /etc/lighttpd
+            # and set the owners
+            chown "${USER}":root /etc/lighttpd
+        # Otherwise, if the config file already exists
+        elif [[ -f "/etc/lighttpd/lighttpd.conf" ]]; then
+            # back up the original
+            mv /etc/lighttpd/lighttpd.conf /etc/lighttpd/lighttpd.conf.orig
+        fi
+        # and copy in the config file Pi-hole needs
+        cp ${PI_HOLE_LOCAL_REPO}/advanced/${LIGHTTPD_CFG} /etc/lighttpd/lighttpd.conf
+        # if there is a custom block page in the html/pihole directory, replace 404 handler in lighttpd config
+        if [[ -f "/var/www/html/pihole/custom.php" ]]; then
+            sed -i 's/^\(server\.error-handler-404\s*=\s*\).*$/\1"pihole\/custom\.php"/' /etc/lighttpd/lighttpd.conf
+        fi
+        # Make the directories if they do not exist and set the owners
+        mkdir -p /var/run/lighttpd
+        chown ${LIGHTTPD_USER}:${LIGHTTPD_GROUP} /var/run/lighttpd
+        mkdir -p /var/cache/lighttpd/compress
+        chown ${LIGHTTPD_USER}:${LIGHTTPD_GROUP} /var/cache/lighttpd/compress
+        mkdir -p /var/cache/lighttpd/uploads
+        chown ${LIGHTTPD_USER}:${LIGHTTPD_GROUP} /var/cache/lighttpd/uploads
+    fi
+}
+
+install_manpage() {
+    # Copy Pi-hole man pages and call mandb to update man page database
+    # Default location for man files for /usr/local/bin is /usr/local/share/man
+    # on lightweight systems may not be present, so check before copying.
+    echo -en "  ${INFO} Testing man page installation"
+    if ! command -v mandb &>/dev/null; then
+        # if mandb is not present, no manpage support
+        echo -e "${OVER}  ${INFO} man not installed"
+        return
+    elif [[ ! -d "/usr/local/share/man" ]]; then
+        # appropriate directory for Pi-hole's man page is not present
+        echo -e "${OVER}  ${INFO} man pages not installed"
+        return
+    fi
+    if [[ ! -d "/usr/local/share/man/man8" ]]; then
+        # if not present, create man8 directory
+        mkdir /usr/local/share/man/man8
+    fi
+    if [[ ! -d "/usr/local/share/man/man5" ]]; then
+        # if not present, create man8 directory
+        mkdir /usr/local/share/man/man5
+    fi
+    # Testing complete, copy the files & update the man db
+    cp ${PI_HOLE_LOCAL_REPO}/manpages/pihole.8 /usr/local/share/man/man8/pihole.8
+    cp ${PI_HOLE_LOCAL_REPO}/manpages/pihole-FTL.8 /usr/local/share/man/man8/pihole-FTL.8
+    cp ${PI_HOLE_LOCAL_REPO}/manpages/pihole-FTL.conf.5 /usr/local/share/man/man5/pihole-FTL.conf.5
+    if mandb -q &>/dev/null; then
+        # Updated successfully
+        echo -e "${OVER}  ${TICK} man pages installed and database updated"
+        return
+    else
+        # Something is wrong with the system's man installation, clean up
+        # our files, (leave everything how we found it).
+        rm /usr/local/share/man/man8/pihole.8 /usr/local/share/man/man8/pihole-FTL.8 /usr/local/share/man/man5/pihole-FTL.conf.5
+        echo -e "${OVER}  ${CROSS} man page db not updated, man pages not installed"
+    fi
 }
 
 stop_service() {
-  # Stop service passed in as argument.
-  # Can softfail, as process may not be installed when this is called
-  local str="Stopping ${1} service"
-  echo ""
-  echo -ne "  ${INFO} ${str}..."
-  if command -v systemctl &> /dev/null; then
-    systemctl stop "${1}" &> /dev/null || true
-  else
-    service "${1}" stop &> /dev/null || true
-  fi
-  echo -e "${OVER}  ${TICK} ${str}..."
+    # Stop service passed in as argument.
+    # Can softfail, as process may not be installed when this is called
+    local str="Stopping ${1} service"
+    echo -ne "  ${INFO} ${str}..."
+    if command -v systemctl &> /dev/null; then
+        systemctl stop "${1}" &> /dev/null || true
+    else
+        service "${1}" stop &> /dev/null || true
+    fi
+    echo -e "${OVER}  ${TICK} ${str}..."
 }
 
 # Start/Restart service passed in as argument
 start_service() {
-  # Local, named variables
-  local str="Starting ${1} service"
-  echo ""
-  echo -ne "  ${INFO} ${str}..."
-  # If systemctl exists,
-  if command -v systemctl &> /dev/null; then
-    # use that to restart the service
-    systemctl restart "${1}" &> /dev/null
-  # Otherwise,
-  else
-    # fall back to the service command
-    service "${1}" restart &> /dev/null
-  fi
-  echo -e "${OVER}  ${TICK} ${str}"
+    # Local, named variables
+    local str="Starting ${1} service"
+    echo -ne "  ${INFO} ${str}..."
+    # If systemctl exists,
+    if command -v systemctl &> /dev/null; then
+        # use that to restart the service
+        systemctl restart "${1}" &> /dev/null
+    # Otherwise,
+    else
+        # fall back to the service command
+        service "${1}" restart &> /dev/null
+    fi
+    echo -e "${OVER}  ${TICK} ${str}"
 }
 
 # Enable service so that it will start with next reboot
 enable_service() {
-  # Local, named variables
-  local str="Enabling ${1} service to start on reboot"
-  echo ""
-  echo -ne "  ${INFO} ${str}..."
-  # If systemctl exists,
-  if command -v systemctl &> /dev/null; then
-    # use that to enable the service
-    systemctl enable "${1}" &> /dev/null
-  # Othwerwise,
-  else
-    # use update-rc.d to accomplish this
-    update-rc.d "${1}" defaults &> /dev/null
-  fi
-  echo -e "${OVER}  ${TICK} ${str}"
+    # Local, named variables
+    local str="Enabling ${1} service to start on reboot"
+    echo -ne "  ${INFO} ${str}..."
+    # If systemctl exists,
+    if command -v systemctl &> /dev/null; then
+        # use that to enable the service
+        systemctl enable "${1}" &> /dev/null
+    # Otherwise,
+    else
+        # use update-rc.d to accomplish this
+        update-rc.d "${1}" defaults &> /dev/null
+    fi
+    echo -e "${OVER}  ${TICK} ${str}"
 }
 
-update_package_cache() {
-  # Running apt-get update/upgrade with minimal output can cause some issues with
-  # requiring user input (e.g password for phpmyadmin see #218)
-
-  # Update package cache on apt based OSes. Do this every time since
-  # it's quick and packages can be updated at any time.
-
-  # Local, named variables
-  local str="Update local cache of available packages"
-  echo ""
-  echo -ne "  ${INFO} ${str}..."
-  # Create a command from the package cache variable
-  if eval "${UPDATE_PKG_CACHE}" &> /dev/null; then
+# Disable service so that it will not with next reboot
+disable_service() {
+    # Local, named variables
+    local str="Disabling ${1} service"
+    echo -ne "  ${INFO} ${str}..."
+    # If systemctl exists,
+    if command -v systemctl &> /dev/null; then
+        # use that to disable the service
+        systemctl disable "${1}" &> /dev/null
+    # Otherwise,
+    else
+        # use update-rc.d to accomplish this
+        update-rc.d "${1}" disable &> /dev/null
+    fi
     echo -e "${OVER}  ${TICK} ${str}"
-  # Otherwise,
-  else
-    # show an error and exit
-    echo -e "${OVER}  ${CROSS} ${str}"
-    echo -ne "  ${COL_LIGHT_RED}Error: Unable to update package cache. Please try \"${UPDATE_PKG_CACHE}\"${COL_NC}"
-    return 1
-  fi
+}
+
+check_service_active() {
+    # If systemctl exists,
+    if command -v systemctl &> /dev/null; then
+        # use that to check the status of the service
+        systemctl is-enabled "${1}" &> /dev/null
+    # Otherwise,
+    else
+        # fall back to service command
+        service "${1}" status &> /dev/null
+    fi
+}
+
+# Systemd-resolved's DNSStubListener and dnsmasq can't share port 53.
+disable_resolved_stublistener() {
+    echo -en "  ${INFO} Testing if systemd-resolved is enabled"
+    # Check if Systemd-resolved's DNSStubListener is enabled and active on port 53
+    if check_service_active "systemd-resolved"; then
+        # Check if DNSStubListener is enabled
+        echo -en "  ${OVER}  ${INFO} Testing if systemd-resolved DNSStub-Listener is active"
+        if ( grep -E '#?DNSStubListener=yes' /etc/systemd/resolved.conf &> /dev/null ); then
+            # Disable the DNSStubListener to unbind it from port 53
+            # Note that this breaks dns functionality on host until dnsmasq/ftl are up and running
+            echo -en "${OVER}  ${TICK} Disabling systemd-resolved DNSStubListener"
+            # Make a backup of the original /etc/systemd/resolved.conf
+            # (This will need to be restored on uninstallation)
+            sed -r -i.orig 's/#?DNSStubListener=yes/DNSStubListener=no/g' /etc/systemd/resolved.conf
+            echo -e " and restarting systemd-resolved"
+            systemctl reload-or-restart systemd-resolved
+        else
+            echo -e "${OVER}  ${INFO} Systemd-resolved does not need to be restarted"
+        fi
+    else
+        echo -e "${OVER}  ${INFO} Systemd-resolved is not enabled"
+    fi
+}
+
+update_package_cache() {
+    # Running apt-get update/upgrade with minimal output can cause some issues with
+    # requiring user input (e.g password for phpmyadmin see #218)
+
+    # Update package cache on apt based OSes. Do this every time since
+    # it's quick and packages can be updated at any time.
+
+    # Local, named variables
+    local str="Update local cache of available packages"
+    echo ""
+    echo -ne "  ${INFO} ${str}..."
+    # Create a command from the package cache variable
+    if eval "${UPDATE_PKG_CACHE}" &> /dev/null; then
+        echo -e "${OVER}  ${TICK} ${str}"
+    # Otherwise,
+    else
+        # show an error and exit
+        echo -e "${OVER}  ${CROSS} ${str}"
+        echo -ne "  ${COL_LIGHT_RED}Error: Unable to update package cache. Please try \"${UPDATE_PKG_CACHE}\"${COL_NC}"
+        return 1
+    fi
 }
 
 # Let user know if they have outdated packages on their system and
 # advise them to run a package update at soonest possible.
 notify_package_updates_available() {
-  # Local, named variables
-  local str="Checking ${PKG_MANAGER} for upgraded packages"
-  echo -ne "\\n  ${INFO} ${str}..."
-  # Store the list of packages in a variable
-  updatesToInstall=$(eval "${PKG_COUNT}")
-
-  if [[ -d "/lib/modules/$(uname -r)" ]]; then
-    #
-    if [[ "${updatesToInstall}" -eq 0 ]]; then
-      #
-      echo -e "${OVER}  ${TICK} ${str}... up to date!"
-      echo ""
+    # Local, named variables
+    local str="Checking ${PKG_MANAGER} for upgraded packages"
+    echo -ne "\\n  ${INFO} ${str}..."
+    # Store the list of packages in a variable
+    updatesToInstall=$(eval "${PKG_COUNT}")
+
+    if [[ -d "/lib/modules/$(uname -r)" ]]; then
+        if [[ "${updatesToInstall}" -eq 0 ]]; then
+            echo -e "${OVER}  ${TICK} ${str}... up to date!"
+            echo ""
+        else
+            echo -e "${OVER}  ${TICK} ${str}... ${updatesToInstall} updates available"
+            echo -e "  ${INFO} ${COL_LIGHT_GREEN}It is recommended to update your OS after installing the Pi-hole! ${COL_NC}"
+            echo ""
+        fi
     else
-      #
-      echo -e "${OVER}  ${TICK} ${str}... ${updatesToInstall} updates available"
-      echo -e "  ${INFO} ${COL_LIGHT_GREEN}It is recommended to update your OS after installing the Pi-hole! ${COL_NC}"
-      echo ""
-    fi
-  else
-    echo -e "${OVER}  ${CROSS} ${str}
-      Kernel update detected. If the install fails, please reboot and try again\\n"
-  fi
+        echo -e "${OVER}  ${CROSS} ${str}
+        Kernel update detected. If the install fails, please reboot and try again\\n"
+    fi
 }
 
 # What's this doing outside of a function in the middle of nowhere?
 counter=0
 
 install_dependent_packages() {
-  # Local, named variables should be used here, especially for an iterator
-  # Add one to the counter
-  counter=$((counter+1))
-  # If it equals 1,
-  if [[ "${counter}" == 1 ]]; then
-    #
-    echo -e "  ${INFO} Installer Dependency checks..."
-  else
-    #
-    echo -e "  ${INFO} Main Dependency checks..."
-  fi
-
-  # Install packages passed in via argument array
-  # No spinner - conflicts with set -e
-  declare -a argArray1=("${!1}")
-  declare -a installArray
-
-  # Debian based package install - debconf will download the entire package list
-  # so we just create an array of packages not currently installed to cut down on the
-  # amount of download traffic.
-  # NOTE: We may be able to use this installArray in the future to create a list of package that were
-  # installed by us, and remove only the installed packages, and not the entire list.
-  if command -v debconf-apt-progress &> /dev/null; then
-    # For each package,
-    for i in "${argArray1[@]}"; do
-      echo -ne "  ${INFO} Checking for $i..."
-      #
-      if dpkg-query -W -f='${Status}' "${i}" 2>/dev/null | grep "ok installed" &> /dev/null; then
+    # Local, named variables should be used here, especially for an iterator
+    # Add one to the counter
+    counter=$((counter+1))
+    # If it equals 1,
+    if [[ "${counter}" == 1 ]]; then
         #
-        echo -e "${OVER}  ${TICK} Checking for $i"
-      else
-        #
-        echo -e "${OVER}  ${INFO} Checking for $i (will be installed)"
+        echo -e "  ${INFO} Installer Dependency checks..."
+    else
         #
-        installArray+=("${i}")
-      fi
+        echo -e "  ${INFO} Main Dependency checks..."
+    fi
+
+    # Install packages passed in via argument array
+    # No spinner - conflicts with set -e
+    declare -a argArray1=("${!1}")
+    declare -a installArray
+
+    # Debian based package install - debconf will download the entire package list
+    # so we just create an array of packages not currently installed to cut down on the
+    # amount of download traffic.
+    # NOTE: We may be able to use this installArray in the future to create a list of package that were
+    # installed by us, and remove only the installed packages, and not the entire list.
+    if command -v debconf-apt-progress &> /dev/null; then
+        # For each package,
+        for i in "${argArray1[@]}"; do
+            echo -ne "  ${INFO} Checking for $i..."
+            if dpkg-query -W -f='${Status}' "${i}" 2>/dev/null | grep "ok installed" &> /dev/null; then
+                echo -e "${OVER}  ${TICK} Checking for $i"
+            else
+                echo -e "${OVER}  ${INFO} Checking for $i (will be installed)"
+                installArray+=("${i}")
+            fi
+        done
+        if [[ "${#installArray[@]}" -gt 0 ]]; then
+            test_dpkg_lock
+            debconf-apt-progress -- "${PKG_INSTALL[@]}" "${installArray[@]}"
+            return
+        fi
+        echo ""
+        return 0
+    fi
+
+    # Install Fedora/CentOS packages
+    for i in "${argArray1[@]}"; do
+        echo -ne "  ${INFO} Checking for $i..."
+        if ${PKG_MANAGER} -q list installed "${i}" &> /dev/null; then
+            echo -e "${OVER}  ${TICK} Checking for $i"
+        else
+            echo -e "${OVER}  ${INFO} Checking for $i (will be installed)"
+            installArray+=("${i}")
+        fi
     done
-    #
     if [[ "${#installArray[@]}" -gt 0 ]]; then
-      #
-      test_dpkg_lock
-      #
-      debconf-apt-progress -- "${PKG_INSTALL[@]}" "${installArray[@]}"
-      return
-    fi
-      echo ""
-      #
-      return 0
-  fi
-
-  # Install Fedora/CentOS packages
-  for i in "${argArray1[@]}"; do
-    echo -ne "  ${INFO} Checking for $i..."
-    #
-    if ${PKG_MANAGER} -q list installed "${i}" &> /dev/null; then
-      echo -e "${OVER}  ${TICK} Checking for $i"
-    else
-      echo -e "${OVER}  ${INFO} Checking for $i (will be installed)"
-      #
-      installArray+=("${i}")
+        "${PKG_INSTALL[@]}" "${installArray[@]}" &> /dev/null
+        return
     fi
-  done
-  #
-  if [[ "${#installArray[@]}" -gt 0 ]]; then
-    #
-    "${PKG_INSTALL[@]}" "${installArray[@]}" &> /dev/null
-    return
-  fi
-  echo ""
-  return 0
-}
-
-# Create logfiles if necessary
-CreateLogFile() {
-  local str="Creating log and changing owner to dnsmasq"
-  echo ""
-  echo -ne "  ${INFO} ${str}..."
-  # If the pihole log does not exist,
-  if [[ ! -f "/var/log/pihole.log" ]]; then
-    # Make it,
-    touch /var/log/pihole.log
-    # set the permissions,
-    chmod 644 /var/log/pihole.log
-    # and owners
-    chown "${DNSMASQ_USER}":root /var/log/pihole.log
-    echo -e "${OVER}  ${TICK} ${str}"
-  # Otherwise,
-  else
-    # the file should already exist
-    echo -e " ${COL_LIGHT_GREEN}log already exists!${COL_NC}"
-  fi
+    echo ""
+    return 0
 }
 
 # Install the Web interface dashboard
 installPiholeWeb() {
-  echo ""
-  echo "  ${INFO} Installing blocking page..."
-
-  local str="Creating directory for blocking page, and copying files"
-  echo -ne "  ${INFO} ${str}..."
-  # Install the directory
-  install -d /var/www/html/pihole
-  # and the blockpage
-  install -D ${PI_HOLE_LOCAL_REPO}/advanced/{index,blockingpage}.* /var/www/html/pihole/
-
-  # Remove superseded file
-  if [[ -e "/var/www/html/pihole/index.js" ]]; then
-    rm "/var/www/html/pihole/index.js"
-  fi
-
-  echo -e "${OVER}  ${TICK} ${str}"
-
-  local str="Backing up index.lighttpd.html"
-  echo -ne "  ${INFO} ${str}..."
-  # If the default index file exists,
-  if [[ -f "/var/www/html/index.lighttpd.html" ]]; then
-    # back it up
-    mv /var/www/html/index.lighttpd.html /var/www/html/index.lighttpd.orig
+    echo ""
+    echo "  ${INFO} Installing blocking page..."
+
+    local str="Creating directory for blocking page, and copying files"
+    echo -ne "  ${INFO} ${str}..."
+    # Install the directory
+    install -d /var/www/html/pihole
+    # and the blockpage
+    install -D ${PI_HOLE_LOCAL_REPO}/advanced/{index,blockingpage}.* /var/www/html/pihole/
+
+    # Remove superseded file
+    if [[ -e "/var/www/html/pihole/index.js" ]]; then
+        rm "/var/www/html/pihole/index.js"
+    fi
+
+    echo -e "${OVER}  ${TICK} ${str}"
+
+    local str="Backing up index.lighttpd.html"
+    echo -ne "  ${INFO} ${str}..."
+    # If the default index file exists,
+    if [[ -f "/var/www/html/index.lighttpd.html" ]]; then
+        # back it up
+        mv /var/www/html/index.lighttpd.html /var/www/html/index.lighttpd.orig
+        echo -e "${OVER}  ${TICK} ${str}"
+    # Otherwise,
+    else
+        # don't do anything
+        echo -e "${OVER}  ${CROSS} ${str}
+        No default index.lighttpd.html file found... not backing up"
+    fi
+
+    # Install Sudoers file
+    echo ""
+    local str="Installing sudoer file"
+    echo -ne "  ${INFO} ${str}..."
+    # Make the .d directory if it doesn't exist
+    mkdir -p /etc/sudoers.d/
+    # and copy in the pihole sudoers file
+    cp ${PI_HOLE_LOCAL_REPO}/advanced/Templates/pihole.sudo /etc/sudoers.d/pihole
+    # Add lighttpd user (OS dependent) to sudoers file
+    echo "${LIGHTTPD_USER} ALL=NOPASSWD: /usr/local/bin/pihole" >> /etc/sudoers.d/pihole
+
+    # If the Web server user is lighttpd,
+    if [[ "$LIGHTTPD_USER" == "lighttpd" ]]; then
+        # Allow executing pihole via sudo with Fedora
+        # Usually /usr/local/bin is not permitted as directory for sudoable programs
+        echo "Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin" >> /etc/sudoers.d/pihole
+    fi
+    # Set the strict permissions on the file
+    chmod 0440 /etc/sudoers.d/pihole
     echo -e "${OVER}  ${TICK} ${str}"
-  # Othwerwise,
-  else
-    # don't do anything
-    echo -e "${OVER}  ${CROSS} ${str}
-      No default index.lighttpd.html file found... not backing up"
-  fi
-
-  # Install Sudoers file
-  echo ""
-  local str="Installing sudoer file"
-  echo -ne "  ${INFO} ${str}..."
-  # Make the .d directory if it doesn't exist
-  mkdir -p /etc/sudoers.d/
-  # and copy in the pihole sudoers file
-  cp ${PI_HOLE_LOCAL_REPO}/advanced/pihole.sudo /etc/sudoers.d/pihole
-  # Add lighttpd user (OS dependent) to sudoers file
-  echo "${LIGHTTPD_USER} ALL=NOPASSWD: /usr/local/bin/pihole" >> /etc/sudoers.d/pihole
-
-  # If the Web server user is lighttpd,
-  if [[ "$LIGHTTPD_USER" == "lighttpd" ]]; then
-    # Allow executing pihole via sudo with Fedora
-    # Usually /usr/local/bin is not permitted as directory for sudoable programms
-    echo "Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin" >> /etc/sudoers.d/pihole
-  fi
-  # Set the strict permissions on the file
-  chmod 0440 /etc/sudoers.d/pihole
-  echo -e "${OVER}  ${TICK} ${str}"
 }
 
 # Installs a cron file
 installCron() {
-  # Install the cron job
-  local str="Installing latest Cron script"
-  echo ""
-  echo -ne "  ${INFO} ${str}..."
-  # Copy the cron file over from the local repo
-  cp ${PI_HOLE_LOCAL_REPO}/advanced/pihole.cron /etc/cron.d/pihole
-  # Randomize gravity update time
-  sed -i "s/59 1 /$((1 + RANDOM % 58)) $((3 + RANDOM % 2))/" /etc/cron.d/pihole
-  # Randomize update checker time
-  sed -i "s/59 17/$((1 + RANDOM % 58)) $((12 + RANDOM % 8))/" /etc/cron.d/pihole
-  echo -e "${OVER}  ${TICK} ${str}"
+    # Install the cron job
+    local str="Installing latest Cron script"
+    echo ""
+    echo -ne "  ${INFO} ${str}..."
+    # Copy the cron file over from the local repo
+    cp ${PI_HOLE_LOCAL_REPO}/advanced/Templates/pihole.cron /etc/cron.d/pihole
+    # Randomize gravity update time
+    sed -i "s/59 1 /$((1 + RANDOM % 58)) $((3 + RANDOM % 2))/" /etc/cron.d/pihole
+    # Randomize update checker time
+    sed -i "s/59 17/$((1 + RANDOM % 58)) $((12 + RANDOM % 8))/" /etc/cron.d/pihole
+    echo -e "${OVER}  ${TICK} ${str}"
 }
 
 # Gravity is a very important script as it aggregates all of the domains into a single HOSTS formatted list,
 # which is what Pi-hole needs to begin blocking ads
 runGravity() {
-  echo ""
-  echo -e "  ${INFO} Preparing to run gravity.sh to refresh hosts..."
-  # If cached lists exist,
-  if ls /etc/pihole/list* 1> /dev/null 2>&1; then
-    echo -e "  ${INFO} Cleaning up previous install (preserving whitelist/blacklist)"
-    # remove them
-    rm /etc/pihole/list.*
-  fi
-  # If the default ad lists file exists,
-  if [[ ! -e /etc/pihole/adlists.default ]]; then
-    # copy it over from the local repo
-    cp ${PI_HOLE_LOCAL_REPO}/adlists.default /etc/pihole/adlists.default
-  fi
-  echo -e "  ${INFO} Running gravity.sh"
-  # Run gravity in the current shell
-  { /opt/pihole/gravity.sh; }
+    # Run gravity in the current shell
+    { /opt/pihole/gravity.sh --force; }
 }
 
 # Check if the pihole user exists and create if it does not
 create_pihole_user() {
-  local str="Checking for user 'pihole'"
-  echo -ne "  ${INFO} ${str}..."
-  # If the user pihole exists,
-  if id -u pihole &> /dev/null; then
-    # just show a success
-    echo -ne "${OVER}  ${TICK} ${str}"
-  # Othwerwise,
-  else
-    echo -ne "${OVER}  ${CROSS} ${str}"
-    local str="Creating user 'pihole'"
+    local str="Checking for user 'pihole'"
     echo -ne "  ${INFO} ${str}..."
-    # create her with the useradd command
-    useradd -r -s /usr/sbin/nologin pihole
-    echo -ne "${OVER}  ${TICK} ${str}"
-  fi
+    # If the user pihole exists,
+    if id -u pihole &> /dev/null; then
+        # just show a success
+        echo -ne "${OVER}  ${TICK} ${str}"
+    # Otherwise,
+    else
+        echo -ne "${OVER}  ${CROSS} ${str}"
+        local str="Creating user 'pihole'"
+        echo -ne "  ${INFO} ${str}..."
+        # create her with the useradd command
+        useradd -r -s /usr/sbin/nologin pihole
+        echo -ne "${OVER}  ${TICK} ${str}"
+    fi
 }
 
 # Allow HTTP and DNS traffic
 configureFirewall() {
-  echo ""
-  # If a firewall is running,
-  if firewall-cmd --state &> /dev/null; then
-    # ask if the user wants to install Pi-hole's default firwall rules
-    whiptail --title "Firewall in use" --yesno "We have detected a running firewall\\n\\nPi-hole currently requires HTTP and DNS port access.\\n\\n\\n\\nInstall Pi-hole default firewall rules?" ${r} ${c} || \
-    { echo -e "  ${INFO} Not installing firewall rulesets."; return 0; }
-    echo -e "  ${TICK} Configuring FirewallD for httpd and dnsmasq"
-    # Allow HTTP and DNS traffice
-    firewall-cmd --permanent --add-service=http --add-service=dns
-    # Reload the firewall to apply these changes
-    firewall-cmd --reload
-    return 0
-  # Check for proper kernel modules to prevent failure
-  elif modinfo ip_tables &> /dev/null && command -v iptables &> /dev/null; then
+    echo ""
+    # If a firewall is running,
+    if firewall-cmd --state &> /dev/null; then
+        # ask if the user wants to install Pi-hole's default firewall rules
+        whiptail --title "Firewall in use" --yesno "We have detected a running firewall\\n\\nPi-hole currently requires HTTP and DNS port access.\\n\\n\\n\\nInstall Pi-hole default firewall rules?" ${r} ${c} || \
+        { echo -e "  ${INFO} Not installing firewall rulesets."; return 0; }
+        echo -e "  ${TICK} Configuring FirewallD for httpd and pihole-FTL"
+        # Allow HTTP and DNS traffic
+        firewall-cmd --permanent --add-service=http --add-service=dns
+        # Reload the firewall to apply these changes
+        firewall-cmd --reload
+        return 0
+        # Check for proper kernel modules to prevent failure
+    elif modinfo ip_tables &> /dev/null && command -v iptables &> /dev/null; then
     # If chain Policy is not ACCEPT or last Rule is not ACCEPT
     # then check and insert our Rules above the DROP/REJECT Rule.
-    if iptables -S INPUT | head -n1 | grep -qv '^-P.*ACCEPT$' || iptables -S INPUT | tail -n1 | grep -qv '^-\(A\|P\).*ACCEPT$'; then
-      whiptail --title "Firewall in use" --yesno "We have detected a running firewall\\n\\nPi-hole currently requires HTTP and DNS port access.\\n\\n\\n\\nInstall Pi-hole default firewall rules?" ${r} ${c} || \
-      { echo -e "  ${INFO} Not installing firewall rulesets."; return 0; }
-      echo -e "  ${TICK} Installing new IPTables firewall rulesets"
-      # Check chain first, otherwise a new rule will duplicate old ones
-      iptables -C INPUT -p tcp -m tcp --dport 80 -j ACCEPT &> /dev/null || iptables -I INPUT 1 -p tcp -m tcp --dport 80 -j ACCEPT
-      iptables -C INPUT -p tcp -m tcp --dport 53 -j ACCEPT &> /dev/null || iptables -I INPUT 1 -p tcp -m tcp --dport 53 -j ACCEPT
-      iptables -C INPUT -p udp -m udp --dport 53 -j ACCEPT &> /dev/null || iptables -I INPUT 1 -p udp -m udp --dport 53 -j ACCEPT
-      iptables -C INPUT -p tcp -m tcp --dport 4711:4720 -i lo -j ACCEPT &> /dev/null || iptables -I INPUT 1 -p tcp -m tcp --dport 4711:4720 -i lo -j ACCEPT
-      return 0
-    fi
-  # Othwerwise,
-  else
-    # no firewall is running
-    echo -e "  ${INFO} No active firewall detected.. skipping firewall configuration"
-    # so just exit
-    return 0
-  fi
-  echo -e "  ${INFO} Skipping firewall configuration"
+        if iptables -S INPUT | head -n1 | grep -qv '^-P.*ACCEPT$' || iptables -S INPUT | tail -n1 | grep -qv '^-\(A\|P\).*ACCEPT$'; then
+            whiptail --title "Firewall in use" --yesno "We have detected a running firewall\\n\\nPi-hole currently requires HTTP and DNS port access.\\n\\n\\n\\nInstall Pi-hole default firewall rules?" ${r} ${c} || \
+            { echo -e "  ${INFO} Not installing firewall rulesets."; return 0; }
+            echo -e "  ${TICK} Installing new IPTables firewall rulesets"
+            # Check chain first, otherwise a new rule will duplicate old ones
+            iptables -C INPUT -p tcp -m tcp --dport 80 -j ACCEPT &> /dev/null || iptables -I INPUT 1 -p tcp -m tcp --dport 80 -j ACCEPT
+            iptables -C INPUT -p tcp -m tcp --dport 53 -j ACCEPT &> /dev/null || iptables -I INPUT 1 -p tcp -m tcp --dport 53 -j ACCEPT
+            iptables -C INPUT -p udp -m udp --dport 53 -j ACCEPT &> /dev/null || iptables -I INPUT 1 -p udp -m udp --dport 53 -j ACCEPT
+            iptables -C INPUT -p tcp -m tcp --dport 4711:4720 -i lo -j ACCEPT &> /dev/null || iptables -I INPUT 1 -p tcp -m tcp --dport 4711:4720 -i lo -j ACCEPT
+            return 0
+        fi
+    # Otherwise,
+    else
+        # no firewall is running
+        echo -e "  ${INFO} No active firewall detected.. skipping firewall configuration"
+        # so just exit
+        return 0
+    fi
+    echo -e "  ${INFO} Skipping firewall configuration"
 }
 
 #
 finalExports() {
-  # If the Web interface is not set to be installed,
-  if [[ "${INSTALL_WEB}" == false ]]; then
-    # and if there is not an IPv4 address,
-    if [[ "${IPV4_ADDRESS}" ]]; then
-      # there is no block page, so set IPv4 to 0.0.0.0 (all IP addresses)
-      IPV4_ADDRESS="0.0.0.0"
-    fi
-    if [[ "${IPV6_ADDRESS}" ]]; then
-      # and IPv6 to ::/0
-      IPV6_ADDRESS="::/0"
-    fi
-  fi
-
-  # If the setup variable file exists,
-  if [[ -e "${setupVars}" ]]; then
-    # update the variables in the file
-    sed -i.update.bak '/PIHOLE_INTERFACE/d;/IPV4_ADDRESS/d;/IPV6_ADDRESS/d;/PIHOLE_DNS_1/d;/PIHOLE_DNS_2/d;/QUERY_LOGGING/d;/INSTALL_WEB/d;/LIGHTTPD_ENABLED/d;' "${setupVars}"
-  fi
-  # echo the information to the user
+    # If the Web interface is not set to be installed,
+    if [[ "${INSTALL_WEB_INTERFACE}" == false ]]; then
+        # and if there is not an IPv4 address,
+        if [[ "${IPV4_ADDRESS}" ]]; then
+            # there is no block page, so set IPv4 to 0.0.0.0 (all IP addresses)
+            IPV4_ADDRESS="0.0.0.0"
+        fi
+        if [[ "${IPV6_ADDRESS}" ]]; then
+            # and IPv6 to ::/0
+            IPV6_ADDRESS="::/0"
+        fi
+    fi
+
+    # If the setup variable file exists,
+    if [[ -e "${setupVars}" ]]; then
+        # update the variables in the file
+        sed -i.update.bak '/PIHOLE_INTERFACE/d;/IPV4_ADDRESS/d;/IPV6_ADDRESS/d;/PIHOLE_DNS_1/d;/PIHOLE_DNS_2/d;/QUERY_LOGGING/d;/INSTALL_WEB_SERVER/d;/INSTALL_WEB_INTERFACE/d;/LIGHTTPD_ENABLED/d;' "${setupVars}"
+    fi
+    # echo the information to the user
     {
-  echo "PIHOLE_INTERFACE=${PIHOLE_INTERFACE}"
-  echo "IPV4_ADDRESS=${IPV4_ADDRESS}"
-  echo "IPV6_ADDRESS=${IPV6_ADDRESS}"
-  echo "PIHOLE_DNS_1=${PIHOLE_DNS_1}"
-  echo "PIHOLE_DNS_2=${PIHOLE_DNS_2}"
-  echo "QUERY_LOGGING=${QUERY_LOGGING}"
-  echo "INSTALL_WEB=${INSTALL_WEB}"
-  echo "LIGHTTPD_ENABLED=${LIGHTTPD_ENABLED}"
+    echo "PIHOLE_INTERFACE=${PIHOLE_INTERFACE}"
+    echo "IPV4_ADDRESS=${IPV4_ADDRESS}"
+    echo "IPV6_ADDRESS=${IPV6_ADDRESS}"
+    echo "PIHOLE_DNS_1=${PIHOLE_DNS_1}"
+    echo "PIHOLE_DNS_2=${PIHOLE_DNS_2}"
+    echo "QUERY_LOGGING=${QUERY_LOGGING}"
+    echo "INSTALL_WEB_SERVER=${INSTALL_WEB_SERVER}"
+    echo "INSTALL_WEB_INTERFACE=${INSTALL_WEB_INTERFACE}"
+    echo "LIGHTTPD_ENABLED=${LIGHTTPD_ENABLED}"
     }>> "${setupVars}"
 
-  # Bring in the current settings and the functions to manipulate them
-  source "${setupVars}"
-  source "${PI_HOLE_LOCAL_REPO}/advanced/Scripts/webpage.sh"
+    # Bring in the current settings and the functions to manipulate them
+    source "${setupVars}"
+    source "${PI_HOLE_LOCAL_REPO}/advanced/Scripts/webpage.sh"
 
-  # Look for DNS server settings which would have to be reapplied
-  ProcessDNSSettings
+    # Look for DNS server settings which would have to be reapplied
+    ProcessDNSSettings
 
-  # Look for DHCP server settings which would have to be reapplied
-  ProcessDHCPSettings
+    # Look for DHCP server settings which would have to be reapplied
+    ProcessDHCPSettings
 }
 
 # Install the logrotate script
 installLogrotate() {
 
-  local str="Installing latest logrotate script"
-  echo ""
-  echo -ne "  ${INFO} ${str}..."
-  # Copy the file over from the local repo
-  cp ${PI_HOLE_LOCAL_REPO}/advanced/logrotate /etc/pihole/logrotate
-  # Different operating systems have different user / group
-  # settings for logrotate that makes it impossible to create
-  # a static logrotate file that will work with e.g.
-  # Rasbian and Ubuntu at the same time. Hence, we have to
-  # customize the logrotate script here in order to reflect
-  # the local properties of the /var/log directory
-  logusergroup="$(stat -c '%U %G' /var/log)"
-  # If the variable has a value,
-  if [[ ! -z "${logusergroup}" ]]; then
-    #
-    sed -i "s/# su #/su ${logusergroup}/g;" /etc/pihole/logrotate
-  fi
-  echo -e "${OVER}  ${TICK} ${str}"
-}
-
-# Install base files and web interface
-installPihole() {
-  # Create the pihole user
-  create_pihole_user
-
-  # If the user wants to install the Web interface,
-  if [[ "${INSTALL_WEB}" == true ]]; then
-    if [[ ! -d "/var/www/html" ]]; then
-      # make the Web directory if necessary
-      mkdir -p /var/www/html
-    fi
-    # Set the owner and permissions
-    chown ${LIGHTTPD_USER}:${LIGHTTPD_GROUP} /var/www/html
-    chmod 775 /var/www/html
-    # Give pihole access to the Web server group
-    usermod -a -G ${LIGHTTPD_GROUP} pihole
-    # If the lighttpd command is executable,
-    if [[ -x "$(command -v lighty-enable-mod)" ]]; then
-      # enable fastcgi and fastcgi-php
-      lighty-enable-mod fastcgi fastcgi-php > /dev/null || true
-    else
-      # Othweise, show info about installing them
-      echo -e  "  ${INFO} Warning: 'lighty-enable-mod' utility not found
-      Please ensure fastcgi is enabled if you experience issues\\n"
-    fi
-  fi
-  # Install scripts,
-  installScripts
-  # configs,
-  installConfigs
-  # and create the log file
-  CreateLogFile
-  # If the user wants to install the dashboard,
-  if [[ "${INSTALL_WEB}" == true ]]; then
-    # do so
-    installPiholeWeb
-  fi
-  # Install the cron file
-  installCron
-  # Install the logrotate file
-  installLogrotate
-  # Check if FTL is installed
-  FTLdetect || echo -e "  ${CROSS} FTL Engine not installed"
-  # Configure the firewall
-  configureFirewall
-
-  #update setupvars.conf with any variables that may or may not have been changed during the install
-  finalExports
+    local str="Installing latest logrotate script"
+    echo ""
+    echo -ne "  ${INFO} ${str}..."
+    # Copy the file over from the local repo
+    cp ${PI_HOLE_LOCAL_REPO}/advanced/Templates/logrotate /etc/pihole/logrotate
+    # Different operating systems have different user / group
+    # settings for logrotate that makes it impossible to create
+    # a static logrotate file that will work with e.g.
+    # Rasbian and Ubuntu at the same time. Hence, we have to
+    # customize the logrotate script here in order to reflect
+    # the local properties of the /var/log directory
+    logusergroup="$(stat -c '%U %G' /var/log)"
+    # If the variable has a value,
+    if [[ ! -z "${logusergroup}" ]]; then
+        #
+        sed -i "s/# su #/su ${logusergroup}/g;" /etc/pihole/logrotate
+    fi
+    echo -e "${OVER}  ${TICK} ${str}"
 }
 
 # At some point in the future this list can be pruned, for now we'll need it to ensure updates don't break.
 # Refactoring of install script has changed the name of a couple of variables. Sort them out here.
 accountForRefactor() {
-  sed -i 's/piholeInterface/PIHOLE_INTERFACE/g' ${setupVars}
-  sed -i 's/IPv4_address/IPV4_ADDRESS/g' ${setupVars}
-  sed -i 's/IPv4addr/IPV4_ADDRESS/g' ${setupVars}
-  sed -i 's/IPv6_address/IPV6_ADDRESS/g' ${setupVars}
-  sed -i 's/piholeIPv6/IPV6_ADDRESS/g' ${setupVars}
-  sed -i 's/piholeDNS1/PIHOLE_DNS_1/g' ${setupVars}
-  sed -i 's/piholeDNS2/PIHOLE_DNS_2/g' ${setupVars}
+    sed -i 's/piholeInterface/PIHOLE_INTERFACE/g' ${setupVars}
+    sed -i 's/IPv4_address/IPV4_ADDRESS/g' ${setupVars}
+    sed -i 's/IPv4addr/IPV4_ADDRESS/g' ${setupVars}
+    sed -i 's/IPv6_address/IPV6_ADDRESS/g' ${setupVars}
+    sed -i 's/piholeIPv6/IPV6_ADDRESS/g' ${setupVars}
+    sed -i 's/piholeDNS1/PIHOLE_DNS_1/g' ${setupVars}
+    sed -i 's/piholeDNS2/PIHOLE_DNS_2/g' ${setupVars}
+    sed -i 's/^INSTALL_WEB=/INSTALL_WEB_INTERFACE=/' ${setupVars}
+    # Add 'INSTALL_WEB_SERVER', if its not been applied already: https://github.com/pi-hole/pi-hole/pull/2115
+    if ! grep -q '^INSTALL_WEB_SERVER=' ${setupVars}; then
+        local webserver_installed=false
+        if grep -q '^INSTALL_WEB_INTERFACE=true' ${setupVars}; then
+            webserver_installed=true
+        fi
+        echo -e "INSTALL_WEB_SERVER=$webserver_installed" >> ${setupVars}
+    fi
 }
 
-updatePihole() {
-  accountForRefactor
-  # Install base files and web interface
-  installScripts
-  # Install config files
-  installConfigs
-  # Create the log file
-  CreateLogFile
-  # If the user wants to install the dasboard,
-  if [[ "${INSTALL_WEB}" == true ]]; then
-    # do so
-    installPiholeWeb
-  fi
-  # Install the cron file
-  installCron
-  # Install logrotate
-  installLogrotate
-  # Detect if FTL is installed
-  FTLdetect || echo -e "  ${CROSS} FTL Engine not installed."
-
-  #update setupvars.conf with any variables that may or may not have been changed during the install
-  finalExports
+# Install base files and web interface
+installPihole() {
+    # Create the pihole user
+    create_pihole_user
+
+    # If the user wants to install the Web interface,
+    if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
+        if [[ ! -d "/var/www/html" ]]; then
+            # make the Web directory if necessary
+            mkdir -p /var/www/html
+        fi
 
-}
+        if [[ "${INSTALL_WEB_SERVER}" == true ]]; then
+            # Set the owner and permissions
+            chown ${LIGHTTPD_USER}:${LIGHTTPD_GROUP} /var/www/html
+            chmod 775 /var/www/html
+            # Give pihole access to the Web server group
+            usermod -a -G ${LIGHTTPD_GROUP} pihole
+            # If the lighttpd command is executable,
+            if [[ -x "$(command -v lighty-enable-mod)" ]]; then
+                # enable fastcgi and fastcgi-php
+                lighty-enable-mod fastcgi fastcgi-php > /dev/null || true
+            else
+                # Otherwise, show info about installing them
+                echo -e  "  ${INFO} Warning: 'lighty-enable-mod' utility not found
+                Please ensure fastcgi is enabled if you experience issues\\n"
+            fi
+        fi
+    fi
+    # For updates and unattended install.
+    if [[ "${useUpdateVars}" == true ]]; then
+        accountForRefactor
+    fi
+    # Install base files and web interface
+    if ! installScripts; then
+        echo -e "  {CROSS} Failure in dependent script copy function."
+        exit 1
+    fi
+    # Install config files
+    if ! installConfigs; then
+        echo -e "  {CROSS} Failure in dependent config copy function."
+        exit 1
+    fi
+    # If the user wants to install the dashboard,
+    if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
+        # do so
+        installPiholeWeb
+    fi
+    # Install the cron file
+    installCron
+    # Install the logrotate file
+    installLogrotate
+    # Check if FTL is installed
+    FTLdetect || echo -e "  ${CROSS} FTL Engine not installed"
+    # Configure the firewall
+    if [[ "${useUpdateVars}" == false ]]; then
+        configureFirewall
+    fi
 
+    # install a man page entry for pihole
+    install_manpage
+
+    # Update setupvars.conf with any variables that may or may not have been changed during the install
+    finalExports
+}
 
 # SELinux
 checkSelinux() {
-  # If the getenforce command exists,
-  if command -v getenforce &> /dev/null; then
-    # Store the current mode in a variable
-    enforceMode=$(getenforce)
-    echo -e "\\n  ${INFO} SELinux mode detected: ${enforceMode}"
-
-    # If it's enforcing,
-    if [[ "${enforceMode}" == "Enforcing" ]]; then
-      # Explain Pi-hole does not support it yet
-      whiptail --defaultno --title "SELinux Enforcing Detected" --yesno "SELinux is being ENFORCED on your system! \\n\\nPi-hole currently does not support SELinux, but you may still continue with the installation.\\n\\nNote: Web Admin will not be fully functional unless you set your policies correctly\\n\\nContinue installing Pi-hole?" ${r} ${c} || \
-        { echo -e "\\n  ${COL_LIGHT_RED}SELinux Enforcing detected, exiting installer${COL_NC}"; exit 1; }
-      echo -e "  ${INFO} Continuing installation with SELinux Enforcing
-  ${INFO} Please refer to official SELinux documentation to create a custom policy"
-    fi
-  fi
+    # If the getenforce command exists,
+    if command -v getenforce &> /dev/null; then
+        # Store the current mode in a variable
+        enforceMode=$(getenforce)
+        echo -e "\\n  ${INFO} SELinux mode detected: ${enforceMode}"
+
+        # If it's enforcing,
+        if [[ "${enforceMode}" == "Enforcing" ]]; then
+            # Explain Pi-hole does not support it yet
+            whiptail --defaultno --title "SELinux Enforcing Detected" --yesno "SELinux is being ENFORCED on your system! \\n\\nPi-hole currently does not support SELinux, but you may still continue with the installation.\\n\\nNote: Web Admin will not be fully functional unless you set your policies correctly\\n\\nContinue installing Pi-hole?" ${r} ${c} || \
+            { echo -e "\\n  ${COL_LIGHT_RED}SELinux Enforcing detected, exiting installer${COL_NC}"; exit 1; }
+            echo -e "  ${INFO} Continuing installation with SELinux Enforcing
+            ${INFO} Please refer to official SELinux documentation to create a custom policy"
+        fi
+    fi
 }
 
 # Installation complete message with instructions for the user
 displayFinalMessage() {
-  # If
-  if [[ "${#1}" -gt 0 ]] ; then
-    pwstring="$1"
-  # else, if the dashboard password in the setup variables exists,
-  elif [[ $(grep 'WEBPASSWORD' -c /etc/pihole/setupVars.conf) -gt 0 ]]; then
-    # set a variable for evaluation later
-    pwstring="unchanged"
-  else
-    # set a variable for evaluation later
-    pwstring="NOT SET"
-  fi
-   # If the user wants to install the dashboard,
-   if [[ "${INSTALL_WEB}" == true ]]; then
-       # Store a message in a variable and display it
-       additional="View the web interface at http://pi.hole/admin or http://${IPV4_ADDRESS%/*}/admin
+    # If
+    if [[ "${#1}" -gt 0 ]] ; then
+        pwstring="$1"
+        # else, if the dashboard password in the setup variables exists,
+    elif [[ $(grep 'WEBPASSWORD' -c /etc/pihole/setupVars.conf) -gt 0 ]]; then
+        # set a variable for evaluation later
+        pwstring="unchanged"
+    else
+        # set a variable for evaluation later
+        pwstring="NOT SET"
+    fi
+    # If the user wants to install the dashboard,
+    if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
+        # Store a message in a variable and display it
+        additional="View the web interface at http://pi.hole/admin or http://${IPV4_ADDRESS%/*}/admin
 
 Your Admin Webpage login password is ${pwstring}"
    fi
 
-  # Final completion message to user
-  whiptail --msgbox --backtitle "Make it so." --title "Installation Complete!" "Configure your devices to use the Pi-hole as their DNS server using:
+    # Final completion message to user
+    whiptail --msgbox --backtitle "Make it so." --title "Installation Complete!" "Configure your devices to use the Pi-hole as their DNS server using:
 
 IPv4:	${IPV4_ADDRESS%/*}
 IPv6:	${IPV6_ADDRESS:-"Not Configured"}
@@ -1668,504 +1870,648 @@ ${additional}" ${r} ${c}
 }
 
 update_dialogs() {
-  # If pihole -r "reconfigure" option was selected,
-  if [[ "${reconfigure}" = true ]]; then
-    # set some variables that will be used
-    opt1a="Repair"
-    opt1b="This will retain existing settings"
-    strAdd="You will remain on the same version"
-  # Othweise,
-  else
-    # set some variables with different values
-    opt1a="Update"
-    opt1b="This will retain existing settings."
-    strAdd="You will be updated to the latest version."
-  fi
-  opt2a="Reconfigure"
-  opt2b="This will allow you to enter new settings"
-
-  # Display the information to the user
-  UpdateCmd=$(whiptail --title "Existing Install Detected!" --menu "\\n\\nWe have detected an existing install.\\n\\nPlease choose from the following options: \\n($strAdd)" ${r} ${c} 2 \
-  "${opt1a}"  "${opt1b}" \
-  "${opt2a}"  "${opt2b}" 3>&2 2>&1 1>&3) || \
-  { echo -e "  ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; }
-
-  # Set the variable based on if the user chooses
-  case ${UpdateCmd} in
-    # repair, or
-    ${opt1a})
-      echo -e "  ${INFO} ${opt1a} option selected"
-      useUpdateVars=true
-      ;;
-    # reconfigure,
-    ${opt2a})
-      echo -e "  ${INFO} ${opt2a} option selected"
-      useUpdateVars=false
-      ;;
+    # If pihole -r "reconfigure" option was selected,
+    if [[ "${reconfigure}" = true ]]; then
+        # set some variables that will be used
+        opt1a="Repair"
+        opt1b="This will retain existing settings"
+        strAdd="You will remain on the same version"
+    # Otherwise,
+    else
+        # set some variables with different values
+        opt1a="Update"
+        opt1b="This will retain existing settings."
+        strAdd="You will be updated to the latest version."
+    fi
+    opt2a="Reconfigure"
+    opt2b="This will reset your Pi-hole and allow you to enter new settings."
+
+    # Display the information to the user
+    UpdateCmd=$(whiptail --title "Existing Install Detected!" --menu "\\n\\nWe have detected an existing install.\\n\\nPlease choose from the following options: \\n($strAdd)" ${r} ${c} 2 \
+    "${opt1a}"  "${opt1b}" \
+    "${opt2a}"  "${opt2b}" 3>&2 2>&1 1>&3) || \
+    { echo -e "  ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; }
+
+    # Set the variable based on if the user chooses
+    case ${UpdateCmd} in
+        # repair, or
+        ${opt1a})
+            echo -e "  ${INFO} ${opt1a} option selected"
+            useUpdateVars=true
+            ;;
+        # reconfigure,
+        ${opt2a})
+            echo -e "  ${INFO} ${opt2a} option selected"
+            useUpdateVars=false
+            ;;
     esac
 }
 
+check_download_exists() {
+    status=$(curl --head --silent "https://ftl.pi-hole.net/${1}" | head -n 1)
+    if grep -q "404" <<< "$status"; then
+        return 1
+    else
+        return 0
+    fi
+}
+
+fully_fetch_repo() {
+    # Add upstream branches to shallow clone
+    local directory="${1}"
+
+    cd "${directory}" || return 1
+    if is_repo "${directory}"; then
+        git remote set-branches origin '*' || return 1
+        git fetch --quiet || return 1
+    else
+        return 1
+    fi
+    return 0
+}
+
+get_available_branches() {
+    # Return available branches
+    local directory
+    directory="${1}"
+    local output
+
+    cd "${directory}" || return 1
+    # Get reachable remote branches, but store STDERR as STDOUT variable
+    output=$( { git ls-remote --heads --quiet | cut -d'/' -f3- -; } 2>&1 )
+    echo "$output"
+    return
+}
+
+fetch_checkout_pull_branch() {
+    # Check out specified branch
+    local directory
+    directory="${1}"
+    local branch
+    branch="${2}"
+
+    # Set the reference for the requested branch, fetch, check it put and pull it
+    cd "${directory}" || return 1
+    git remote set-branches origin "${branch}" || return 1
+    git stash --all --quiet &> /dev/null || true
+    git clean --quiet --force -d || true
+    git fetch --quiet || return 1
+    checkout_pull_branch "${directory}" "${branch}" || return 1
+}
+
+checkout_pull_branch() {
+    # Check out specified branch
+    local directory
+    directory="${1}"
+    local branch
+    branch="${2}"
+    local oldbranch
+
+    cd "${directory}" || return 1
+
+    oldbranch="$(git symbolic-ref HEAD)"
+
+    str="Switching to branch: '${branch}' from '${oldbranch}'"
+    echo -ne "  ${INFO} $str"
+    git checkout "${branch}" --quiet || return 1
+    echo -e "${OVER}  ${TICK} $str"
+
+    git_pull=$(git pull || return 1)
+
+    if [[ "$git_pull" == *"up-to-date"* ]]; then
+        echo -e "  ${INFO} ${git_pull}"
+    else
+        echo -e "$git_pull\\n"
+    fi
+
+    return 0
+}
+
 clone_or_update_repos() {
-  # If the user wants to reconfigure,
-  if [[ "${reconfigure}" == true ]]; then
-    echo "  ${INFO} Performing reconfiguration, skipping download of local repos"
-    # Reset the Core repo
-    resetRepo ${PI_HOLE_LOCAL_REPO} || \
-      { echo -e "  ${COL_LIGHT_RED}Unable to reset ${PI_HOLE_LOCAL_REPO}, exiting installer${COL_NC}"; \
+    # If the user wants to reconfigure,
+    if [[ "${reconfigure}" == true ]]; then
+        echo "  ${INFO} Performing reconfiguration, skipping download of local repos"
+        # Reset the Core repo
+        resetRepo ${PI_HOLE_LOCAL_REPO} || \
+        { echo -e "  ${COL_LIGHT_RED}Unable to reset ${PI_HOLE_LOCAL_REPO}, exiting installer${COL_NC}"; \
         exit 1; \
-      }
-    # If the Web interface was installed,
-    if [[ "${INSTALL_WEB}" == true ]]; then
-      # reset it's repo
-      resetRepo ${webInterfaceDir} || \
-        { echo -e "  ${COL_LIGHT_RED}Unable to reset ${webInterfaceDir}, exiting installer${COL_NC}"; \
-          exit 1; \
         }
-    fi
-  # Otherwise, a repair is happening
-  else
-    # so get git files for Core
-    getGitFiles ${PI_HOLE_LOCAL_REPO} ${piholeGitUrl} || \
-      { echo -e "  ${COL_LIGHT_RED}Unable to clone ${piholeGitUrl} into ${PI_HOLE_LOCAL_REPO}, unable to continue${COL_NC}"; \
+        # If the Web interface was installed,
+        if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
+            # reset it's repo
+            resetRepo ${webInterfaceDir} || \
+            { echo -e "  ${COL_LIGHT_RED}Unable to reset ${webInterfaceDir}, exiting installer${COL_NC}"; \
+            exit 1; \
+            }
+        fi
+    # Otherwise, a repair is happening
+    else
+        # so get git files for Core
+        getGitFiles ${PI_HOLE_LOCAL_REPO} ${piholeGitUrl} || \
+        { echo -e "  ${COL_LIGHT_RED}Unable to clone ${piholeGitUrl} into ${PI_HOLE_LOCAL_REPO}, unable to continue${COL_NC}"; \
         exit 1; \
-      }
-      # If the Web interface was installed,
-      if [[ "${INSTALL_WEB}" == true ]]; then
-        # get the Web git files
-        getGitFiles ${webInterfaceDir} ${webInterfaceGitUrl} || \
-        { echo -e "  ${COL_LIGHT_RED}Unable to clone ${webInterfaceGitUrl} into ${webInterfaceDir}, exiting installer${COL_NC}"; \
-          exit 1; \
         }
-      fi
-  fi
+        # If the Web interface was installed,
+        if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
+            # get the Web git files
+            getGitFiles ${webInterfaceDir} ${webInterfaceGitUrl} || \
+            { echo -e "  ${COL_LIGHT_RED}Unable to clone ${webInterfaceGitUrl} into ${webInterfaceDir}, exiting installer${COL_NC}"; \
+            exit 1; \
+            }
+        fi
+    fi
 }
 
 # Download FTL binary to random temp directory and install FTL binary
 FTLinstall() {
-  # Local, named variables
-  local binary="${1}"
-  local latesttag
-  local str="Downloading and Installing FTL"
-  echo -ne "  ${INFO} ${str}..."
-
-  # Find the latest version tag for FTL
-  latesttag=$(curl -sI https://github.com/pi-hole/FTL/releases/latest | grep "Location" | awk -F '/' '{print $NF}')
-  # Tags should always start with v, check for that.
-  if [[ ! "${latesttag}" == v* ]]; then
-    echo -e "${OVER}  ${CROSS} ${str}"
-    echo -e "  ${COL_LIGHT_RED}Error: Unable to get latest release location from GitHub${COL_NC}"
-    return 1
-  fi
-
-  # Move into the temp ftl directory
-  pushd "$(mktemp -d)" || { echo "Unable to make temporary directory for FTL binary download"; return 1; }
-
-  # Always replace pihole-FTL.service
-  install -T -m 0755 "${PI_HOLE_LOCAL_REPO}/advanced/pihole-FTL.service" "/etc/init.d/pihole-FTL"
-
-  # If the download worked,
-  if curl -sSL --fail "https://github.com/pi-hole/FTL/releases/download/${latesttag%$'\r'}/${binary}" -o "${binary}"; then
-    # get sha1 of the binary we just downloaded for verification.
-    curl -sSL --fail "https://github.com/pi-hole/FTL/releases/download/${latesttag%$'\r'}/${binary}.sha1" -o "${binary}.sha1"
-
-    # If we downloaded binary file (as opposed to text),
-    if sha1sum --status --quiet -c "${binary}".sha1; then
-      echo -n "transferred... "
-      # Stop FTL
-      stop_service pihole-FTL &> /dev/null
-      # Install the new version with the correct permissions
-      install -T -m 0755 "${binary}" /usr/bin/pihole-FTL
-      # Move back into the original directory the user was in
-      popd || { echo "Unable to return to original directory after FTL binary download."; return 1; }
-      # Install the FTL service
-      echo -e "${OVER}  ${TICK} ${str}"
-      return 0
-    # Otherise,
+    # Local, named variables
+    local binary="${1}"
+    local latesttag
+    local str="Downloading and Installing FTL"
+    echo -ne "  ${INFO} ${str}..."
+
+    # Find the latest version tag for FTL
+    latesttag=$(curl -sI https://github.com/pi-hole/FTL/releases/latest | grep "Location" | awk -F '/' '{print $NF}')
+    # Tags should always start with v, check for that.
+    if [[ ! "${latesttag}" == v* ]]; then
+        echo -e "${OVER}  ${CROSS} ${str}"
+        echo -e "  ${COL_LIGHT_RED}Error: Unable to get latest release location from GitHub${COL_NC}"
+        return 1
+    fi
+
+    # Move into the temp ftl directory
+    pushd "$(mktemp -d)" > /dev/null || { echo "Unable to make temporary directory for FTL binary download"; return 1; }
+
+    # Always replace pihole-FTL.service
+    install -T -m 0755 "${PI_HOLE_LOCAL_REPO}/advanced/Templates/pihole-FTL.service" "/etc/init.d/pihole-FTL"
+
+    local ftlBranch
+    local url
+
+    if [[ -f "/etc/pihole/ftlbranch" ]];then
+        ftlBranch=$(</etc/pihole/ftlbranch)
     else
-      # the download failed, so just go back to the original directory
-      popd || { echo "Unable to return to original directory after FTL binary download."; return 1; }
-      echo -e "${OVER}  ${CROSS} ${str}"
-      echo -e "  ${COL_LIGHT_RED}Error: Download of binary from Github failed${COL_NC}"
-      return 1
-    fi
-  # Otherwise,
-  else
-    popd || { echo "Unable to return to original directory after FTL binary download."; return 1; }
-    echo -e "${OVER}  ${CROSS} ${str}"
-    # The URL could not be found
-    echo -e "  ${COL_LIGHT_RED}Error: URL not found${COL_NC}"
-    return 1
-  fi
-}
+        ftlBranch="master"
+    fi
 
-# Detect suitable FTL binary platform
-FTLdetect() {
-  echo ""
-  echo -e "  ${INFO} FTL Checks..."
+    # Determine which version of FTL to download
+    if [[ "${ftlBranch}" == "master" ]];then
+        url="https://github.com/pi-hole/FTL/releases/download/${latesttag%$'\r'}"
+    else
+        url="https://ftl.pi-hole.net/${ftlBranch}"
+    fi
 
-  # Local, named variables
-  local machine
-  local binary
+    # If the download worked,
+    if curl -sSL --fail "${url}/${binary}" -o "${binary}"; then
+        # get sha1 of the binary we just downloaded for verification.
+        curl -sSL --fail "${url}/${binary}.sha1" -o "${binary}.sha1"
+
+        # If we downloaded binary file (as opposed to text),
+        if sha1sum --status --quiet -c "${binary}".sha1; then
+            echo -n "transferred... "
+            # Stop FTL
+            stop_service pihole-FTL &> /dev/null
+            # Install the new version with the correct permissions
+            install -T -m 0755 "${binary}" /usr/bin/pihole-FTL
+            # Move back into the original directory the user was in
+            popd > /dev/null || { echo "Unable to return to original directory after FTL binary download."; return 1; }
+            # Install the FTL service
+            echo -e "${OVER}  ${TICK} ${str}"
+            # dnsmasq can now be stopped and disabled if it exists
+            if which dnsmasq &> /dev/null; then
+                if check_service_active "dnsmasq";then
+                    echo "  ${INFO} FTL can now resolve DNS Queries without dnsmasq running separately"
+                    stop_service dnsmasq
+                    disable_service dnsmasq
+                fi
+            fi
+
+            #ensure /etc/dnsmasq.conf contains `conf-dir=/etc/dnsmasq.d`
+            confdir="conf-dir=/etc/dnsmasq.d"
+            conffile="/etc/dnsmasq.conf"
+            if ! grep -q "$confdir" "$conffile"; then
+                echo "$confdir" >> "$conffile"
+            fi
+
+            return 0
+        # Otherwise,
+        else
+            # the download failed, so just go back to the original directory
+            popd > /dev/null || { echo "Unable to return to original directory after FTL binary download."; return 1; }
+            echo -e "${OVER}  ${CROSS} ${str}"
+            echo -e "  ${COL_LIGHT_RED}Error: Download of binary from Github failed${COL_NC}"
+            return 1
+        fi
+    # Otherwise,
+    else
+        popd > /dev/null || { echo "Unable to return to original directory after FTL binary download."; return 1; }
+        echo -e "${OVER}  ${CROSS} ${str}"
+        # The URL could not be found
+        echo -e "  ${COL_LIGHT_RED}Error: URL not found${COL_NC}"
+        return 1
+    fi
+}
 
-  # Store architecture in a variable
-  machine=$(uname -m)
+get_binary_name() {
+    # This gives the machine architecture which may be different from the OS architecture...
+    local machine
+    machine=$(uname -m)
 
-  local str="Detecting architecture"
-  echo -ne "  ${INFO} ${str}..."
-  # If the machine is arm or aarch
-  if [[ "${machine}" == "arm"* || "${machine}" == *"aarch"* ]]; then
-    # ARM
-    #
-    local rev
-    rev=$(uname -m | sed "s/[^0-9]//g;")
-    #
-    local lib
-    lib=$(ldd /bin/ls | grep -E '^\s*/lib' | awk '{ print $1 }')
-    #
-    if [[ "${lib}" == "/lib/ld-linux-aarch64.so.1" ]]; then
-      echo -e "${OVER}  ${TICK} Detected ARM-aarch64 architecture"
-      # set the binary to be used
-      binary="pihole-FTL-aarch64-linux-gnu"
-    #
-    elif [[ "${lib}" == "/lib/ld-linux-armhf.so.3" ]]; then
-      #
-      if [[ "${rev}" -gt 6 ]]; then
-        echo -e "${OVER}  ${TICK} Detected ARM-hf architecture (armv7+)"
-        # set the binary to be used
-        binary="pihole-FTL-arm-linux-gnueabihf"
-      # Otherwise,
-      else
-        echo -e "${OVER}  ${TICK} Detected ARM-hf architecture (armv6 or lower) Using ARM binary"
+    local str="Detecting architecture"
+    echo -ne "  ${INFO} ${str}..."
+    # If the machine is arm or aarch
+    if [[ "${machine}" == "arm"* || "${machine}" == *"aarch"* ]]; then
+        # ARM
+        #
+        local rev
+        rev=$(uname -m | sed "s/[^0-9]//g;")
+        #
+        local lib
+        lib=$(ldd /bin/ls | grep -E '^\s*/lib' | awk '{ print $1 }')
+        #
+        if [[ "${lib}" == "/lib/ld-linux-aarch64.so.1" ]]; then
+            echo -e "${OVER}  ${TICK} Detected ARM-aarch64 architecture"
+            # set the binary to be used
+            binary="pihole-FTL-aarch64-linux-gnu"
+        #
+        elif [[ "${lib}" == "/lib/ld-linux-armhf.so.3" ]]; then
+            #
+            if [[ "${rev}" -gt 6 ]]; then
+                echo -e "${OVER}  ${TICK} Detected ARM-hf architecture (armv7+)"
+                # set the binary to be used
+                binary="pihole-FTL-arm-linux-gnueabihf"
+            # Otherwise,
+            else
+                echo -e "${OVER}  ${TICK} Detected ARM-hf architecture (armv6 or lower) Using ARM binary"
+                # set the binary to be used
+                binary="pihole-FTL-arm-linux-gnueabi"
+            fi
+        else
+            echo -e "${OVER}  ${TICK} Detected ARM architecture"
+            # set the binary to be used
+            binary="pihole-FTL-arm-linux-gnueabi"
+        fi
+    elif [[ "${machine}" == "ppc" ]]; then
+        # PowerPC
+        echo -e "${OVER}  ${TICK} Detected PowerPC architecture"
         # set the binary to be used
-        binary="pihole-FTL-arm-linux-gnueabi"
-      fi
+        binary="pihole-FTL-powerpc-linux-gnu"
+    elif [[ "${machine}" == "x86_64" ]]; then
+        # This gives the architecture of packages dpkg installs (for example, "i386")
+        local dpkgarch
+        dpkgarch=$(dpkg --print-architecture 2> /dev/null)
+
+        # Special case: This is a 32 bit OS, installed on a 64 bit machine
+        # -> change machine architecture to download the 32 bit executable
+        if [[ "${dpkgarch}" == "i386" ]]; then
+            echo -e "${OVER}  ${TICK} Detected 32bit (i686) architecture"
+            binary="pihole-FTL-linux-x86_32"
+        else
+            # 64bit
+            echo -e "${OVER}  ${TICK} Detected x86_64 architecture"
+            # set the binary to be used
+            binary="pihole-FTL-linux-x86_64"
+        fi
     else
-      echo -e "${OVER}  ${TICK} Detected ARM architecture"
-      # set the binary to be used
-      binary="pihole-FTL-arm-linux-gnueabi"
-    fi
-  elif [[ "${machine}" == "ppc" ]]; then
-    # PowerPC
-    echo -e "${OVER}  ${TICK} Detected PowerPC architecture"
-    # set the binary to be used
-    binary="pihole-FTL-powerpc-linux-gnu"
-  elif [[ "${machine}" == "x86_64" ]]; then
-    # 64bit
-    echo -e "${OVER}  ${TICK} Detected x86_64 architecture"
-    # set the binary to be used
-    binary="pihole-FTL-linux-x86_64"
-  else
-    # Something else - we try to use 32bit executable and warn the user
-    if [[ ! "${machine}" == "i686" ]]; then
-      echo -e "${OVER}  ${CROSS} ${str}...
-      ${COL_LIGHT_RED}Not able to detect architecture (unknown: ${machine}), trying 32bit executable${COL_NC}
-      Contact Pi-hole Support if you experience issues (e.g: FTL not running)"
+        # Something else - we try to use 32bit executable and warn the user
+        if [[ ! "${machine}" == "i686" ]]; then
+            echo -e "${OVER}  ${CROSS} ${str}..."
+            echo -e "  ${INFO} ${COL_LIGHT_RED}Not able to detect architecture (unknown: ${machine}), trying 32bit executable${COL_NC}"
+            echo -e "  ${INFO} Contact Pi-hole Support if you experience issues (e.g: FTL not running)"
+        else
+            echo -e "${OVER}  ${TICK} Detected 32bit (i686) architecture"
+        fi
+        binary="pihole-FTL-linux-x86_32"
+    fi
+}
+
+FTLcheckUpdate() {
+    get_binary_name
+
+    #In the next section we check to see if FTL is already installed (in case of pihole -r).
+    #If the installed version matches the latest version, then check the installed sha1sum of the binary vs the remote sha1sum. If they do not match, then download
+    echo -e "  ${INFO} Checking for existing FTL binary..."
+
+    local ftlLoc
+    ftlLoc=$(which pihole-FTL 2>/dev/null)
+
+    local ftlBranch
+
+    if [[ -f "/etc/pihole/ftlbranch" ]];then
+        ftlBranch=$(</etc/pihole/ftlbranch)
     else
-      echo -e "${OVER}  ${TICK} Detected 32bit (i686) architecture"
-    fi
-    binary="pihole-FTL-linux-x86_32"
-  fi
-
-  #In the next section we check to see if FTL is already installed (in case of pihole -r).
-  #If the installed version matches the latest version, then check the installed sha1sum of the binary vs the remote sha1sum. If they do not match, then download
-  echo -e "  ${INFO} Checking for existing FTL binary..."
-
-  local ftlLoc=$(which pihole-FTL 2>/dev/null)
-
-  if [[ ${ftlLoc} ]]; then
-    local FTLversion=$(/usr/bin/pihole-FTL tag)
-	  local FTLlatesttag=$(curl -sI https://github.com/pi-hole/FTL/releases/latest | grep 'Location' | awk -F '/' '{print $NF}' | tr -d '\r\n')
-
-	  if [[ "${FTLversion}" != "${FTLlatesttag}" ]]; then
-		  # Install FTL
-      FTLinstall "${binary}" || return 1
-	  else
-	    echo -e "  ${INFO} Latest FTL Binary already installed (${FTLlatesttag}). Confirming Checksum..."
-
-	    local remoteSha1=$(curl -sSL --fail "https://github.com/pi-hole/FTL/releases/download/${FTLversion%$'\r'}/${binary}.sha1" | cut -d ' ' -f 1)
-	    local localSha1=$(sha1sum "$(which pihole-FTL)" | cut -d ' ' -f 1)
-
-	    if [[ "${remoteSha1}" != "${localSha1}" ]]; then
-	      echo -e "  ${INFO} Corruption detected..."
-	      FTLinstall "${binary}" || return 1
-	    else
-	      echo -e "  ${INFO} Checksum correct. No need to download!"
-	    fi
-	  fi
-	else
-	  # Install FTL
-    FTLinstall "${binary}" || return 1
-  fi
+        ftlBranch="master"
+    fi
+
+    local remoteSha1
+    local localSha1
+
+    # if dnsmasq exists and is running at this point, force reinstall of FTL Binary
+    if which dnsmasq &> /dev/null; then
+        if check_service_active "dnsmasq";then
+            return 0
+        fi
+    fi
+
+    if [[ ! "${ftlBranch}" == "master" ]]; then
+        #Check whether or not the binary for this FTL branch actually exists. If not, then there is no update!
+        local path
+        path="${ftlBranch}/${binary}"
+        # shellcheck disable=SC1090
+        if ! check_download_exists "$path"; then
+            echo -e "  ${INFO} Branch \"${ftlBranch}\" is not available.\\n  ${INFO} Use ${COL_LIGHT_GREEN}pihole checkout ftl [branchname]${COL_NC} to switch to a valid branch."
+            return 2
+        fi
+
+        if [[ ${ftlLoc} ]]; then
+            # We already have a pihole-FTL binary downloaded.
+            # Alt branches don't have a tagged version against them, so just confirm the checksum of the local vs remote to decide whether we download or not
+            remoteSha1=$(curl -sSL --fail "https://ftl.pi-hole.net/${ftlBranch}/${binary}.sha1" | cut -d ' ' -f 1)
+            localSha1=$(sha1sum "$(which pihole-FTL)" | cut -d ' ' -f 1)
+
+            if [[ "${remoteSha1}" != "${localSha1}" ]]; then
+                echo -e "  ${INFO} Checksums do not match, downloading from ftl.pi-hole.net."
+                return 0
+            else
+                echo -e "  ${INFO} Checksum of installed binary matches remote. No need to download!"
+                return 1
+            fi
+        else
+            return 0
+        fi
+    else
+        if [[ ${ftlLoc} ]]; then
+            local FTLversion
+            FTLversion=$(/usr/bin/pihole-FTL tag)
+            local FTLlatesttag
+            FTLlatesttag=$(curl -sI https://github.com/pi-hole/FTL/releases/latest | grep 'Location' | awk -F '/' '{print $NF}' | tr -d '\r\n')
+
+            if [[ "${FTLversion}" != "${FTLlatesttag}" ]]; then
+                return 0
+            else
+                echo -e "  ${INFO} Latest FTL Binary already installed (${FTLlatesttag}). Confirming Checksum..."
+
+                remoteSha1=$(curl -sSL --fail "https://github.com/pi-hole/FTL/releases/download/${FTLversion%$'\r'}/${binary}.sha1" | cut -d ' ' -f 1)
+                localSha1=$(sha1sum "$(which pihole-FTL)" | cut -d ' ' -f 1)
+
+                if [[ "${remoteSha1}" != "${localSha1}" ]]; then
+                    echo -e "  ${INFO} Corruption detected..."
+                    return 0
+                else
+                    echo -e "  ${INFO} Checksum correct. No need to download!"
+                    return 1
+                fi
+            fi
+        else
+            return 0
+        fi
+    fi
+}
+
+# Detect suitable FTL binary platform
+FTLdetect() {
+    echo ""
+    echo -e "  ${INFO} FTL Checks..."
+
+    if FTLcheckUpdate ; then
+        FTLinstall "${binary}" || return 1
+    fi
+
+    echo ""
 }
 
 make_temporary_log() {
-  # Create a random temporary file for the log
-  TEMPLOG=$(mktemp /tmp/pihole_temp.XXXXXX)
-  # Open handle 3 for templog
-  # https://stackoverflow.com/questions/18460186/writing-outputs-to-log-file-and-console
-  exec 3>"$TEMPLOG"
-  # Delete templog, but allow for addressing via file handle
-  # This lets us write to the log without having a temporary file on the drive, which
-  # is meant to be a security measure so there is not a lingering file on the drive during the install process
-  rm "$TEMPLOG"
+    # Create a random temporary file for the log
+    TEMPLOG=$(mktemp /tmp/pihole_temp.XXXXXX)
+    # Open handle 3 for templog
+    # https://stackoverflow.com/questions/18460186/writing-outputs-to-log-file-and-console
+    exec 3>"$TEMPLOG"
+    # Delete templog, but allow for addressing via file handle
+    # This lets us write to the log without having a temporary file on the drive, which
+    # is meant to be a security measure so there is not a lingering file on the drive during the install process
+    rm "$TEMPLOG"
 }
 
 copy_to_install_log() {
-  # Copy the contents of file descriptor 3 into the install log
-  # Since we use color codes such as '\e[1;33m', they should be removed
-  sed 's/\[[0-9;]\{1,5\}m//g' < /proc/$$/fd/3 > "${installLogLoc}"
+    # Copy the contents of file descriptor 3 into the install log
+    # Since we use color codes such as '\e[1;33m', they should be removed
+    sed 's/\[[0-9;]\{1,5\}m//g' < /proc/$$/fd/3 > "${installLogLoc}"
 }
 
 main() {
-  ######## FIRST CHECK ########
-  # Must be root to install
-  local str="Root user check"
-  echo ""
-
-  # If the user's id is zero,
-  if [[ "${EUID}" -eq 0 ]]; then
-    # they are root and all is good
-    echo -e "  ${TICK} ${str}"
-    # Show the Pi-hole logo so people know it's genuine since the logo and name are trademarked
-    show_ascii_berry
-    make_temporary_log
-  # Otherwise,
-  else
-    # They do not have enough privileges, so let the user know
-    echo -e "  ${CROSS} ${str}
-      ${COL_LIGHT_RED}Script called with non-root privileges${COL_NC}
-      The Pi-hole requires elevated privileges to install and run
-      Please check the installer for any concerns regarding this requirement
-      Make sure to download this script from a trusted source\\n"
-    echo -ne "  ${INFO} Sudo utility check"
-
-    # If the sudo command exists,
-    if command -v sudo &> /dev/null; then
-      echo -e "${OVER}  ${TICK} Sudo utility check"
-      # Download the install script and run it with admin rights
-      exec curl -sSL https://raw.githubusercontent.com/pi-hole/pi-hole/master/automated%20install/basic-install.sh | sudo bash "$@"
-      exit $?
+    ######## FIRST CHECK ########
+    # Must be root to install
+    local str="Root user check"
+    echo ""
+
+    # If the user's id is zero,
+    if [[ "${EUID}" -eq 0 ]]; then
+        # they are root and all is good
+        echo -e "  ${TICK} ${str}"
+        # Show the Pi-hole logo so people know it's genuine since the logo and name are trademarked
+        show_ascii_berry
+        make_temporary_log
     # Otherwise,
     else
-      # Let them know they need to run it as root
-      echo -e "${OVER}  ${CROSS} Sudo utility check
-      Sudo is needed for the Web Interface to run pihole commands\\n
-  ${COL_LIGHT_RED}Please re-run this installer as root${COL_NC}"
-      exit 1
+        # They do not have enough privileges, so let the user know
+        echo -e "  ${CROSS} ${str}"
+        echo -e "  ${INFO} ${COL_LIGHT_RED}Script called with non-root privileges${COL_NC}"
+        echo -e "  ${INFO} The Pi-hole requires elevated privileges to install and run"
+        echo -e "  ${INFO} Please check the installer for any concerns regarding this requirement"
+        echo -e "  ${INFO} Make sure to download this script from a trusted source\\n"
+        echo -ne "  ${INFO} Sudo utility check"
+
+        # If the sudo command exists,
+        if command -v sudo &> /dev/null; then
+            echo -e "${OVER}  ${TICK} Sudo utility check"
+            # Download the install script and run it with admin rights
+            exec curl -sSL https://raw.githubusercontent.com/pi-hole/pi-hole/master/automated%20install/basic-install.sh | sudo bash "$@"
+            exit $?
+        # Otherwise,
+        else
+            # Let them know they need to run it as root
+            echo -e "${OVER}  ${CROSS} Sudo utility check"
+            echo -e "  ${INFO} Sudo is needed for the Web Interface to run pihole commands\\n"
+            echo -e "  ${INFO} ${COL_LIGHT_RED}Please re-run this installer as root${COL_NC}"
+            exit 1
+        fi
     fi
-  fi
 
-  # Check for supported distribution
-  distro_check
+    # Check for supported distribution
+    distro_check
 
-  # Check arguments for the undocumented flags
-  for var in "$@"; do
-    case "$var" in
-      "--reconfigure" ) reconfigure=true;;
-      "--i_do_not_follow_recommendations" ) skipSpaceCheck=true;;
-      "--unattended" ) runUnattended=true;;
-    esac
-  done
-
-  # If the setup variable file exists,
-  if [[ -f "${setupVars}" ]]; then
-    # if it's running unattended,
-    if [[ "${runUnattended}" == true ]]; then
-      echo -e "  ${INFO} Performing unattended setup, no whiptail dialogs will be displayed"
-      # Use the setup variables
-      useUpdateVars=true
-    # Otherwise,
+    # If the setup variable file exists,
+    if [[ -f "${setupVars}" ]]; then
+        # if it's running unattended,
+        if [[ "${runUnattended}" == true ]]; then
+            echo -e "  ${INFO} Performing unattended setup, no whiptail dialogs will be displayed"
+            # Use the setup variables
+            useUpdateVars=true
+        # Otherwise,
+        else
+            # show the available options (repair/reconfigure)
+            update_dialogs
+        fi
+    fi
+
+    # Start the installer
+    # Verify there is enough disk space for the install
+    if [[ "${skipSpaceCheck}" == true ]]; then
+        echo -e "  ${INFO} Skipping free disk space verification"
+    else
+        verifyFreeDiskSpace
+    fi
+
+    # Update package cache
+    update_package_cache || exit 1
+
+    # Notify user of package availability
+    notify_package_updates_available
+
+    # Install packages used by this installation script
+    install_dependent_packages INSTALLER_DEPS[@]
+
+    # Check if SELinux is Enforcing
+    checkSelinux
+
+    if [[ "${useUpdateVars}" == false ]]; then
+        # Display welcome dialogs
+        welcomeDialogs
+        # Create directory for Pi-hole storage
+        mkdir -p /etc/pihole/
+        # Determine available interfaces
+        get_available_interfaces
+        # Find interfaces and let the user choose one
+        chooseInterface
+        # Decide what upstream DNS Servers to use
+        setDNS
+        # Give the user a choice of blocklists to include in their install. Or not.
+        chooseBlocklists
+        # Let the user decide if they want to block ads over IPv4 and/or IPv6
+        use4andor6
+        # Let the user decide if they want the web interface to be installed automatically
+        setAdminFlag
+        # Let the user decide if they want query logging enabled...
+        setLogging
     else
-      # show the available options (repair/reconfigure)
-      update_dialogs
-    fi
-  fi
-
-  # Start the installer
-  # Verify there is enough disk space for the install
-  if [[ "${skipSpaceCheck}" == true ]]; then
-    echo -e "  ${INFO} Skipping free disk space verification"
-  else
-    verifyFreeDiskSpace
-  fi
-
-  # Update package cache
-  update_package_cache || exit 1
-
-  # Notify user of package availability
-  notify_package_updates_available
-
-  # Install packages used by this installation script
-  install_dependent_packages INSTALLER_DEPS[@]
-
-   # Check if SELinux is Enforcing
-  checkSelinux
-
-  if [[ "${useUpdateVars}" == false ]]; then
-    # Display welcome dialogs
-    welcomeDialogs
-    # Create directory for Pi-hole storage
-    mkdir -p /etc/pihole/
-
-    stop_service dnsmasq
-    if [[ "${INSTALL_WEB}" == true ]]; then
-      stop_service lighttpd
-    fi
-    # Determine available interfaces
-    get_available_interfaces
-    # Find interfaces and let the user choose one
-    chooseInterface
-    # Decide what upstream DNS Servers to use
-    setDNS
-    # Let the user decide if they want to block ads over IPv4 and/or IPv6
-    use4andor6
-    # Let the user decide if they want the web interface to be installed automatically
-    setAdminFlag
-    # Let the user decide if they want query logging enabled...
-    setLogging
+        # Source ${setupVars} to use predefined user variables in the functions
+        source ${setupVars}
+    fi
     # Clone/Update the repos
     clone_or_update_repos
 
-    # Install packages used by the Pi-hole
-    if [[ "${INSTALL_WEB}" == true ]]; then
-      # Install the Web dependencies
-      DEPS=("${PIHOLE_DEPS[@]}" "${PIHOLE_WEB_DEPS[@]}")
-    # Otherwise,
-    else
-      # just install the Core dependencies
-      DEPS=("${PIHOLE_DEPS[@]}")
+    # Install the Core dependencies
+    local dep_install_list=("${PIHOLE_DEPS[@]}")
+    if [[ "${INSTALL_WEB_SERVER}" == true ]]; then
+        # Install the Web dependencies
+        dep_install_list+=("${PIHOLE_WEB_DEPS[@]}")
     fi
 
-    install_dependent_packages DEPS[@]
+    install_dependent_packages dep_install_list[@]
+    unset dep_install_list
 
     # On some systems, lighttpd is not enabled on first install. We need to enable it here if the user
     # has chosen to install the web interface, else the `LIGHTTPD_ENABLED` check will fail
-    if [[ "${INSTALL_WEB}" == true ]]; then
-      enable_service lighttpd
+    if [[ "${INSTALL_WEB_SERVER}" == true ]]; then
+        enable_service lighttpd
     fi
-
-    if [[ -x "$(command -v systemctl)" ]]; then
-      # Value will either be 1, if true, or 0
-      LIGHTTPD_ENABLED=$(systemctl is-enabled lighttpd | grep -c 'enabled' || true)
+    # Determine if lighttpd is correctly enabled
+    if check_service_active "lighttpd"; then
+        LIGHTTPD_ENABLED=true
     else
-      # Value will either be 1, if true, or 0
-      LIGHTTPD_ENABLED=$(service lighttpd status | awk '/Loaded:/ {print $0}' | grep -c 'enabled' || true)
+        LIGHTTPD_ENABLED=false
     fi
 
     # Install and log everything to a file
     installPihole | tee -a /proc/$$/fd/3
-  else
-    # Source ${setupVars} to use predefined user variables in the functions
-    source ${setupVars}
-
-    # Clone/Update the repos
-    clone_or_update_repos
 
-    # Install packages used by the Pi-hole
-    if [[ "${INSTALL_WEB}" == true ]]; then
-      # Install the Web dependencies
-      DEPS=("${PIHOLE_DEPS[@]}" "${PIHOLE_WEB_DEPS[@]}")
-    # Otherwise,
-    else
-      # just install the Core dependencies
-      DEPS=("${PIHOLE_DEPS[@]}")
+    # Copy the temp log file into final log location for storage
+    copy_to_install_log
+
+    if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
+        # Add password to web UI if there is none
+        pw=""
+        # If no password is set,
+        if [[ $(grep 'WEBPASSWORD' -c /etc/pihole/setupVars.conf) == 0 ]] ; then
+            # generate a random password
+            pw=$(tr -dc _A-Z-a-z-0-9 < /dev/urandom | head -c 8)
+            # shellcheck disable=SC1091
+            . /opt/pihole/webpage.sh
+            echo "WEBPASSWORD=$(HashPassword ${pw})" >> ${setupVars}
+        fi
     fi
-    install_dependent_packages DEPS[@]
 
-    if [[ -x "$(command -v systemctl)" ]]; then
-      # Value will either be 1, if true, or 0
-      LIGHTTPD_ENABLED=$(systemctl is-enabled lighttpd | grep -c 'enabled' || true)
-    else
-      # Value will either be 1, if true, or 0
-      LIGHTTPD_ENABLED=$(service lighttpd status | awk '/Loaded:/ {print $0}' | grep -c 'enabled' || true)
-    fi
-    updatePihole | tee -a /proc/$$/fd/3
-  fi
-
-  # Copy the temp log file into final log location for storage
-  copy_to_install_log
-
-  if [[ "${INSTALL_WEB}" == true ]]; then
-    # Add password to web UI if there is none
-    pw=""
-    # If no password is set,
-    if [[ $(grep 'WEBPASSWORD' -c /etc/pihole/setupVars.conf) == 0 ]] ; then
-        # generate a random password
-        pw=$(tr -dc _A-Z-a-z-0-9 < /dev/urandom | head -c 8)
-        # shellcheck disable=SC1091
-        . /opt/pihole/webpage.sh
-        echo "WEBPASSWORD=$(HashPassword ${pw})" >> ${setupVars}
-    fi
-  fi
-
-  echo -e "  ${INFO} Restarting services..."
-  # Start services
-  start_service dnsmasq
-  enable_service dnsmasq
-
-  # If the Web server was installed,
-  if [[ "${INSTALL_WEB}" == true ]]; then
-
-    if [[ "${LIGHTTPD_ENABLED}" == "1" ]]; then
-      start_service lighttpd
-      enable_service lighttpd
-    else
-      echo -e "  ${INFO} Lighttpd is disabled, skipping service restart"
+    # Check for and disable systemd-resolved-DNSStubListener before reloading resolved
+    # DNSStubListener needs to remain in place for installer to download needed files,
+    # so this change needs to be made after installation is complete,
+    # but before starting or resarting the dnsmasq or ftl services
+    disable_resolved_stublistener
+
+    # If the Web server was installed,
+    if [[ "${INSTALL_WEB_SERVER}" == true ]]; then
+
+        if [[ "${LIGHTTPD_ENABLED}" == true ]]; then
+            start_service lighttpd
+            enable_service lighttpd
+        else
+            echo -e "  ${INFO} Lighttpd is disabled, skipping service restart"
+        fi
     fi
-  fi
 
-  # Enable FTL
-  start_service pihole-FTL
-  enable_service pihole-FTL
+    echo -e "  ${INFO} Restarting services..."
+    # Start services
 
-  # Download and compile the aggregated block list
-  runGravity
+    # Enable FTL
+    start_service pihole-FTL
+    enable_service pihole-FTL
 
-  # Force an update of the updatechecker
-  . /opt/pihole/updatecheck.sh
-  . /opt/pihole/updatecheck.sh x remote
+    # Download and compile the aggregated block list
+    runGravity
 
-  #
-  if [[ "${useUpdateVars}" == false ]]; then
-      displayFinalMessage "${pw}"
-  fi
+    # Force an update of the updatechecker
+    /opt/pihole/updatecheck.sh
+    /opt/pihole/updatecheck.sh x remote
 
-  # If the Web interface was installed,
-  if [[ "${INSTALL_WEB}" == true ]]; then
-    # If there is a password,
-    if (( ${#pw} > 0 )) ; then
-      # display the password
-      echo -e "  ${INFO} Web Interface password: ${COL_LIGHT_GREEN}${pw}${COL_NC}
-      This can be changed using 'pihole -a -p'\\n"
+    if [[ "${useUpdateVars}" == false ]]; then
+        displayFinalMessage "${pw}"
     fi
-  fi
 
-  #
-  if [[ "${useUpdateVars}" == false ]]; then
     # If the Web interface was installed,
-    if [[ "${INSTALL_WEB}" == true ]]; then
-      echo -e "  View the web interface at http://pi.hole/admin or http://${IPV4_ADDRESS%/*}/admin"
-      echo ""
-    fi
-    # Explain to the user how to use Pi-hole as their DNS server
-    echo "  You may now configure your devices to use the Pi-hole as their DNS server"
-    [[ -n "${IPV4_ADDRESS%/*}" ]] && echo -e "  ${INFO} Pi-hole DNS (IPv4): ${IPV4_ADDRESS%/*}"
-    [[ -n "${IPV6_ADDRESS}" ]] && echo -e "  ${INFO} Pi-hole DNS (IPv6): ${IPV6_ADDRESS}"
-    echo -e "  If you set a new IP address, please restart the server running the Pi-hole"
-    #
-    INSTALL_TYPE="Installation"
-  else
-    #
-    INSTALL_TYPE="Update"
-  fi
+    if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
+        # If there is a password,
+        if (( ${#pw} > 0 )) ; then
+            # display the password
+            echo -e "  ${INFO} Web Interface password: ${COL_LIGHT_GREEN}${pw}${COL_NC}"
+            echo -e "  ${INFO} This can be changed using 'pihole -a -p'\\n"
+        fi
+    fi
 
-  # Display where the log file is
-  echo -e "\\n  ${INFO} The install log is located at: ${installLogLoc}
-  ${COL_LIGHT_GREEN}${INSTALL_TYPE} Complete! ${COL_NC}"
+    if [[ "${useUpdateVars}" == false ]]; then
+        # If the Web interface was installed,
+        if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
+            echo -e "  ${INFO} View the web interface at http://pi.hole/admin or http://${IPV4_ADDRESS%/*}/admin"
+            echo ""
+        fi
+        # Explain to the user how to use Pi-hole as their DNS server
+        echo -e "  ${INFO} You may now configure your devices to use the Pi-hole as their DNS server"
+        [[ -n "${IPV4_ADDRESS%/*}" ]] && echo -e "  ${INFO} Pi-hole DNS (IPv4): ${IPV4_ADDRESS%/*}"
+        [[ -n "${IPV6_ADDRESS}" ]] && echo -e "  ${INFO} Pi-hole DNS (IPv6): ${IPV6_ADDRESS}"
+        echo -e "  ${INFO} If you set a new IP address, please restart the server running the Pi-hole"
+        INSTALL_TYPE="Installation"
+    else
+        INSTALL_TYPE="Update"
+    fi
 
+    # Display where the log file is
+    echo -e "\\n  ${INFO} The install log is located at: ${installLogLoc}
+    ${COL_LIGHT_GREEN}${INSTALL_TYPE} Complete! ${COL_NC}"
+
+    if [[ "${INSTALL_TYPE}" == "Update" ]]; then
+        echo ""
+        /usr/local/bin/pihole version --current
+    fi
 }
 
-#
 if [[ "${PH_TEST}" != true ]] ; then
-  main "$@"
+    main "$@"
 fi
diff --git a/automated install/uninstall.sh b/automated install/uninstall.sh
index 2f4f4f9f90c6fba60c9b538a052112ef992bc413..9322de921ff2594f5ed9c1212c89c6e90e308d5c 100755
--- a/automated install/uninstall.sh	
+++ b/automated install/uninstall.sh	
@@ -11,29 +11,29 @@
 source "/opt/pihole/COL_TABLE"
 
 while true; do
-	read -rp "  ${QST} Are you sure you would like to remove ${COL_WHITE}Pi-hole${COL_NC}? [y/N] " yn
-	case ${yn} in
-		[Yy]* ) break;;
-		[Nn]* ) echo -e "\n  ${COL_LIGHT_GREEN}Uninstall has been cancelled${COL_NC}"; exit 0;;
-    * ) echo -e "\n  ${COL_LIGHT_GREEN}Uninstall has been cancelled${COL_NC}"; exit 0;;
-	esac
+    read -rp "  ${QST} Are you sure you would like to remove ${COL_WHITE}Pi-hole${COL_NC}? [y/N] " yn
+    case ${yn} in
+        [Yy]* ) break;;
+        [Nn]* ) echo -e "${OVER}  ${COL_LIGHT_GREEN}Uninstall has been cancelled${COL_NC}"; exit 0;;
+        * ) echo -e "${OVER}  ${COL_LIGHT_GREEN}Uninstall has been cancelled${COL_NC}"; exit 0;;
+    esac
 done
 
 # Must be root to uninstall
 str="Root user check"
 if [[ ${EUID} -eq 0 ]]; then
-	echo -e "  ${TICK} ${str}"
+    echo -e "  ${TICK} ${str}"
 else
-	# Check if sudo is actually installed
-	# If it isn't, exit because the uninstall can not complete
-	if [ -x "$(command -v sudo)" ]; then
-		export SUDO="sudo"
-	else
-    echo -e "  ${CROSS} ${str}
-       Script called with non-root privileges
-       The Pi-hole requires elevated privleges to uninstall"
-		exit 1
-	fi
+    # Check if sudo is actually installed
+    # If it isn't, exit because the uninstall can not complete
+    if [ -x "$(command -v sudo)" ]; then
+        export SUDO="sudo"
+    else
+        echo -e "  ${CROSS} ${str}
+            Script called with non-root privileges
+            The Pi-hole requires elevated privleges to uninstall"
+        exit 1
+    fi
 fi
 
 readonly PI_HOLE_FILES_DIR="/etc/.pihole"
@@ -46,178 +46,172 @@ source "${setupVars}"
 distro_check
 
 # Install packages used by the Pi-hole
-if [[ "${INSTALL_WEB}" == true ]]; then
-  # Install the Web dependencies
-  DEPS=("${INSTALLER_DEPS[@]}" "${PIHOLE_DEPS[@]}" "${PIHOLE_WEB_DEPS[@]}")
-# Otherwise,
-else
-  # just install the Core dependencies
-  DEPS=("${INSTALLER_DEPS[@]}" "${PIHOLE_DEPS[@]}")
+DEPS=("${INSTALLER_DEPS[@]}" "${PIHOLE_DEPS[@]}")
+if [[ "${INSTALL_WEB_SERVER}" == true ]]; then
+    # Install the Web dependencies
+    DEPS+=("${PIHOLE_WEB_DEPS[@]}")
 fi
 
 # Compatability
-if [ -x "$(command -v rpm)" ]; then
-	# Fedora Family
-	PKG_REMOVE="${PKG_MANAGER} remove -y"
-	package_check() {
-		rpm -qa | grep ^$1- > /dev/null
-	}
-	package_cleanup() {
-		${SUDO} ${PKG_MANAGER} -y autoremove
-	}
-elif [ -x "$(command -v apt-get)" ]; then
-	# Debian Family
-	PKG_REMOVE="${PKG_MANAGER} -y remove --purge"
-	package_check() {
-		dpkg-query -W -f='${Status}' "$1" 2>/dev/null | grep -c "ok installed"
-	}
-	package_cleanup() {
-		${SUDO} ${PKG_MANAGER} -y autoremove
-		${SUDO} ${PKG_MANAGER} -y autoclean
-	}
+if [ -x "$(command -v apt-get)" ]; then
+    # Debian Family
+    PKG_REMOVE="${PKG_MANAGER} -y remove --purge"
+    package_check() {
+        dpkg-query -W -f='${Status}' "$1" 2>/dev/null | grep -c "ok installed"
+    }
+elif [ -x "$(command -v rpm)" ]; then
+    # Fedora Family
+    PKG_REMOVE="${PKG_MANAGER} remove -y"
+    package_check() {
+        rpm -qa | grep "^$1-" > /dev/null
+    }
 else
-  echo -e "  ${CROSS} OS distribution not supported"
-	exit 1
+    echo -e "  ${CROSS} OS distribution not supported"
+    exit 1
 fi
 
 removeAndPurge() {
-	# Purge dependencies
-  echo ""
-	for i in "${DEPS[@]}"; do
-		package_check ${i} > /dev/null
-		if [[ "$?" -eq 0 ]]; then
-			while true; do
-				read -rp "  ${QST} Do you wish to remove ${COL_WHITE}${i}${COL_NC} from your system? [Y/N] " yn
-				case ${yn} in
-					[Yy]* )
-            echo -ne "  ${INFO} Removing ${i}...";
-            ${SUDO} ${PKG_REMOVE} "${i}" &> /dev/null;
-            echo -e "${OVER}  ${INFO} Removed ${i}";
-            break;;
-					[Nn]* ) echo -e "  ${INFO} Skipped ${i}"; break;;
-				esac
-			done
-		else
-			echo -e "  ${INFO} Package ${i} not installed"
-		fi
-	done
-
-	# Remove dnsmasq config files
-	${SUDO} rm -f /etc/dnsmasq.conf /etc/dnsmasq.conf.orig /etc/dnsmasq.d/01-pihole.conf &> /dev/null
-  echo -e "  ${TICK} Removing dnsmasq config files"
-
-	# Take care of any additional package cleaning
-	echo -ne "  ${INFO} Removing & cleaning remaining dependencies..."
-	package_cleanup &> /dev/null
-  echo -e "${OVER}  ${TICK} Removed & cleaned up remaining dependencies"
-
-	# Call removeNoPurge to remove Pi-hole specific files
-	removeNoPurge
+    # Purge dependencies
+    echo ""
+    for i in "${DEPS[@]}"; do
+        if package_check "${i}" > /dev/null; then
+            while true; do
+                read -rp "  ${QST} Do you wish to remove ${COL_WHITE}${i}${COL_NC} from your system? [Y/N] " yn
+                case ${yn} in
+                    [Yy]* )
+                        echo -ne "  ${INFO} Removing ${i}...";
+                        ${SUDO} "${PKG_REMOVE} ${i}" &> /dev/null;
+                        echo -e "${OVER}  ${INFO} Removed ${i}";
+                        break;;
+                    [Nn]* ) echo -e "  ${INFO} Skipped ${i}"; break;;
+                esac
+            done
+        else
+            echo -e "  ${INFO} Package ${i} not installed"
+        fi
+    done
+
+    # Remove dnsmasq config files
+    ${SUDO} rm -f /etc/dnsmasq.conf /etc/dnsmasq.conf.orig /etc/dnsmasq.d/*-pihole*.conf &> /dev/null
+    echo -e "  ${TICK} Removing dnsmasq config files"
+
+    # Call removeNoPurge to remove Pi-hole specific files
+    removeNoPurge
 }
 
 removeNoPurge() {
-	# Only web directories/files that are created by Pi-hole should be removed
-	echo -ne "  ${INFO} Removing Web Interface..."
-	${SUDO} rm -rf /var/www/html/admin &> /dev/null
-	${SUDO} rm -rf /var/www/html/pihole &> /dev/null
-	${SUDO} rm -f /var/www/html/index.lighttpd.orig &> /dev/null
-
-	# If the web directory is empty after removing these files, then the parent html folder can be removed.
-	if [ -d "/var/www/html" ]; then
-		if [[ ! "$(ls -A /var/www/html)" ]]; then
-    			${SUDO} rm -rf /var/www/html &> /dev/null
-		fi
-	fi
-  echo -e "${OVER}  ${TICK} Removed Web Interface"
-
-	# Attempt to preserve backwards compatibility with older versions
-	# to guarantee no additional changes were made to /etc/crontab after
-	# the installation of pihole, /etc/crontab.pihole should be permanently
-	# preserved.
-	if [[ -f /etc/crontab.orig ]]; then
-		${SUDO} mv /etc/crontab /etc/crontab.pihole
-		${SUDO} mv /etc/crontab.orig /etc/crontab
-		${SUDO} service cron restart
-    echo -e "  ${TICK} Restored the default system cron"
-	fi
-
-	# Attempt to preserve backwards compatibility with older versions
-	if [[ -f /etc/cron.d/pihole ]];then
-		${SUDO} rm -f /etc/cron.d/pihole &> /dev/null
-    echo -e "  ${TICK} Removed /etc/cron.d/pihole"
-	fi
-
-	package_check lighttpd > /dev/null
-	if [[ $? -eq 1 ]]; then
-		${SUDO} rm -rf /etc/lighttpd/ &> /dev/null
-    	echo -e "  ${TICK} Removed lighttpd"
-	else
-		if [ -f /etc/lighttpd/lighttpd.conf.orig ]; then
-			${SUDO} mv /etc/lighttpd/lighttpd.conf.orig /etc/lighttpd/lighttpd.conf
-		fi
-	fi
-
-	${SUDO} rm -f /etc/dnsmasq.d/adList.conf &> /dev/null
-	${SUDO} rm -f /etc/dnsmasq.d/01-pihole.conf &> /dev/null
-	${SUDO} rm -rf /var/log/*pihole* &> /dev/null
-	${SUDO} rm -rf /etc/pihole/ &> /dev/null
-	${SUDO} rm -rf /etc/.pihole/ &> /dev/null
-	${SUDO} rm -rf /opt/pihole/ &> /dev/null
-	${SUDO} rm -f /usr/local/bin/pihole &> /dev/null
-	${SUDO} rm -f /etc/bash_completion.d/pihole &> /dev/null
-	${SUDO} rm -f /etc/sudoers.d/pihole &> /dev/null
-  echo -e "  ${TICK} Removed config files"
-
-  # Remove FTL
-  if command -v pihole-FTL &> /dev/null; then
-    echo -ne "  ${INFO} Removing pihole-FTL..."
-
-    if [[ -x "$(command -v systemctl)" ]]; then
-      systemctl stop pihole-FTL
-    else
-      service pihole-FTL stop
+    # Only web directories/files that are created by Pi-hole should be removed
+    echo -ne "  ${INFO} Removing Web Interface..."
+    ${SUDO} rm -rf /var/www/html/admin &> /dev/null
+    ${SUDO} rm -rf /var/www/html/pihole &> /dev/null
+    ${SUDO} rm -f /var/www/html/index.lighttpd.orig &> /dev/null
+
+    # If the web directory is empty after removing these files, then the parent html folder can be removed.
+    if [ -d "/var/www/html" ]; then
+        if [[ ! "$(ls -A /var/www/html)" ]]; then
+            ${SUDO} rm -rf /var/www/html &> /dev/null
+        fi
+    fi
+    echo -e "${OVER}  ${TICK} Removed Web Interface"
+ 
+    # Attempt to preserve backwards compatibility with older versions
+    # to guarantee no additional changes were made to /etc/crontab after
+    # the installation of pihole, /etc/crontab.pihole should be permanently
+    # preserved.
+    if [[ -f /etc/crontab.orig ]]; then
+        ${SUDO} mv /etc/crontab /etc/crontab.pihole
+        ${SUDO} mv /etc/crontab.orig /etc/crontab
+        ${SUDO} service cron restart
+        echo -e "  ${TICK} Restored the default system cron"
     fi
 
-    ${SUDO} rm -f /etc/init.d/pihole-FTL
-    ${SUDO} rm -f /usr/bin/pihole-FTL
-    echo -e "${OVER}  ${TICK} Removed pihole-FTL"
-  fi
+    # Attempt to preserve backwards compatibility with older versions
+    if [[ -f /etc/cron.d/pihole ]];then
+        ${SUDO} rm -f /etc/cron.d/pihole &> /dev/null
+        echo -e "  ${TICK} Removed /etc/cron.d/pihole"
+    fi
 
-	# If the pihole user exists, then remove
-	if id "pihole" &> /dev/null; then
-		${SUDO} userdel -r pihole 2> /dev/null
-    if [[ "$?" -eq 0 ]]; then
-      echo -e "  ${TICK} Removed 'pihole' user"
+    package_check lighttpd > /dev/null
+    if [[ $? -eq 1 ]]; then
+        ${SUDO} rm -rf /etc/lighttpd/ &> /dev/null
+        echo -e "  ${TICK} Removed lighttpd"
     else
-      echo -e "  ${CROSS} Unable to remove 'pihole' user"
+        if [ -f /etc/lighttpd/lighttpd.conf.orig ]; then
+            ${SUDO} mv /etc/lighttpd/lighttpd.conf.orig /etc/lighttpd/lighttpd.conf
+        fi
+    fi
+
+    ${SUDO} rm -f /etc/dnsmasq.d/adList.conf &> /dev/null
+    ${SUDO} rm -f /etc/dnsmasq.d/01-pihole.conf &> /dev/null
+    ${SUDO} rm -rf /var/log/*pihole* &> /dev/null
+    ${SUDO} rm -rf /etc/pihole/ &> /dev/null
+    ${SUDO} rm -rf /etc/.pihole/ &> /dev/null
+    ${SUDO} rm -rf /opt/pihole/ &> /dev/null
+    ${SUDO} rm -f /usr/local/bin/pihole &> /dev/null
+    ${SUDO} rm -f /etc/bash_completion.d/pihole &> /dev/null
+    ${SUDO} rm -f /etc/sudoers.d/pihole &> /dev/null
+    echo -e "  ${TICK} Removed config files"
+
+    # Restore Resolved
+    if [[ -e /etc/systemd/resolved.conf.orig ]]; then
+        ${SUDO} cp /etc/systemd/resolved.conf.orig /etc/systemd/resolved.conf
+        systemctl reload-or-restart systemd-resolved
+    fi
+
+    # Remove FTL
+    if command -v pihole-FTL &> /dev/null; then
+        echo -ne "  ${INFO} Removing pihole-FTL..."
+        if [[ -x "$(command -v systemctl)" ]]; then
+            systemctl stop pihole-FTL
+        else
+            service pihole-FTL stop
+        fi
+        ${SUDO} rm -f /etc/init.d/pihole-FTL
+        ${SUDO} rm -f /usr/bin/pihole-FTL
+        echo -e "${OVER}  ${TICK} Removed pihole-FTL"
+    fi
+
+    # If the pihole manpage exists, then delete and rebuild man-db
+    if [[ -f /usr/local/share/man/man8/pihole.8 ]]; then
+        ${SUDO} rm -f /usr/local/share/man/man8/pihole.8 /usr/local/share/man/man8/pihole-FTL.8 /usr/local/share/man/man5/pihole-FTL.conf.5
+        ${SUDO} mandb -q &>/dev/null
+        echo -e "  ${TICK} Removed pihole man page"
+    fi
+
+    # If the pihole user exists, then remove
+    if id "pihole" &> /dev/null; then
+        if ${SUDO} userdel -r pihole 2> /dev/null; then
+            echo -e "  ${TICK} Removed 'pihole' user"
+        else
+            echo -e "  ${CROSS} Unable to remove 'pihole' user"
+        fi
     fi
-	fi
 
-  echo -e "\n   We're sorry to see you go, but thanks for checking out Pi-hole!
-   If you need help, reach out to us on Github, Discourse, Reddit or Twitter
-   Reinstall at any time: ${COL_WHITE}curl -sSL https://install.pi-hole.net | bash${COL_NC}
+    echo -e "\\n   We're sorry to see you go, but thanks for checking out Pi-hole!
+       If you need help, reach out to us on Github, Discourse, Reddit or Twitter
+       Reinstall at any time: ${COL_WHITE}curl -sSL https://install.pi-hole.net | bash${COL_NC}
 
-  ${COL_LIGHT_RED}Please reset the DNS on your router/clients to restore internet connectivity
-  ${COL_LIGHT_GREEN}Uninstallation Complete! ${COL_NC}"
+      ${COL_LIGHT_RED}Please reset the DNS on your router/clients to restore internet connectivity
+      ${COL_LIGHT_GREEN}Uninstallation Complete! ${COL_NC}"
 }
 
 ######### SCRIPT ###########
 if command -v vcgencmd &> /dev/null; then
-  echo -e "  ${INFO} All dependencies are safe to remove on Raspbian"
+    echo -e "  ${INFO} All dependencies are safe to remove on Raspbian"
 else
-  echo -e "  ${INFO} Be sure to confirm if any dependencies should not be removed"
+    echo -e "  ${INFO} Be sure to confirm if any dependencies should not be removed"
 fi
 while true; do
-  echo -e "  ${INFO} ${COL_YELLOW}The following dependencies may have been added by the Pi-hole install:"
-  echo -n "    "
-  for i in "${DEPS[@]}"; do
-    echo -n "${i} "
-  done
-  echo "${COL_NC}"
-	read -rp "  ${QST} Do you wish to go through each dependency for removal? (Choosing No will leave all dependencies installed) [Y/n] " yn
-	case ${yn} in
-		[Yy]* ) removeAndPurge; break;;
-		[Nn]* ) removeNoPurge; break;;
-    * ) removeAndPurge; break;;
-	esac
+    echo -e "  ${INFO} ${COL_YELLOW}The following dependencies may have been added by the Pi-hole install:"
+    echo -n "    "
+    for i in "${DEPS[@]}"; do
+        echo -n "${i} "
+    done
+    echo "${COL_NC}"
+    read -rp "  ${QST} Do you wish to go through each dependency for removal? (Choosing No will leave all dependencies installed) [Y/n] " yn
+    case ${yn} in
+        [Yy]* ) removeAndPurge; break;;
+        [Nn]* ) removeNoPurge; break;;
+        * ) removeAndPurge; break;;
+    esac
 done
diff --git a/gravity.sh b/gravity.sh
index 395ea54873e9d2074d80f8747960efee4e960593..50c37784797e3861ce8fec0ed07adfb09dd59eff 100755
--- a/gravity.sh
+++ b/gravity.sh
@@ -15,20 +15,20 @@ export LC_ALL=C
 
 coltable="/opt/pihole/COL_TABLE"
 source "${coltable}"
+regexconverter="/opt/pihole/wildcard_regex_converter.sh"
+source "${regexconverter}"
 
 basename="pihole"
 PIHOLE_COMMAND="/usr/local/bin/${basename}"
 
 piholeDir="/etc/${basename}"
-piholeRepo="/etc/.${basename}"
 
 adListFile="${piholeDir}/adlists.list"
 adListDefault="${piholeDir}/adlists.default"
-adListRepoDefault="${piholeRepo}/adlists.default"
 
 whitelistFile="${piholeDir}/whitelist.txt"
 blacklistFile="${piholeDir}/blacklist.txt"
-wildcardFile="/etc/dnsmasq.d/03-pihole-wildcard.conf"
+regexFile="${piholeDir}/regex.list"
 
 adList="${piholeDir}/gravity.list"
 blackList="${piholeDir}/black.list"
@@ -44,6 +44,10 @@ preEventHorizon="list.preEventHorizon"
 
 skipDownload="false"
 
+resolver="pihole-FTL"
+
+haveSourceUrls=true
+
 # Source setupVars from install script
 setupVars="${piholeDir}/setupVars.conf"
 if [[ -f "${setupVars}" ]];then
@@ -104,7 +108,7 @@ gravity_CheckDNSResolutionAvailable() {
   fi
 
   # Determine error output message
-  if pidof dnsmasq &> /dev/null; then
+  if pidof ${resolver} &> /dev/null; then
     echo -e "  ${CROSS} DNS resolution is currently unavailable"
   else
     echo -e "  ${CROSS} DNS service is not running"
@@ -129,20 +133,12 @@ gravity_CheckDNSResolutionAvailable() {
 gravity_GetBlocklistUrls() {
   echo -e "  ${INFO} ${COL_BOLD}Neutrino emissions detected${COL_NC}..."
 
-  # Determine if adlists file needs handling
-  if [[ ! -f "${adListFile}" ]]; then
-    # Create "adlists.list" by copying "adlists.default" from internal core repo
-    cp "${adListRepoDefault}" "${adListFile}" 2> /dev/null || \
-      echo -e "  ${CROSS} Unable to copy ${adListFile##*/} from ${piholeRepo}"
-  elif [[ -f "${adListDefault}" ]] && [[ -f "${adListFile}" ]]; then
+  if [[ -f "${adListDefault}" ]] && [[ -f "${adListFile}" ]]; then
     # Remove superceded $adListDefault file
     rm "${adListDefault}" 2> /dev/null || \
       echo -e "  ${CROSS} Unable to remove ${adListDefault}"
   fi
 
-  local str="Pulling blocklist source list into range"
-  echo -ne "  ${INFO} ${str}..."
-
   # Retrieve source URLs from $adListFile
   # Logic: Remove comments and empty lines
   mapfile -t sources <<< "$(grep -v -E "^(#|$)" "${adListFile}" 2> /dev/null)"
@@ -158,11 +154,15 @@ gravity_GetBlocklistUrls() {
     }' <<< "$(printf '%s\n' "${sources[@]}")" 2> /dev/null
   )"
 
+  local str="Pulling blocklist source list into range"
+
   if [[ -n "${sources[*]}" ]] && [[ -n "${sourceDomains[*]}" ]]; then
     echo -e "${OVER}  ${TICK} ${str}"
   else
     echo -e "${OVER}  ${CROSS} ${str}"
-    gravity_Cleanup "error"
+    echo -e "  ${INFO} No source list found, or it is empty"
+    echo ""
+    haveSourceUrls=false
   fi
 }
 
@@ -220,8 +220,15 @@ gravity_DownloadBlocklistFromUrl() {
   httpCode=$(curl -s -L ${cmd_ext} ${heisenbergCompensator} -w "%{http_code}" -A "${agent}" "${url}" -o "${patternBuffer}" 2> /dev/null)
 
   case $url in
+    # Did we "download" a local file?
+    "file"*)
+        if [[ -s "${patternBuffer}" ]]; then
+          echo -e "${OVER}  ${TICK} ${str} Retrieval successful"; success=true
+        else
+          echo -e "${OVER}  ${CROSS} ${str} Not found / empty list"
+        fi;;
     # Did we "download" a remote file?
-    "http"*)
+    *)
       # Determine "Status:" output based on HTTP response
       case "${httpCode}" in
         "200") echo -e "${OVER}  ${TICK} ${str} Retrieval successful"; success=true;;
@@ -235,16 +242,8 @@ gravity_DownloadBlocklistFromUrl() {
         "504") echo -e "${OVER}  ${CROSS} ${str} Connection Timed Out (Gateway)";;
         "521") echo -e "${OVER}  ${CROSS} ${str} Web Server Is Down (Cloudflare)";;
         "522") echo -e "${OVER}  ${CROSS} ${str} Connection Timed Out (Cloudflare)";;
-        *    ) echo -e "${OVER}  ${CROSS} ${str} ${httpCode}";;
+        *    ) echo -e "${OVER}  ${CROSS} ${str} ${url} (${httpCode})";;
       esac;;
-    # Did we "download" a local file?
-    "file"*)
-        if [[ -s "${patternBuffer}" ]]; then
-          echo -e "${OVER}  ${TICK} ${str} Retrieval successful"; success=true
-        else
-          echo -e "${OVER}  ${CROSS} ${str} Not found / empty list"
-        fi;;
-    *) echo -e "${OVER}  ${CROSS} ${str} ${url} ${httpCode}";;
   esac
 
   # Determine if the blocklist was downloaded and saved correctly
@@ -279,9 +278,9 @@ gravity_ParseFileIntoDomains() {
     # Most of the lists downloaded are already in hosts file format but the spacing/formating is not contigious
     # This helps with that and makes it easier to read
     # It also helps with debugging so each stage of the script can be researched more in depth
-    #Awk -F splits on given IFS, we grab the right hand side (chops trailing #coments and /'s to grab the domain only.
-    #Last awk command takes non-commented lines and if they have 2 fields, take the left field (the domain) and leave
-    #+ the right (IP address), otherwise grab the single field.
+    # Awk -F splits on given IFS, we grab the right hand side (chops trailing #coments and /'s to grab the domain only.
+    # Last awk command takes non-commented lines and if they have 2 fields, take the right field (the domain) and leave
+    # the left (IP address), otherwise grab the single field.
 
     < ${source} awk -F '#' '{print $1}' | \
     awk -F '/' '{print $1}' | \
@@ -345,13 +344,18 @@ gravity_ParseFileIntoDomains() {
     # Scanning for "^IPv4$" is too slow with large (1M) lists on low-end hardware
     echo -ne "  ${INFO} Format: URL"
 
-    awk '{
-      # Remove URL protocol, optional "username:password@", and ":?/;"
-      if ($0 ~ /[:?\/;]/) { gsub(/(^.*:\/\/(.*:.*@)?|[:?\/;].*)/, "", $0) }
-      # Remove lines which are only IPv4 addresses
-      if ($0 ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) { $0="" }
-      if ($0) { print $0 }
-    }' "${source}" 2> /dev/null > "${destination}"
+    awk '
+      # Remove URL scheme, optional "username:password@", and ":?/;"
+      # The scheme must be matched carefully to avoid blocking the wrong URL
+      # in cases like:
+      #   http://www.evil.com?http://www.good.com
+      # See RFC 3986 section 3.1 for details.
+      /[:?\/;]/ { gsub(/(^[a-zA-Z][a-zA-Z0-9+.-]*:\/\/(.*:.*@)?|[:?\/;].*)/, "", $0) }
+      # Skip lines which are only IPv4 addresses
+      /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ { next }
+      # Print if nonempty
+      length { print }
+    ' "${source}" 2> /dev/null > "${destination}"
 
     echo -e "${OVER}  ${TICK} Format: URL"
   else
@@ -371,7 +375,9 @@ gravity_ConsolidateDownloadedBlocklists() {
   local str lastLine
 
   str="Consolidating blocklists"
-  echo -ne "  ${INFO} ${str}..."
+  if [[ "${haveSourceUrls}" == true ]]; then
+    echo -ne "  ${INFO} ${str}..."
+  fi
 
   # Empty $matterAndLight if it already exists, otherwise, create it
   : > "${piholeDir}/${matterAndLight}"
@@ -390,8 +396,9 @@ gravity_ConsolidateDownloadedBlocklists() {
       fi
     fi
   done
-
-  echo -e "${OVER}  ${TICK} ${str}"
+  if [[ "${haveSourceUrls}" == true ]]; then
+    echo -e "${OVER}  ${TICK} ${str}"
+  fi
 }
 
 # Parse consolidated list into (filtered, unique) domains-only format
@@ -399,24 +406,33 @@ gravity_SortAndFilterConsolidatedList() {
   local str num
 
   str="Extracting domains from blocklists"
-  echo -ne "  ${INFO} ${str}..."
+  if [[ "${haveSourceUrls}" == true ]]; then
+    echo -ne "  ${INFO} ${str}..."
+  fi
 
   # Parse into hosts file
   gravity_ParseFileIntoDomains "${piholeDir}/${matterAndLight}" "${piholeDir}/${parsedMatter}"
 
   # Format $parsedMatter line total as currency
   num=$(printf "%'.0f" "$(wc -l < "${piholeDir}/${parsedMatter}")")
-  echo -e "${OVER}  ${TICK} ${str}
-  ${INFO} Number of domains being pulled in by gravity: ${COL_BLUE}${num}${COL_NC}"
+  if [[ "${haveSourceUrls}" == true ]]; then
+    echo -e "${OVER}  ${TICK} ${str}"
+  fi
+  echo -e "  ${INFO} Number of domains being pulled in by gravity: ${COL_BLUE}${num}${COL_NC}"
 
   str="Removing duplicate domains"
-  echo -ne "  ${INFO} ${str}..."
+  if [[ "${haveSourceUrls}" == true ]]; then
+    echo -ne "  ${INFO} ${str}..."
+  fi
+
   sort -u "${piholeDir}/${parsedMatter}" > "${piholeDir}/${preEventHorizon}"
-  echo -e "${OVER}  ${TICK} ${str}"
 
-  # Format $preEventHorizon line total as currency
-  num=$(printf "%'.0f" "$(wc -l < "${piholeDir}/${preEventHorizon}")")
-  echo -e "  ${INFO} Number of unique domains trapped in the Event Horizon: ${COL_BLUE}${num}${COL_NC}"
+  if [[ "${haveSourceUrls}" == true ]]; then
+    echo -e "${OVER}  ${TICK} ${str}"
+    # Format $preEventHorizon line total as currency
+    num=$(printf "%'.0f" "$(wc -l < "${piholeDir}/${preEventHorizon}")")
+    echo -e "  ${INFO} Number of unique domains trapped in the Event Horizon: ${COL_BLUE}${num}${COL_NC}"
+  fi
 }
 
 # Whitelist user-defined domains
@@ -438,7 +454,7 @@ gravity_Whitelist() {
   echo -e "${OVER}  ${INFO} ${str}"
 }
 
-# Output count of blacklisted domains and wildcards
+# Output count of blacklisted domains and regex filters
 gravity_ShowBlockCount() {
   local num
 
@@ -447,13 +463,9 @@ gravity_ShowBlockCount() {
     echo -e "  ${INFO} Number of blacklisted domains: ${num}"
   fi
 
-  if [[ -f "${wildcardFile}" ]]; then
-    num=$(grep -c "^" "${wildcardFile}")
-    # If IPv4 and IPv6 is used, divide total wildcard count by 2
-    if [[ -n "${IPV4_ADDRESS}" ]] && [[ -n "${IPV6_ADDRESS}" ]];then
-      num=$(( num/2 ))
-    fi
-    echo -e "  ${INFO} Number of wildcard blocked domains: ${num}"
+  if [[ -f "${regexFile}" ]]; then
+    num=$(grep -c "^(?!#)" "${regexFile}")
+    echo -e "  ${INFO} Number of regex filters: ${num}"
   fi
 }
 
@@ -505,12 +517,12 @@ gravity_ParseBlacklistDomains() {
 
   # Empty $accretionDisc if it already exists, otherwise, create it
   : > "${piholeDir}/${accretionDisc}"
-  
+
   if [[ -f "${piholeDir}/${whitelistMatter}" ]]; then
-    gravity_ParseDomainsIntoHosts "${piholeDir}/${whitelistMatter}" "${piholeDir}/${accretionDisc}"
+    mv "${piholeDir}/${whitelistMatter}" "${piholeDir}/${accretionDisc}"
   else
     # There was no whitelist file, so use preEventHorizon instead of whitelistMatter.
-    gravity_ParseDomainsIntoHosts "${piholeDir}/${preEventHorizon}" "${piholeDir}/${accretionDisc}"
+    mv "${piholeDir}/${preEventHorizon}" "${piholeDir}/${accretionDisc}"
   fi
 
   # Move the file over as /etc/pihole/gravity.list so dnsmasq can use it
@@ -528,11 +540,9 @@ gravity_ParseUserDomains() {
   if [[ ! -f "${blacklistFile}" ]]; then
     return 0
   fi
-
-  gravity_ParseDomainsIntoHosts "${blacklistFile}" "${blackList}.tmp"
   # Copy the file over as /etc/pihole/black.list so dnsmasq can use it
-  mv "${blackList}.tmp" "${blackList}" 2> /dev/null || \
-    echo -e "\\n  ${CROSS} Unable to move ${blackList##*/}.tmp to ${piholeDir}"
+  cp "${blacklistFile}" "${blackList}" 2> /dev/null || \
+    echo -e "\\n  ${CROSS} Unable to move ${blacklistFile##*/} to ${piholeDir}"
 }
 
 # Trap Ctrl-C
@@ -567,7 +577,7 @@ gravity_Cleanup() {
   echo -e "${OVER}  ${TICK} ${str}"
 
   # Only restart DNS service if offline
-  if ! pidof dnsmasq &> /dev/null; then
+  if ! pidof ${resolver} &> /dev/null; then
     "${PIHOLE_COMMAND}" restartdns
     dnsWasOffline=true
   fi
@@ -616,7 +626,9 @@ if [[ "${skipDownload}" == false ]]; then
   # Gravity needs to download blocklists
   gravity_CheckDNSResolutionAvailable
   gravity_GetBlocklistUrls
-  gravity_SetDownloadOptions
+  if [[ "${haveSourceUrls}" == true ]]; then
+    gravity_SetDownloadOptions
+  fi
   gravity_ConsolidateDownloadedBlocklists
   gravity_SortAndFilterConsolidatedList
 else
@@ -631,6 +643,7 @@ if [[ "${skipDownload}" == false ]] || [[ "${listType}" == "whitelist" ]]; then
   gravity_Whitelist
 fi
 
+convert_wildcard_to_regex
 gravity_ShowBlockCount
 
 # Perform when downloading blocklists, or modifying the white/blacklist (not wildcards)
diff --git a/manpages/pihole-FTL.8 b/manpages/pihole-FTL.8
new file mode 100644
index 0000000000000000000000000000000000000000..2928f2d88cc33d2219d28d076e19f2a8e2d24db4
--- /dev/null
+++ b/manpages/pihole-FTL.8
@@ -0,0 +1,112 @@
+.TH "Pihole-FTL" "8" "pihole-FTL" "Pi-hole" "June 2018"
+.SH "NAME"
+pihole-FTL - Pi-hole : The Faster-Than-Light (FTL) Engine
+.br
+.SH "SYNOPSIS"
+\fBservice pihole-FTL \fR(\fBstart\fR|\fBstop\fR|\fBrestart\fR)
+.br
+
+\fBpihole-FTL debug\fR
+.br
+\fBpihole-FTL test\fR
+.br
+\fBpihole-FTL -v\fR
+.br
+\fBpihole-FTL -t\fR
+.br
+\fBpihole-FTL -b\fR
+.br
+\fBpihole-FTL -f\fR
+.br
+\fBpihole-FTL -h\fR
+.br
+\fBpihole-FTL dnsmasq-test\fR
+.br
+\fBpihole-FTL --\fR (\fBoptions\fR)
+.br
+
+.SH "DESCRIPTION"
+Pi-hole : The Faster-Than-Light (FTL) Engine is a lightweight, purpose-built daemon used to provide statistics needed for the Pi-hole Web Interface, and its API can be easily integrated into your own projects. Although it is an optional component of the Pi-hole ecosystem, it will be installed by default to provide statistics. As the name implies, FTL does its work \fIvery\fR \fIquickly\fR!
+.br
+
+Usage
+.br
+
+\fBservice pihole-FTL start\fR
+.br
+    Start the pihole-FTL daemon
+.br
+
+\fBservice pihole-FTL stop\fR
+.br
+    Stop the pihole-FTL daemon
+.br
+
+\fBservice pihole-FTL restart\fR
+.br
+    If the pihole-FTP daemon is running, stop and then start, otherwise start.
+.br
+
+Command line arguments
+.br
+
+\fBdebug\fR
+.br
+    Don't go into daemon mode (stay in foreground) + more verbose logging
+.br
+
+\fBtest\fR
+.br
+    Start FTL and process everything, but shut down immediately afterwards
+.br
+
+\fB-v, version\fR
+.br
+    Don't start FTL, show only version
+.br
+
+\fB-t, tag\fR
+.br
+    Don't start FTL, show only git tag
+.br
+
+\fB-b, branch\fR
+.br
+    Don't start FTL, show only git branch FTL was compiled from
+.br
+
+\fB-f, no-daemon\fR
+.br
+    Don't go into background (daemon mode)
+.br
+
+\fB-h, help\fR
+.br
+    Don't start FTL, show help
+.br
+
+\fBdnsmasq-test\fR
+.br
+    Test resolver config file syntax
+.br
+
+\fB--\fR  (options)
+.br
+    Pass options to internal dnsmasq resolver
+.br
+.SH "EXAMPLE"
+Command line arguments can be arbitrarily combined, e.g:
+.br
+
+\fBpihole-FTL debug test\fR
+.br
+
+Start ftl in foreground with more verbose logging, process everything and shutdown immediately
+.br
+.SH "SEE ALSO"
+\fBpihole\fR(8), \fBpihole-FTL.conf\fR(5)
+.br
+.SH "COLOPHON"
+
+Get sucked into the latest news and community activity by entering Pi-hole's orbit. Information about Pi-hole, and the latest version of the software can be found at https://pi-hole.net
+.br
diff --git a/manpages/pihole-FTL.conf.5 b/manpages/pihole-FTL.conf.5
new file mode 100644
index 0000000000000000000000000000000000000000..505362791ab334aeca95dcad13296cdb55d400c5
--- /dev/null
+++ b/manpages/pihole-FTL.conf.5
@@ -0,0 +1,102 @@
+.TH "pihole-FTL.conf" "5" "pihole-FTL.conf" "pihole-FTL.conf" "June 2018"
+.SH "NAME"
+
+pihole-FTL.conf - FTL's config file
+.br
+.SH "DESCRIPTION"
+
+/etc/pihole/pihole-FTL.conf will be read by \fBpihole-FTL(8)\fR on startup.
+.br
+
+\fBSOCKET_LISTENING=localonly|all\fR
+.br
+    Listen only for local socket connections or permit all connections
+.br
+
+\fBQUERY_DISPLAY=yes|no\fR
+.br
+    Display all queries? Set to no to hide query display
+.br
+
+\fBAAAA_QUERY_ANALYSIS=yes|no\fR
+.br
+    Allow FTL to analyze AAAA queries from pihole.log?
+.br
+
+\fBRESOLVE_IPV6=yes|no\fR
+.br
+    Should FTL try to resolve IPv6 addresses to host names?
+.br
+
+\fBRESOLVE_IPV4=yes|no\fR
+.br
+    Should FTL try to resolve IPv4 addresses to host names?
+.br
+
+\fBMAXDBDAYS=365\fR
+.br
+    How long should queries be stored in the database?
+.br
+    Setting this to 0 disables the database
+.br
+
+\fBDBINTERVAL=1.0\fR
+.br
+    How often do we store queries in FTL's database [minutes]?
+.br
+
+\fBDBFILE=/etc/pihole/pihole-FTL.db\fR
+.br
+    Specify path and filename of FTL's SQLite long-term database.
+.br
+    Setting this to DBFILE= disables the database altogether
+.br
+
+\fBMAXLOGAGE=24.0\fR
+.br
+    Up to how many hours of queries should be imported from the database and logs?
+.br
+    Maximum is 744 (31 days)
+.br
+
+\fBFTLPORT=4711\fR
+.br
+    On which port should FTL be listening?
+.br
+
+\fBPRIVACYLEVEL=0|1|2|3\fR
+.br
+    Which privacy level is used?
+.br
+    0 - show everything
+.br
+    1 - hide domains
+.br
+    2 - hide domains and clients
+.br
+    3 - paranoia mode (hide everything)
+.br
+
+\fBIGNORE_LOCALHOST=no|yes\fR
+.br
+    Should FTL ignore queries coming from the local machine?
+.br
+
+\fBBLOCKINGMODE=IP|IP-AAAA-NODATA|NXDOMAIN|NULL\fR
+.br
+    How should FTL reply to blocked queries?
+.br
+
+For each setting, the option shown first is the default.
+.br
+.SH "SEE ALSO"
+
+\fBpihole\fR(8), \fBpihole-FTL\fR(8)
+.br
+.SH "COLOPHON"
+
+Pi-hole : The Faster-Than-Light (FTL) Engine is a lightweight, purpose-built daemon used to provide statistics needed for the Pi-hole Web Interface, and its API can be easily integrated into your own projects. Although it is an optional component of the Pi-hole ecosystem, it will be installed by default to provide statistics. As the name implies, FTL does its work \fIvery quickly\fR!
+.br
+
+Get sucked into the latest news and community activity by entering Pi-hole's orbit. Information about Pi-hole, and the latest version of the software can be found at https://pi-hole.net
+.br
diff --git a/manpages/pihole.8 b/manpages/pihole.8
new file mode 100644
index 0000000000000000000000000000000000000000..e0845387e151fc22570bbb45ef47d00861a11065
--- /dev/null
+++ b/manpages/pihole.8
@@ -0,0 +1,361 @@
+.TH "Pi-hole" "8" "Pi-hole" "Pi-hole" "May 2018"
+.SH "NAME"
+
+Pi-hole : A black-hole for internet advertisements
+.br
+.SH "SYNOPSIS"
+
+\fBpihole\fR (\fB-w\fR|\fB-b\fR|\fB--wild\fR|\fB--regex\fR) [options] domain(s)
+.br
+\fBpihole -a\fR \fB-p\fR password
+.br
+\fBpihole -a\fR (\fB-c|-f|-k\fR)
+.br
+\fBpihole -a\fR [\fB-r\fR hostrecord]
+.br
+\fBpihole -a -e\fR email
+.br
+\fBpihole -a -i\fR interface
+.br
+\fBpihole -a -l\fR privacylevel
+.br
+\fBpihole -c\fR [-j|-r|-e]
+.br
+\fBpihole\fR \fB-d\fR [-a]
+.br
+\fBpihole -f
+.br
+pihole -r
+.br
+pihole -t
+.br
+pihole -g\fR
+.br
+\fBpihole\fR -\fBq\fR [options]
+.br
+\fBpihole\fR \fB-l\fR (\fBon|off|off noflush\fR)
+.br
+\fBpihole -up \fR[--checkonly]
+.br
+\fBpihole -v\fR [-p|-a|-f] [-c|-l|-hash]
+.br
+\fBpihole uninstall
+.br
+pihole status
+.br
+pihole restartdns\fR
+.br
+\fBpihole\fR (\fBenable\fR|\fBdisable\fR [time])
+.br
+\fBpihole\fR \fBcheckout\fR repo [branch]
+.br
+\fBpihole\fR \fBhelp\fR
+.br
+.SH "DESCRIPTION"
+
+Available commands and options:
+.br
+
+\fB-w, whitelist\fR [options] [<domain1> <domain2 ...>]
+.br
+    Adds or removes specified domain or domains tho the Whitelist
+.br
+
+\fB-b, blacklist\fR [options] [<domain1> <domain2 ...>]
+.br
+    Adds or removes specified domain or domains to the blacklist
+.br
+
+\fB--wild, wildcard\fR [options] [<domain1> <domain2 ...>]
+.br
+    Add or removes specified domain to the wildcard blacklist
+.br
+
+\fB--regex, regex\fR [options] [<regex1> <regex2 ...>]
+.br
+    Add or removes specified regex filter to the regex blacklist
+.br
+
+    (Whitelist/Blacklist manipulation options):
+.br
+      -d, --delmode     Remove domain(s) from the list
+.br
+      -nr, --noreload   Update list without refreshing dnsmasq
+.br
+      -q, --quiet       Make output less verbose
+.br
+      -l, --list        Display all your listed domains
+.br
+      --nuke            Removes all entries in a list
+.br
+
+\fB-d, debug\fR [-a]
+.br
+    Start a debugging session
+.br
+
+      -a                Enable automated debugging
+.br
+
+\fB-f, flush\fR
+.br
+    Flush the Pi-hole log
+.br
+
+\fB-r, reconfigure\fR
+.br
+    Reconfigure or Repair Pi-hole subsystems
+.br
+
+\fB-t, tail\fR
+.br
+    View the live output of the Pi-hole log
+.br
+
+\fB-a, admin\fR [options]
+.br
+
+    (Admin options):
+.br
+      -p, password      Set Web Interface password
+.br
+      -c, celsius       Set Celsius as preferred temperature unit
+.br
+      -f, fahrenheit    Set Fahrenheit as preferred temperature unit
+.br
+      -k, kelvin        Set Kelvin as preferred temperature unit
+.br
+      -r, hostrecord    Add a name to the DNS associated to an
+                        IPv4/IPv6 address
+.br
+      -e, email         Set an administrative contact address for the
+                        Block Page
+.br
+      -i, interface     Specify dnsmasq's interface listening behavior
+.br
+      -l, privacylevel  <level> Set privacy level
+                        (0 = lowest, 3 = highest)
+.br
+
+\fB-c, chronometer\fR	[options]
+.br
+    Calculates stats and displays to an LCD
+.br
+
+    (Chronometer Options):
+.br
+      -j, --json        Output stats as JSON formatted string
+.br
+      -r, --refresh     Set update frequency (in seconds)
+.br
+      -e, --exit        Output stats and exit witout refreshing
+.br
+
+\fB-g, updateGravity\fR
+.br
+    Update the list of ad-serving domains
+.br
+
+\fB-q, query\fR [option]
+.br
+    Query the adlists for a specified domain
+.br
+
+    (Query options):
+.br
+      -adlist           Print the name of the block list URL
+.br
+      -exact            Search the block lists for exact domain matches
+.br
+      -all              Return all query matches within a block list
+.br
+
+\fB-h, --help, help\fR
+.br
+    Show a help dialog
+.br
+
+\fB-l, logging\fR [on|off|off noflush]
+.br
+    Specify whether the Pi-hole log should be used
+.br
+
+	(Logging options):
+.br
+      on                Enable the Pi-hole log at /var/log/pihole.log
+.br
+      off               Disable and flush the Pi-hole log at
+                        /var/log/pihole.log
+.br
+      off noflush       Disable the Pi-hole log at /var/log/pihole.log
+.br
+
+\fB-up, updatePihole\fR [--check-only]
+.br
+    Update Pi-hole subsystems
+.br
+
+      --check-only      Exit script before update is performed.
+.br
+
+\fB-v, version\fR [repo] [options]
+.br
+    Show installed versions of Pi-hole, Web Interface &amp; FTL
+.br
+
+.br
+    (repo options):
+.br
+      -p, --pihole      Only retrieve info regarding Pi-hole repository
+.br
+      -a, --admin       Only retrieve info regarding AdminLTE
+                        repository
+.br
+      -f, --ftl         Only retrieve info regarding FTL repository
+.br
+    (version options):
+.br
+      -c, --current     Return the current version
+.br
+      -l, --latest      Return the latest version
+.br
+      --hash            Return the Github hash from your local
+                        repositories
+.br
+
+\fBuninstall\fR
+.br
+    Uninstall Pi-hole from your system
+.br
+
+\fBstatus\fR
+.br
+    Display the running status of Pi-hole subsystems
+.br
+
+\fBenable\fR
+.br
+    Enable Pi-hole subsystems
+.br
+
+\fBdisable\fR [time]
+.br
+    Disable Pi-hole subsystems, optionally for a set duration
+.br
+
+    (time options):
+.br
+      #s                Disable Pi-hole functionality for # second(s)
+.br
+      #m                Disable Pi-hole functionality for # minute(s)
+.br
+
+\fBrestartdns\fR
+.br
+    Restart Pi-hole subsystems
+.br
+
+\fBcheckout\fR [repo] [branch]
+.br
+    Switch Pi-hole subsystems to a different Github branch
+.br
+
+    (repo options):
+.br
+      core              Change the branch of Pi-hole's core subsystem
+.br
+      web               Change the branch of Admin Console subsystem
+.br
+      ftl               Change the branch of Pi-hole's FTL subsystem
+.br
+    (branch options):
+.br
+      master            Update subsystems to the latest stable release
+.br
+      dev               Update subsystems to the latest development
+                        release
+.br
+      branchname        Update subsystems to the specified branchname
+.br
+.SH "EXAMPLE"
+
+Some usage examples
+.br
+
+Whitelist/blacklist manipulation
+.br
+
+\fBpihole -w iloveads.example.com\fR
+.br
+    Adds "iloveads.example.com" to whitelist
+.br
+
+\fBpihole -b -d noads.example.com\fR
+.br
+    Removes "noads.example.com" from blacklist
+.br
+
+\fBpihole --wild example.com\fR
+.br
+    Adds example.com as a wildcard - would block all subdomains of
+    example.com, including example.com itself.
+.br
+
+\fBpihole --regex "ad.*\\.example\\.com$"\fR
+.br
+    Adds "ad.*\\.example\\.com$" to the regex blacklist.
+    Would block all subdomains of example.com which start with "ad"
+.br
+
+Changing the Web Interface password
+.br
+
+\fBpihole -a -p ExamplePassword\fR
+.br
+    Change the password to "ExamplePassword"
+.br
+
+Updating lists from internet sources
+.br
+
+\fBpihole -g\fR
+.br
+    Update the list of ad-serving domains
+.br
+
+Displaying version information
+.br
+
+\fBpihole -v -a -c\fR
+.br
+    Display the current version of AdminLTE
+.br
+
+Temporarily disabling Pi-hole
+.br
+
+\fBpihole disable 5m\fR
+.br
+    Disable Pi-hole functionality for five minutes
+.br
+
+Switching Pi-hole subsystem branches
+.br
+
+\fBpihole checkout master\fR
+.br
+    Switch to master branch
+.br
+
+\fBpihole checkout core dev\fR
+.br
+    Switch to core development branch
+.br
+.SH "SEE ALSO"
+
+\fBlighttpd\fR(8), \fBpihole-FTL\fR(8)
+.br
+.SH "COLOPHON"
+
+Get sucked into the latest news and community activity by entering Pi-hole's orbit. Information about Pi-hole, and the latest version of the software can be found at https://pi-hole.net.
+.br
diff --git a/pihole b/pihole
index 4421cb877cba6f8ddcb144922dafa5c42ea0e72c..8be03f79407b860e393fa1d7a0a22e6a8a8b6717 100755
--- a/pihole
+++ b/pihole
@@ -14,6 +14,8 @@ readonly wildcardlist="/etc/dnsmasq.d/03-pihole-wildcard.conf"
 readonly colfile="${PI_HOLE_SCRIPT_DIR}/COL_TABLE"
 source "${colfile}"
 
+resolver="pihole-FTL"
+
 # Must be root to use this tool
 if [[ ! $EUID -eq 0 ]];then
   if [[ -x "$(command -v sudo)" ]]; then
@@ -31,17 +33,7 @@ webpageFunc() {
   exit 0
 }
 
-whitelistFunc() {
- "${PI_HOLE_SCRIPT_DIR}"/list.sh "$@"
-  exit 0
-}
-
-blacklistFunc() {
-  "${PI_HOLE_SCRIPT_DIR}"/list.sh "$@"
-  exit 0
-}
-
-wildcardFunc() {
+listFunc() {
   "${PI_HOLE_SCRIPT_DIR}"/list.sh "$@"
   exit 0
 }
@@ -69,7 +61,8 @@ flushFunc() {
 }
 
 updatePiholeFunc() {
-  "${PI_HOLE_SCRIPT_DIR}"/update.sh
+  shift
+  "${PI_HOLE_SCRIPT_DIR}"/update.sh "$@"
   exit 0
 }
 
@@ -83,230 +76,9 @@ updateGravityFunc() {
   exit 0
 }
 
-# Scan an array of files for matching strings
-scanList(){
-  # Escape full stops
-  local domain="${1//./\\.}" lists="${2}" type="${3:-}"
-
-  # Prevent grep from printing file path
-  cd "/etc/pihole" || exit 1
-
-  # Prevent grep -i matching slowly: http://bit.ly/2xFXtUX
-  export LC_CTYPE=C
-
-  # /dev/null forces filename to be printed when only one list has been generated
-  # shellcheck disable=SC2086
-  case "${type}" in
-    "exact" ) grep -i -E -l "(^|\\s)${domain}($|\\s|#)" ${lists} /dev/null;;
-    "wc"    ) grep -i -o -m 1 "/${domain}/" ${lists};;
-    *       ) grep -i "${domain}" ${lists} /dev/null;;
-  esac
-}
-
-# Print each subdomain
-# e.g: foo.bar.baz.com = "foo.bar.baz.com bar.baz.com baz.com com"
-processWildcards() {
-  IFS="." read -r -a array <<< "${1}"
-  for (( i=${#array[@]}-1; i>=0; i-- )); do
-    ar=""
-    for (( j=${#array[@]}-1; j>${#array[@]}-i-2; j-- )); do
-      if [[ $j == $((${#array[@]}-1)) ]]; then
-        ar="${array[$j]}"
-      else
-        ar="${array[$j]}.${ar}"
-      fi
-    done
-    echo "${ar}"
-  done
-}
-
 queryFunc() {
   shift
-  local options="$*" adlist="" all="" exact="" blockpage="" matchType="match"
-
-  if [[ "${options}" == "-h" ]] || [[ "${options}" == "--help" ]]; then
-    echo "Usage: pihole -q [option] <domain>
-Example: 'pihole -q -exact domain.com'
-Query the adlists for a specified domain
-
-Options:
-  -adlist             Print the name of the block list URL
-  -exact              Search the block lists for exact domain matches
-  -all                Return all query matches within a block list
-  -h, --help          Show this help dialog"
-    exit 0
-  fi
-
-  if [[ ! -e "/etc/pihole/adlists.list" ]]; then
-    echo -e "${COL_LIGHT_RED}The file '/etc/pihole/adlists.list' was not found${COL_NC}"
-    exit 1
-  fi
-
-  # Handle valid options
-  if [[ "${options}" == *"-bp"* ]]; then
-    exact="exact"; blockpage=true
-  else
-    [[ "${options}" == *"-adlist"* ]] && adlist=true
-    [[ "${options}" == *"-all"* ]] && all=true
-    if [[ "${options}" == *"-exact"* ]]; then
-      exact="exact"; matchType="exact ${matchType}"
-    fi
-  fi
-
-  # Strip valid options, leaving only the domain and invalid options
-  # This allows users to place the options before or after the domain
-  options=$(sed -E 's/ ?-(bp|adlists?|all|exact) ?//g' <<< "${options}")
-
-  # Handle remaining options
-  # If $options contain non ASCII characters, convert to punycode
-  case "${options}" in
-    ""             ) str="No domain specified";;
-    *" "*          ) str="Unknown query option specified";;
-    *[![:ascii:]]* ) domainQuery=$(idn2 "${options}");;
-    *              ) domainQuery="${options}";;
-  esac
-
-  if [[ -n "${str:-}" ]]; then
-    echo -e "${str}${COL_NC}\\nTry 'pihole -q --help' for more information."
-    exit 1
-  fi
-
-  # Scan Whitelist and Blacklist
-  lists="whitelist.txt blacklist.txt"
-  mapfile -t results <<< "$(scanList "${domainQuery}" "${lists}" "${exact}")"
-
-  if [[ -n "${results[*]}" ]]; then
-    wbMatch=true
-
-    # Loop through each result in order to print unique file title once
-    for result in "${results[@]}"; do
-      fileName="${result%%.*}"
-
-      if [[ -n "${blockpage}" ]]; then
-        echo "Ï€ ${result}"
-        exit 0
-      elif [[ -n "${exact}" ]]; then
-        echo " ${matchType^} found in ${COL_BOLD}${fileName^}${COL_NC}"
-      else
-        # Only print filename title once per file
-        if [[ ! "${fileName}" == "${fileName_prev:-}" ]]; then
-          echo " ${matchType^} found in ${COL_BOLD}${fileName^}${COL_NC}"
-          fileName_prev="${fileName}"
-        fi
-        echo "   ${result#*:}"
-      fi
-    done
-  fi
-
-  # Scan Wildcards
-  if [[ -e "${wildcardlist}" ]]; then
-    # Determine all subdomains, domain and TLDs
-    mapfile -t wildcards <<< "$(processWildcards "${domainQuery}")"
-
-    for match in "${wildcards[@]}"; do
-      # Search wildcard list for matches
-      mapfile -t results <<< "$(scanList "${match}" "${wildcardlist}" "wc")"
-
-      if [[ -n "${results[*]}" ]]; then
-        if [[ -z "${wcMatch:-}" ]] && [[ -z "${blockpage}" ]]; then
-          wcMatch=true
-          echo " ${matchType^} found in ${COL_BOLD}Wildcards${COL_NC}:"
-        fi
-
-        case "${blockpage}" in
-          true ) echo "Ï€ ${wildcardlist##*/}"; exit 0;;
-          *    ) echo "   *.${match}";;
-        esac
-      fi
-    done
-  fi
-
-  # Get version sorted *.domains filenames (without dir path)
-  lists=("$(cd "/etc/pihole" || exit 0; printf "%s\\n" -- *.domains | sort -V)")
-
-  # Query blocklists for occurences of domain
-  mapfile -t results <<< "$(scanList "${domainQuery}" "${lists[*]}" "${exact}")"
-
-  # Handle notices
-  if [[ -z "${wbMatch:-}" ]] && [[ -z "${wcMatch:-}" ]] && [[ -z "${results[*]}" ]]; then
-    echo -e "  ${INFO} No ${exact/t/t }results found for ${COL_BOLD}${domainQuery}${COL_NC} found within block lists"
-    exit 0
-  elif [[ -z "${results[*]}" ]]; then
-    # Result found in WL/BL/Wildcards
-    exit 0
-  elif [[ -z "${all}" ]] && [[ "${#results[*]}" -ge 100 ]]; then
-    echo -e "  ${INFO} Over 100 ${exact/t/t }results found for ${COL_BOLD}${domainQuery}${COL_NC}
-      This can be overridden using the -all option"
-    exit 0
-  fi
-
-  # Remove unwanted content from non-exact $results
-  if [[ -z "${exact}" ]]; then
-    # Delete lines starting with #
-    # Remove comments after domain
-    # Remove hosts format IP address
-    mapfile -t results <<< "$(IFS=$'\n'; sed \
-      -e "/:#/d" \
-      -e "s/[ \\t]#.*//g" \
-      -e "s/:.*[ \\t]/:/g" \
-    <<< "${results[*]}")"
-
-    # Exit if result was in a comment
-    [[ -z "${results[*]}" ]] && exit 0
-  fi
-
-  # Get adlist file content as array
-  if [[ -n "${adlist}" ]] || [[ -n "${blockpage}" ]]; then
-    for adlistUrl in $(< "/etc/pihole/adlists.list"); do
-      if [[ "${adlistUrl:0:4}" =~ (http|www.) ]]; then
-        adlists+=("${adlistUrl}")
-      fi
-    done
-  fi
-
-  # Print "Exact matches for" title
-  if [[ -n "${exact}" ]] && [[ -z "${blockpage}" ]]; then
-    plural=""; [[ "${#results[*]}" -gt 1 ]] && plural="es"
-    echo " ${matchType^}${plural} for ${COL_BOLD}${domainQuery}${COL_NC} found in:"
-  fi
-
-  for result in "${results[@]}"; do
-    fileName="${result/:*/}"
-
-    # Determine *.domains URL using filename's number
-    if [[ -n "${adlist}" ]] || [[ -n "${blockpage}" ]]; then
-      fileNum="${fileName/list./}"; fileNum="${fileNum%%.*}"
-      fileName="${adlists[$fileNum]}"
-
-      # Discrepency occurs when adlists has been modified, but Gravity has not been run
-      if [[ -z "${fileName}" ]]; then
-        fileName="${COL_LIGHT_RED}(no associated adlists URL found)${COL_NC}"
-      fi
-    fi
-
-    if [[ -n "${blockpage}" ]]; then
-      echo "${fileNum} ${fileName}"
-    elif [[ -n "${exact}" ]]; then
-      echo "   ${fileName}"
-    else
-      if [[ ! "${fileName}" == "${fileName_prev:-}" ]]; then
-        count=""
-        echo " ${matchType^} found in ${COL_BOLD}${fileName}${COL_NC}:"
-        fileName_prev="${fileName}"
-      fi
-      : $((count++))
-
-      # Print matching domain if $max_count has not been reached
-      [[ -z "${all}" ]] && max_count="50"
-      if [[ -z "${all}" ]] && [[ "${count}" -ge "${max_count}" ]]; then
-        [[ "${count}" -gt "${max_count}" ]] && continue
-        echo "   ${COL_GRAY}Over ${count} results found, skipping rest of file${COL_NC}"
-      else
-        echo "   ${result#*:}"
-      fi
-    fi
-  done
-
+  "${PI_HOLE_SCRIPT_DIR}"/query.sh "$@"
   exit 0
 }
 
@@ -332,18 +104,18 @@ restartDNS() {
   local svcOption svc str output status
   svcOption="${1:-}"
 
-  # Determine if we should reload or restart dnsmasq
+  # Determine if we should reload or restart restart
   if [[ "${svcOption}" =~ "reload" ]]; then
     # Using SIGHUP will NOT re-read any *.conf files
-    svc="killall -s SIGHUP dnsmasq"
+    svc="killall -s SIGHUP ${resolver}"
   else
-    # Get PID of dnsmasq to determine if it needs to start or restart
-    if pidof dnsmasq &> /dev/null; then
+    # Get PID of resolver to determine if it needs to start or restart
+    if pidof pihole-FTL &> /dev/null; then
       svcOption="restart"
     else
       svcOption="start"
     fi
-    svc="service dnsmasq ${svcOption}"
+    svc="service ${resolver} ${svcOption}"
   fi
 
   # Print output to Terminal, but not to Web Admin
@@ -359,9 +131,6 @@ restartDNS() {
     [[ ! -t 1 ]] && local OVER=""
     echo -e "${OVER}  ${CROSS} ${output}"
   fi
-
-  # Send signal to FTL to have it re-parse the gravity files
-  killall -s SIGHUP pihole-FTL
 }
 
 piholeEnable() {
@@ -476,7 +245,7 @@ statusFunc() {
   local addnConfigs
 
   # Determine if service is running on port 53 (Cr: https://superuser.com/a/806331)
-  if (echo > /dev/tcp/localhost/53) >/dev/null 2>&1; then
+  if (echo > /dev/tcp/127.0.0.1/53) >/dev/null 2>&1; then
     if [[ "${1}" != "web" ]]; then
       echo -e "  ${TICK} DNS service is running"
     fi
@@ -516,6 +285,13 @@ statusFunc() {
 }
 
 tailFunc() {
+  # Warn user if Pi-hole's logging is disabled
+  local logging_enabled=$(grep -c "^log-queries" /etc/dnsmasq.d/01-pihole.conf)
+  if [[ "${logging_enabled}" == "0" ]]; then
+    # No "log-queries" lines are found.
+    # Commented out lines (such as "#log-queries") are ignored
+    echo "  ${CROSS} Warning: Query logging is disabled"
+  fi
   echo -e "  ${INFO} Press Ctrl-C to exit"
 
   # Retrieve IPv4/6 addresses
@@ -541,12 +317,13 @@ Switch Pi-hole subsystems to a different Github branch
 
 Repositories:
   core [branch]       Change the branch of Pi-hole's core subsystem
-  web [branch]        Change the branch of Admin Console subsystem
+  web [branch]        Change the branch of Web Interface subsystem
   ftl [branch]        Change the branch of Pi-hole's FTL subsystem
 
 Branches:
   master              Update subsystems to the latest stable release
-  dev                 Update subsystems to the latest development release"
+  dev                 Update subsystems to the latest development release
+  branchname          Update subsystems to the specified branchname"
     exit 0
   fi
 
@@ -599,7 +376,8 @@ Add '-h' after specific commands for more information on usage
 Whitelist/Blacklist Options:
   -w, whitelist       Whitelist domain(s)
   -b, blacklist       Blacklist domain(s)
-  -wild, wildcard     Blacklist domain(s), and all its subdomains
+  --wild, wildcard     Wildcard blacklist domain(s)
+  --regex, regex       Regex blacklist domains(s)
                         Add '-h' for more info on whitelist/blacklist usage
 
 Debugging Options:
@@ -610,8 +388,8 @@ Debugging Options:
   -t, tail            View the live output of the Pi-hole log
 
 Options:
-  -a, admin           Admin Console options
-                        Add '-h' for more info on admin console usage
+  -a, admin           Web interface options
+                        Add '-h' for more info on Web Interface usage
   -c, chronometer     Calculates stats and displays to an LCD
                         Add '-h' for more info on chronometer usage
   -g, updateGravity   Update the list of ad-serving domains
@@ -621,7 +399,8 @@ Options:
   -q, query           Query the adlists for a specified domain
                         Add '-h' for more info on query usage
   -up, updatePihole   Update Pi-hole subsystems
-  -v, version         Show installed versions of Pi-hole, Admin Console & FTL
+                        Add '--check-only' to exit script before update is performed.
+  -v, version         Show installed versions of Pi-hole, Web Interface & FTL
                         Add '-h' for more info on version usage
   uninstall           Uninstall Pi-hole from your system
   status              Display the running status of Pi-hole subsystems
@@ -640,12 +419,13 @@ fi
 
 # Handle redirecting to specific functions based on arguments
 case "${1}" in
-  "-w" | "whitelist"            ) whitelistFunc "$@";;
-  "-b" | "blacklist"            ) blacklistFunc "$@";;
-  "-wild" | "wildcard"          ) wildcardFunc "$@";;
+  "-w" | "whitelist"            ) listFunc "$@";;
+  "-b" | "blacklist"            ) listFunc "$@";;
+  "--wild" | "wildcard"          ) listFunc "$@";;
+  "--regex" | "regex"            ) listFunc "$@";;
   "-d" | "debug"                ) debugFunc "$@";;
   "-f" | "flush"                ) flushFunc "$@";;
-  "-up" | "updatePihole"        ) updatePiholeFunc;;
+  "-up" | "updatePihole"        ) updatePiholeFunc "$@";;
   "-r"  | "reconfigure"         ) reconfigurePiholeFunc;;
   "-g" | "updateGravity"        ) updateGravityFunc "$@";;
   "-c" | "chronometer"          ) chronometerFunc "$@";;
diff --git a/requirements.txt b/requirements.txt
index 53737ca5f51f50304d0607874c1efb16a970c980..f2c61e427e75f2170a80a617f254cc450b1f6390 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,3 +3,4 @@ pytest
 pytest-xdist
 pytest-cov
 testinfra
+tox
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000000000000000000000000000000000000..0e393bc131ea5a41a3047e436126bdb555970bf9
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,6 @@
+from setuptools import setup
+
+setup(
+    setup_requires=['pytest-runner'],
+    tests_require=['pytest'],
+)
diff --git a/test/README.md b/test/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..f5a9b5e8bd692fc01a2cbcce51ba7b661d237f8a
--- /dev/null
+++ b/test/README.md
@@ -0,0 +1,25 @@
+# Recommended way to run tests
+
+Make sure you have Docker and Python w/pip package manager.
+
+From command line all you need to do is:
+
+- `pip install tox`
+- `tox`
+
+Tox handles setting up a virtual environment for python dependancies, installing dependancies, building the docker images used by tests, and finally running tests.  It's an easy way to have travis-ci like build behavior locally.
+
+## Alternative py.test method of running tests
+
+You're responsible for setting up your virtual env and dependancies in this situation.
+
+```
+py.test -vv -n auto -m "build_stage"
+py.test -vv -n auto -m "not build_stage"
+```
+
+The build_stage tests have to run first to create the docker images, followed by the actual tests which utilize said images.  Unless you're changing your dockerfiles you shouldn't have to run the build_stage every time - but it's a good idea to rebuild at least once a day in case the base Docker images or packages change.
+
+# How do I debug python?
+
+Highly recommended: Setup PyCharm on a **Docker enabled** machine.  Having a python debugger like PyCharm changes your life if you've never used it :)
diff --git a/test/conftest.py b/test/conftest.py
index 5960cc246238e85d4261ca332b69327f19d5fc82..58530d3809ca7f432f4934a1f4646d739244f615 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -1,14 +1,30 @@
 import pytest
 import testinfra
+from textwrap import dedent
 
 check_output = testinfra.get_backend(
     "local://"
 ).get_module("Command").check_output
 
+SETUPVARS = {
+    'PIHOLE_INTERFACE': 'eth99',
+    'IPV4_ADDRESS': '1.1.1.1',
+    'IPV6_ADDRESS': 'FE80::240:D0FF:FE48:4672',
+    'PIHOLE_DNS_1': '4.2.2.1',
+    'PIHOLE_DNS_2': '4.2.2.2'
+}
+
+tick_box = "[\x1b[1;32m\xe2\x9c\x93\x1b[0m]".decode("utf-8")
+cross_box = "[\x1b[1;31m\xe2\x9c\x97\x1b[0m]".decode("utf-8")
+info_box = "[i]".decode("utf-8")
+
+
 @pytest.fixture
 def Pihole(Docker):
-    ''' used to contain some script stubbing, now pretty much an alias.
-    Also provides bash as the default run function shell '''
+    '''
+    used to contain some script stubbing, now pretty much an alias.
+    Also provides bash as the default run function shell
+    '''
     def run_bash(self, command, *args, **kwargs):
         cmd = self.get_command(command, *args)
         if self.user is not None:
@@ -22,12 +38,18 @@ def Pihole(Docker):
         return out
 
     funcType = type(Docker.run)
-    Docker.run = funcType(run_bash, Docker, testinfra.backend.docker.DockerBackend)
+    Docker.run = funcType(run_bash,
+                          Docker,
+                          testinfra.backend.docker.DockerBackend)
     return Docker
 
+
 @pytest.fixture
 def Docker(request, args, image, cmd):
-    ''' combine our fixtures into a docker run command and setup finalizer to cleanup '''
+    '''
+    combine our fixtures into a docker run command and setup finalizer to
+    cleanup
+    '''
     assert 'docker' in check_output('id'), "Are you in the docker group?"
     docker_run = "docker run {} {} {}".format(args, image, cmd)
     docker_id = check_output(docker_run)
@@ -40,22 +62,95 @@ def Docker(request, args, image, cmd):
     docker_container.id = docker_id
     return docker_container
 
+
 @pytest.fixture
 def args(request):
-    ''' -t became required when tput began being used '''
+    '''
+    -t became required when tput began being used
+    '''
     return '-t -d'
 
-@pytest.fixture(params=['debian', 'centos'])
+
+@pytest.fixture(params=['debian', 'centos', 'fedora'])
 def tag(request):
-    ''' consumed by image to make the test matrix '''
+    '''
+    consumed by image to make the test matrix
+    '''
     return request.param
 
+
 @pytest.fixture()
 def image(request, tag):
-    ''' built by test_000_build_containers.py '''
+    '''
+    built by test_000_build_containers.py
+    '''
     return 'pytest_pihole:{}'.format(tag)
 
+
 @pytest.fixture()
 def cmd(request):
-    ''' default to doing nothing by tailing null, but don't exit '''
+    '''
+    default to doing nothing by tailing null, but don't exit
+    '''
     return 'tail -f /dev/null'
+
+
+# Helper functions
+def mock_command(script, args, container):
+    '''
+    Allows for setup of commands we don't really want to have to run for real
+    in unit tests
+    '''
+    full_script_path = '/usr/local/bin/{}'.format(script)
+    mock_script = dedent('''\
+    #!/bin/bash -e
+    echo "\$0 \$@" >> /var/log/{script}
+    case "\$1" in'''.format(script=script))
+    for k, v in args.iteritems():
+        case = dedent('''
+        {arg})
+        echo {res}
+        exit {retcode}
+        ;;'''.format(arg=k, res=v[0], retcode=v[1]))
+        mock_script += case
+    mock_script += dedent('''
+    esac''')
+    container.run('''
+    cat <<EOF> {script}\n{content}\nEOF
+    chmod +x {script}
+    rm -f /var/log/{scriptlog}'''.format(script=full_script_path,
+                                         content=mock_script,
+                                         scriptlog=script))
+
+
+def mock_command_2(script, args, container):
+    '''
+    Allows for setup of commands we don't really want to have to run for real
+    in unit tests
+    '''
+    full_script_path = '/usr/local/bin/{}'.format(script)
+    mock_script = dedent('''\
+    #!/bin/bash -e
+    echo "\$0 \$@" >> /var/log/{script}
+    case "\$1 \$2" in'''.format(script=script))
+    for k, v in args.iteritems():
+        case = dedent('''
+        \"{arg}\")
+        echo \"{res}\"
+        exit {retcode}
+        ;;'''.format(arg=k, res=v[0], retcode=v[1]))
+        mock_script += case
+    mock_script += dedent('''
+    esac''')
+    container.run('''
+    cat <<EOF> {script}\n{content}\nEOF
+    chmod +x {script}
+    rm -f /var/log/{scriptlog}'''.format(script=full_script_path,
+                                         content=mock_script,
+                                         scriptlog=script))
+
+
+def run_script(Pihole, script):
+    result = Pihole.run(script)
+    assert result.rc == 0
+    return result
diff --git a/test/fedora.Dockerfile b/test/fedora.Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..c48343882cab35f36fdab2952abb01e0d9b7c634
--- /dev/null
+++ b/test/fedora.Dockerfile
@@ -0,0 +1,16 @@
+FROM fedora:latest
+
+ENV GITDIR /etc/.pihole
+ENV SCRIPTDIR /opt/pihole
+
+RUN mkdir -p $GITDIR $SCRIPTDIR /etc/pihole
+ADD . $GITDIR
+RUN cp $GITDIR/advanced/Scripts/*.sh $GITDIR/gravity.sh $GITDIR/pihole $GITDIR/automated\ install/*.sh $SCRIPTDIR/
+ENV PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$SCRIPTDIR
+
+RUN true && \
+    chmod +x $SCRIPTDIR/*
+
+ENV PH_TEST true
+
+#sed '/# Start the installer/Q' /opt/pihole/basic-install.sh > /opt/pihole/stub_basic-install.sh && \
diff --git a/test/test_000_build_containers.py b/test/test_000_build_containers.py
index c617f3ae1ac00a0848f96e18f28f27f2e5dfe248..e9e9e7db97e7b95638708f1575746d13d5247d71 100644
--- a/test/test_000_build_containers.py
+++ b/test/test_000_build_containers.py
@@ -6,10 +6,15 @@ run_local = testinfra.get_backend(
     "local://"
 ).get_module("Command").run
 
+
 @pytest.mark.parametrize("image,tag", [
-    ( 'test/debian.Dockerfile', 'pytest_pihole:debian' ),
-    ( 'test/centos.Dockerfile', 'pytest_pihole:centos' ),
+    ('test/debian.Dockerfile', 'pytest_pihole:debian'),
+    ('test/centos.Dockerfile', 'pytest_pihole:centos'),
+    ('test/fedora.Dockerfile', 'pytest_pihole:fedora'),
 ])
+# mark as 'build_stage' so we can ensure images are build first when tests
+# are executed in parallel. (not required when tests are executed serially)
+@pytest.mark.build_stage
 def test_build_pihole_image(image, tag):
     build_cmd = run_local('docker build -f {} -t {} .'.format(image, tag))
     if build_cmd.rc != 0:
diff --git a/test/test_automated_install.py b/test/test_automated_install.py
index 0e961c7f3d54eb76cec58b330ed3ffeb44e3f92d..876b06ebebee7aa22b08f96ed22ef51703d723ac 100644
--- a/test/test_automated_install.py
+++ b/test/test_automated_install.py
@@ -1,24 +1,40 @@
-import pytest
 from textwrap import dedent
+import re
+from conftest import (
+    SETUPVARS,
+    tick_box,
+    info_box,
+    cross_box,
+    mock_command,
+    mock_command_2,
+    run_script
+)
+
+
+def test_supported_operating_system(Pihole):
+    '''
+    confirm installer exists on unsupported distribution
+    '''
+    # break supported package managers to emulate an unsupported distribution
+    Pihole.run('rm -rf /usr/bin/apt-get')
+    Pihole.run('rm -rf /usr/bin/rpm')
+    distro_check = Pihole.run('''
+    source /opt/pihole/basic-install.sh
+    distro_check
+    ''')
+    expected_stdout = cross_box + ' OS distribution not supported'
+    assert expected_stdout in distro_check.stdout
+    # assert distro_check.rc == 1
 
-SETUPVARS = {
-    'PIHOLE_INTERFACE' : 'eth99',
-    'IPV4_ADDRESS' : '1.1.1.1',
-    'IPV6_ADDRESS' : 'FE80::240:D0FF:FE48:4672',
-    'PIHOLE_DNS_1' : '4.2.2.1',
-    'PIHOLE_DNS_2' : '4.2.2.2'
-}
-
-tick_box="[\x1b[1;32m\xe2\x9c\x93\x1b[0m]".decode("utf-8")
-cross_box="[\x1b[1;31m\xe2\x9c\x97\x1b[0m]".decode("utf-8")
-info_box="[i]".decode("utf-8")
 
 def test_setupVars_are_sourced_to_global_scope(Pihole):
-    ''' currently update_dialogs sources setupVars with a dot,
+    '''
+    currently update_dialogs sources setupVars with a dot,
     then various other functions use the variables.
-    This confirms the sourced variables are in scope between functions '''
+    This confirms the sourced variables are in scope between functions
+    '''
     setup_var_file = 'cat <<EOF> /etc/pihole/setupVars.conf\n'
-    for k,v in SETUPVARS.iteritems():
+    for k, v in SETUPVARS.iteritems():
         setup_var_file += "{}={}\n".format(k, v)
     setup_var_file += "EOF\n"
     Pihole.run(setup_var_file)
@@ -43,13 +59,17 @@ def test_setupVars_are_sourced_to_global_scope(Pihole):
 
     output = run_script(Pihole, script).stdout
 
-    for k,v in SETUPVARS.iteritems():
+    for k, v in SETUPVARS.iteritems():
         assert "{}={}".format(k, v) in output
 
+
 def test_setupVars_saved_to_file(Pihole):
-    ''' confirm saved settings are written to a file for future updates to re-use '''
-    set_setup_vars = '\n'  # dedent works better with this and padding matching script below
-    for k,v in SETUPVARS.iteritems():
+    '''
+    confirm saved settings are written to a file for future updates to re-use
+    '''
+    # dedent works better with this and padding matching script below
+    set_setup_vars = '\n'
+    for k, v in SETUPVARS.iteritems():
         set_setup_vars += "    {}={}\n".format(k, v)
     Pihole.run(set_setup_vars).stdout
 
@@ -67,43 +87,57 @@ def test_setupVars_saved_to_file(Pihole):
 
     output = run_script(Pihole, script).stdout
 
-    for k,v in SETUPVARS.iteritems():
+    for k, v in SETUPVARS.iteritems():
         assert "{}={}".format(k, v) in output
 
+
 def test_configureFirewall_firewalld_running_no_errors(Pihole):
-    ''' confirms firewalld rules are applied when firewallD is running '''
+    '''
+    confirms firewalld rules are applied when firewallD is running
+    '''
     # firewallD returns 'running' as status
-    mock_command('firewall-cmd', {'*':('running', 0)}, Pihole)
+    mock_command('firewall-cmd', {'*': ('running', 0)}, Pihole)
     # Whiptail dialog returns Ok for user prompt
-    mock_command('whiptail', {'*':('', 0)}, Pihole)
+    mock_command('whiptail', {'*': ('', 0)}, Pihole)
     configureFirewall = Pihole.run('''
     source /opt/pihole/basic-install.sh
     configureFirewall
     ''')
-    expected_stdout = 'Configuring FirewallD for httpd and dnsmasq'
+    expected_stdout = 'Configuring FirewallD for httpd and pihole-FTL'
     assert expected_stdout in configureFirewall.stdout
     firewall_calls = Pihole.run('cat /var/log/firewall-cmd').stdout
     assert 'firewall-cmd --state' in firewall_calls
-    assert 'firewall-cmd --permanent --add-service=http --add-service=dns' in firewall_calls
+    assert ('firewall-cmd '
+            '--permanent '
+            '--add-service=http '
+            '--add-service=dns') in firewall_calls
     assert 'firewall-cmd --reload' in firewall_calls
 
+
 def test_configureFirewall_firewalld_disabled_no_errors(Pihole):
-    ''' confirms firewalld rules are not applied when firewallD is not running '''
+    '''
+    confirms firewalld rules are not applied when firewallD is not running
+    '''
     # firewallD returns non-running status
-    mock_command('firewall-cmd', {'*':('not running', '1')}, Pihole)
+    mock_command('firewall-cmd', {'*': ('not running', '1')}, Pihole)
     configureFirewall = Pihole.run('''
     source /opt/pihole/basic-install.sh
     configureFirewall
     ''')
-    expected_stdout = 'No active firewall detected.. skipping firewall configuration'
+    expected_stdout = ('No active firewall detected.. '
+                       'skipping firewall configuration')
     assert expected_stdout in configureFirewall.stdout
 
+
 def test_configureFirewall_firewalld_enabled_declined_no_errors(Pihole):
-    ''' confirms firewalld rules are not applied when firewallD is running, user declines ruleset '''
+    '''
+    confirms firewalld rules are not applied when firewallD is running, user
+    declines ruleset
+    '''
     # firewallD returns running status
-    mock_command('firewall-cmd', {'*':('running', 0)}, Pihole)
+    mock_command('firewall-cmd', {'*': ('running', 0)}, Pihole)
     # Whiptail dialog returns Cancel for user prompt
-    mock_command('whiptail', {'*':('', 1)}, Pihole)
+    mock_command('whiptail', {'*': ('', 1)}, Pihole)
     configureFirewall = Pihole.run('''
     source /opt/pihole/basic-install.sh
     configureFirewall
@@ -111,6 +145,7 @@ def test_configureFirewall_firewalld_enabled_declined_no_errors(Pihole):
     expected_stdout = 'Not installing firewall rulesets.'
     assert expected_stdout in configureFirewall.stdout
 
+
 def test_configureFirewall_no_firewall(Pihole):
     ''' confirms firewall skipped no daemon is running '''
     configureFirewall = Pihole.run('''
@@ -120,14 +155,18 @@ def test_configureFirewall_no_firewall(Pihole):
     expected_stdout = 'No active firewall detected'
     assert expected_stdout in configureFirewall.stdout
 
+
 def test_configureFirewall_IPTables_enabled_declined_no_errors(Pihole):
-    ''' confirms IPTables rules are not applied when IPTables is running, user declines ruleset '''
+    '''
+    confirms IPTables rules are not applied when IPTables is running, user
+    declines ruleset
+    '''
     # iptables command exists
-    mock_command('iptables', {'*':('', '0')}, Pihole)
+    mock_command('iptables', {'*': ('', '0')}, Pihole)
     # modinfo returns always true (ip_tables module check)
-    mock_command('modinfo', {'*':('', '0')}, Pihole)
+    mock_command('modinfo', {'*': ('', '0')}, Pihole)
     # Whiptail dialog returns Cancel for user prompt
-    mock_command('whiptail', {'*':('', '1')}, Pihole)
+    mock_command('whiptail', {'*': ('', '1')}, Pihole)
     configureFirewall = Pihole.run('''
     source /opt/pihole/basic-install.sh
     configureFirewall
@@ -135,14 +174,19 @@ def test_configureFirewall_IPTables_enabled_declined_no_errors(Pihole):
     expected_stdout = 'Not installing firewall rulesets.'
     assert expected_stdout in configureFirewall.stdout
 
+
 def test_configureFirewall_IPTables_enabled_rules_exist_no_errors(Pihole):
-    ''' confirms IPTables rules are not applied when IPTables is running and rules exist '''
-    # iptables command exists and returns 0 on calls (should return 0 on iptables -C)
-    mock_command('iptables', {'-S':('-P INPUT DENY', '0')}, Pihole)
+    '''
+    confirms IPTables rules are not applied when IPTables is running and rules
+    exist
+    '''
+    # iptables command exists and returns 0 on calls
+    # (should return 0 on iptables -C)
+    mock_command('iptables', {'-S': ('-P INPUT DENY', '0')}, Pihole)
     # modinfo returns always true (ip_tables module check)
-    mock_command('modinfo', {'*':('', '0')}, Pihole)
+    mock_command('modinfo', {'*': ('', '0')}, Pihole)
     # Whiptail dialog returns Cancel for user prompt
-    mock_command('whiptail', {'*':('', '0')}, Pihole)
+    mock_command('whiptail', {'*': ('', '0')}, Pihole)
     configureFirewall = Pihole.run('''
     source /opt/pihole/basic-install.sh
     configureFirewall
@@ -150,18 +194,46 @@ def test_configureFirewall_IPTables_enabled_rules_exist_no_errors(Pihole):
     expected_stdout = 'Installing new IPTables firewall rulesets'
     assert expected_stdout in configureFirewall.stdout
     firewall_calls = Pihole.run('cat /var/log/iptables').stdout
-    assert 'iptables -I INPUT 1 -p tcp -m tcp --dport 80 -j ACCEPT' not in firewall_calls
-    assert 'iptables -I INPUT 1 -p tcp -m tcp --dport 53 -j ACCEPT' not in firewall_calls
-    assert 'iptables -I INPUT 1 -p udp -m udp --dport 53 -j ACCEPT' not in firewall_calls
+    # General call type occurances
+    assert len(re.findall(r'iptables -S', firewall_calls)) == 1
+    assert len(re.findall(r'iptables -C', firewall_calls)) == 4
+    assert len(re.findall(r'iptables -I', firewall_calls)) == 0
+
+    # Specific port call occurances
+    assert len(re.findall(r'tcp --dport 80', firewall_calls)) == 1
+    assert len(re.findall(r'tcp --dport 53', firewall_calls)) == 1
+    assert len(re.findall(r'udp --dport 53', firewall_calls)) == 1
+    assert len(re.findall(r'tcp --dport 4711:4720', firewall_calls)) == 1
+
 
 def test_configureFirewall_IPTables_enabled_not_exist_no_errors(Pihole):
-    ''' confirms IPTables rules are applied when IPTables is running and rules do not exist '''
+    '''
+    confirms IPTables rules are applied when IPTables is running and rules do
+    not exist
+    '''
     # iptables command and returns 0 on calls (should return 1 on iptables -C)
-    mock_command('iptables', {'-S':('-P INPUT DENY', '0'), '-C':('', 1), '-I':('', 0)}, Pihole)
+    mock_command(
+        'iptables',
+        {
+            '-S': (
+                '-P INPUT DENY',
+                '0'
+            ),
+            '-C': (
+                '',
+                1
+            ),
+            '-I': (
+                '',
+                0
+            )
+        },
+        Pihole
+    )
     # modinfo returns always true (ip_tables module check)
-    mock_command('modinfo', {'*':('', '0')}, Pihole)
+    mock_command('modinfo', {'*': ('', '0')}, Pihole)
     # Whiptail dialog returns Cancel for user prompt
-    mock_command('whiptail', {'*':('', '0')}, Pihole)
+    mock_command('whiptail', {'*': ('', '0')}, Pihole)
     configureFirewall = Pihole.run('''
     source /opt/pihole/basic-install.sh
     configureFirewall
@@ -169,52 +241,160 @@ def test_configureFirewall_IPTables_enabled_not_exist_no_errors(Pihole):
     expected_stdout = 'Installing new IPTables firewall rulesets'
     assert expected_stdout in configureFirewall.stdout
     firewall_calls = Pihole.run('cat /var/log/iptables').stdout
-    assert 'iptables -I INPUT 1 -p tcp -m tcp --dport 80 -j ACCEPT' in firewall_calls
-    assert 'iptables -I INPUT 1 -p tcp -m tcp --dport 53 -j ACCEPT' in firewall_calls
-    assert 'iptables -I INPUT 1 -p udp -m udp --dport 53 -j ACCEPT' in firewall_calls
+    # General call type occurances
+    assert len(re.findall(r'iptables -S', firewall_calls)) == 1
+    assert len(re.findall(r'iptables -C', firewall_calls)) == 4
+    assert len(re.findall(r'iptables -I', firewall_calls)) == 4
+
+    # Specific port call occurances
+    assert len(re.findall(r'tcp --dport 80', firewall_calls)) == 2
+    assert len(re.findall(r'tcp --dport 53', firewall_calls)) == 2
+    assert len(re.findall(r'udp --dport 53', firewall_calls)) == 2
+    assert len(re.findall(r'tcp --dport 4711:4720', firewall_calls)) == 2
+
+
+def test_selinux_enforcing_default_exit(Pihole):
+    '''
+    confirms installer prompts to exit when SELinux is Enforcing by default
+    '''
+    # getenforce returns the running state of SELinux
+    mock_command('getenforce', {'*': ('Enforcing', '0')}, Pihole)
+    # Whiptail dialog returns Cancel for user prompt
+    mock_command('whiptail', {'*': ('', '1')}, Pihole)
+    check_selinux = Pihole.run('''
+    source /opt/pihole/basic-install.sh
+    checkSelinux
+    ''')
+    expected_stdout = info_box + ' SELinux mode detected: Enforcing'
+    assert expected_stdout in check_selinux.stdout
+    expected_stdout = 'SELinux Enforcing detected, exiting installer'
+    assert expected_stdout in check_selinux.stdout
+    assert check_selinux.rc == 1
+
+
+def test_selinux_enforcing_continue(Pihole):
+    '''
+    confirms installer prompts to continue with custom policy warning
+    '''
+    # getenforce returns the running state of SELinux
+    mock_command('getenforce', {'*': ('Enforcing', '0')}, Pihole)
+    # Whiptail dialog returns Continue for user prompt
+    mock_command('whiptail', {'*': ('', '0')}, Pihole)
+    check_selinux = Pihole.run('''
+    source /opt/pihole/basic-install.sh
+    checkSelinux
+    ''')
+    expected_stdout = info_box + ' SELinux mode detected: Enforcing'
+    assert expected_stdout in check_selinux.stdout
+    expected_stdout = info_box + (' Continuing installation with SELinux '
+                                  'Enforcing')
+    assert expected_stdout in check_selinux.stdout
+    expected_stdout = info_box + (' Please refer to official SELinux '
+                                  'documentation to create a custom policy')
+    assert expected_stdout in check_selinux.stdout
+    assert check_selinux.rc == 0
+
+
+def test_selinux_permissive(Pihole):
+    '''
+    confirms installer continues when SELinux is Permissive
+    '''
+    # getenforce returns the running state of SELinux
+    mock_command('getenforce', {'*': ('Permissive', '0')}, Pihole)
+    check_selinux = Pihole.run('''
+    source /opt/pihole/basic-install.sh
+    checkSelinux
+    ''')
+    expected_stdout = info_box + ' SELinux mode detected: Permissive'
+    assert expected_stdout in check_selinux.stdout
+    assert check_selinux.rc == 0
+
+
+def test_selinux_disabled(Pihole):
+    '''
+    confirms installer continues when SELinux is Disabled
+    '''
+    mock_command('getenforce', {'*': ('Disabled', '0')}, Pihole)
+    check_selinux = Pihole.run('''
+    source /opt/pihole/basic-install.sh
+    checkSelinux
+    ''')
+    expected_stdout = info_box + ' SELinux mode detected: Disabled'
+    assert expected_stdout in check_selinux.stdout
+    assert check_selinux.rc == 0
+
 
 def test_installPiholeWeb_fresh_install_no_errors(Pihole):
-    ''' confirms all web page assets from Core repo are installed on a fresh build '''
+    '''
+    confirms all web page assets from Core repo are installed on a fresh build
+    '''
     installWeb = Pihole.run('''
     source /opt/pihole/basic-install.sh
     installPiholeWeb
     ''')
-    assert info_box + ' Installing blocking page...' in installWeb.stdout
-    assert tick_box + ' Creating directory for blocking page, and copying files' in installWeb.stdout
-    assert cross_box + ' Backing up index.lighttpd.html' in installWeb.stdout
-    assert 'No default index.lighttpd.html file found... not backing up' in installWeb.stdout
-    assert tick_box + ' Installing sudoer file' in installWeb.stdout
+    expected_stdout = info_box + ' Installing blocking page...'
+    assert expected_stdout in installWeb.stdout
+    expected_stdout = tick_box + (' Creating directory for blocking page, '
+                                  'and copying files')
+    assert expected_stdout in installWeb.stdout
+    expected_stdout = cross_box + ' Backing up index.lighttpd.html'
+    assert expected_stdout in installWeb.stdout
+    expected_stdout = ('No default index.lighttpd.html file found... '
+                       'not backing up')
+    assert expected_stdout in installWeb.stdout
+    expected_stdout = tick_box + ' Installing sudoer file'
+    assert expected_stdout in installWeb.stdout
     web_directory = Pihole.run('ls -r /var/www/html/pihole').stdout
     assert 'index.php' in web_directory
     assert 'blockingpage.css' in web_directory
 
+
 def test_update_package_cache_success_no_errors(Pihole):
-    ''' confirms package cache was updated without any errors'''
+    '''
+    confirms package cache was updated without any errors
+    '''
     updateCache = Pihole.run('''
     source /opt/pihole/basic-install.sh
     distro_check
     update_package_cache
     ''')
-    assert tick_box + ' Update local cache of available packages' in updateCache.stdout
-    assert 'Error: Unable to update package cache.' not in updateCache.stdout
+    expected_stdout = tick_box + ' Update local cache of available packages'
+    assert expected_stdout in updateCache.stdout
+    assert 'error' not in updateCache.stdout.lower()
+
 
 def test_update_package_cache_failure_no_errors(Pihole):
-    ''' confirms package cache was not updated'''
-    mock_command('apt-get', {'update':('', '1')}, Pihole)
+    '''
+    confirms package cache was not updated
+    '''
+    mock_command('apt-get', {'update': ('', '1')}, Pihole)
     updateCache = Pihole.run('''
     source /opt/pihole/basic-install.sh
     distro_check
     update_package_cache
     ''')
-    assert cross_box + ' Update local cache of available packages' in updateCache.stdout
+    expected_stdout = cross_box + ' Update local cache of available packages'
+    assert expected_stdout in updateCache.stdout
     assert 'Error: Unable to update package cache.' in updateCache.stdout
 
+
 def test_FTL_detect_aarch64_no_errors(Pihole):
-    ''' confirms only aarch64 package is downloaded for FTL engine '''
+    '''
+    confirms only aarch64 package is downloaded for FTL engine
+    '''
     # mock uname to return aarch64 platform
-    mock_command('uname', {'-m':('aarch64', '0')}, Pihole)
+    mock_command('uname', {'-m': ('aarch64', '0')}, Pihole)
     # mock ldd to respond with aarch64 shared library
-    mock_command('ldd', {'/bin/ls':('/lib/ld-linux-aarch64.so.1', '0')}, Pihole)
+    mock_command(
+        'ldd',
+        {
+            '/bin/ls': (
+                '/lib/ld-linux-aarch64.so.1',
+                '0'
+            )
+        },
+        Pihole
+    )
     detectPlatform = Pihole.run('''
     source /opt/pihole/basic-install.sh
     FTLdetect
@@ -226,29 +406,36 @@ def test_FTL_detect_aarch64_no_errors(Pihole):
     expected_stdout = tick_box + ' Downloading and Installing FTL'
     assert expected_stdout in detectPlatform.stdout
 
+
 def test_FTL_detect_armv6l_no_errors(Pihole):
-    ''' confirms only armv6l package is downloaded for FTL engine '''
+    '''
+    confirms only armv6l package is downloaded for FTL engine
+    '''
     # mock uname to return armv6l platform
-    mock_command('uname', {'-m':('armv6l', '0')}, Pihole)
+    mock_command('uname', {'-m': ('armv6l', '0')}, Pihole)
     # mock ldd to respond with aarch64 shared library
-    mock_command('ldd', {'/bin/ls':('/lib/ld-linux-armhf.so.3', '0')}, Pihole)
+    mock_command('ldd', {'/bin/ls': ('/lib/ld-linux-armhf.so.3', '0')}, Pihole)
     detectPlatform = Pihole.run('''
     source /opt/pihole/basic-install.sh
     FTLdetect
     ''')
     expected_stdout = info_box + ' FTL Checks...'
     assert expected_stdout in detectPlatform.stdout
-    expected_stdout = tick_box + ' Detected ARM-hf architecture (armv6 or lower)'
+    expected_stdout = tick_box + (' Detected ARM-hf architecture '
+                                  '(armv6 or lower)')
     assert expected_stdout in detectPlatform.stdout
     expected_stdout = tick_box + ' Downloading and Installing FTL'
     assert expected_stdout in detectPlatform.stdout
 
+
 def test_FTL_detect_armv7l_no_errors(Pihole):
-    ''' confirms only armv7l package is downloaded for FTL engine '''
+    '''
+    confirms only armv7l package is downloaded for FTL engine
+    '''
     # mock uname to return armv7l platform
-    mock_command('uname', {'-m':('armv7l', '0')}, Pihole)
+    mock_command('uname', {'-m': ('armv7l', '0')}, Pihole)
     # mock ldd to respond with aarch64 shared library
-    mock_command('ldd', {'/bin/ls':('/lib/ld-linux-armhf.so.3', '0')}, Pihole)
+    mock_command('ldd', {'/bin/ls': ('/lib/ld-linux-armhf.so.3', '0')}, Pihole)
     detectPlatform = Pihole.run('''
     source /opt/pihole/basic-install.sh
     FTLdetect
@@ -260,8 +447,11 @@ def test_FTL_detect_armv7l_no_errors(Pihole):
     expected_stdout = tick_box + ' Downloading and Installing FTL'
     assert expected_stdout in detectPlatform.stdout
 
+
 def test_FTL_detect_x86_64_no_errors(Pihole):
-    ''' confirms only x86_64 package is downloaded for FTL engine '''
+    '''
+    confirms only x86_64 package is downloaded for FTL engine
+    '''
     detectPlatform = Pihole.run('''
     source /opt/pihole/basic-install.sh
     FTLdetect
@@ -273,10 +463,11 @@ def test_FTL_detect_x86_64_no_errors(Pihole):
     expected_stdout = tick_box + ' Downloading and Installing FTL'
     assert expected_stdout in detectPlatform.stdout
 
+
 def test_FTL_detect_unknown_no_errors(Pihole):
     ''' confirms only generic package is downloaded for FTL engine '''
     # mock uname to return generic platform
-    mock_command('uname', {'-m':('mips', '0')}, Pihole)
+    mock_command('uname', {'-m': ('mips', '0')}, Pihole)
     detectPlatform = Pihole.run('''
     source /opt/pihole/basic-install.sh
     FTLdetect
@@ -284,8 +475,11 @@ def test_FTL_detect_unknown_no_errors(Pihole):
     expected_stdout = 'Not able to detect architecture (unknown: mips)'
     assert expected_stdout in detectPlatform.stdout
 
+
 def test_FTL_download_aarch64_no_errors(Pihole):
-    ''' confirms only aarch64 package is downloaded for FTL engine '''
+    '''
+    confirms only aarch64 package is downloaded for FTL engine
+    '''
     # mock uname to return generic platform
     download_binary = Pihole.run('''
     source /opt/pihole/basic-install.sh
@@ -293,13 +487,13 @@ def test_FTL_download_aarch64_no_errors(Pihole):
     ''')
     expected_stdout = tick_box + ' Downloading and Installing FTL'
     assert expected_stdout in download_binary.stdout
-    error = 'Error: Download of binary from Github failed'
-    assert error not in download_binary.stdout
-    error = 'Error: URL not found'
-    assert error not in download_binary.stdout
+    assert 'error' not in download_binary.stdout.lower()
+
 
 def test_FTL_download_unknown_fails_no_errors(Pihole):
-    ''' confirms unknown binary is not downloaded for FTL engine '''
+    '''
+    confirms unknown binary is not downloaded for FTL engine
+    '''
     # mock uname to return generic platform
     download_binary = Pihole.run('''
     source /opt/pihole/basic-install.sh
@@ -310,8 +504,11 @@ def test_FTL_download_unknown_fails_no_errors(Pihole):
     error = 'Error: URL not found'
     assert error in download_binary.stdout
 
+
 def test_FTL_binary_installed_and_responsive_no_errors(Pihole):
-    ''' confirms FTL binary is copied and functional in installed location '''
+    '''
+    confirms FTL binary is copied and functional in installed location
+    '''
     installed_binary = Pihole.run('''
     source /opt/pihole/basic-install.sh
     FTLdetect
@@ -320,8 +517,11 @@ def test_FTL_binary_installed_and_responsive_no_errors(Pihole):
     expected_stdout = 'v'
     assert expected_stdout in installed_binary.stdout
 
+
 # def test_FTL_support_files_installed(Pihole):
-#     ''' confirms FTL support files are installed '''
+#     '''
+#     confirms FTL support files are installed
+#     '''
 #     support_files = Pihole.run('''
 #     source /opt/pihole/basic-install.sh
 #     FTLdetect
@@ -334,21 +534,46 @@ def test_FTL_binary_installed_and_responsive_no_errors(Pihole):
 #     assert '644 /run/pihole-FTL.pid' in support_files.stdout
 #     assert '644 /var/log/pihole-FTL.log' in support_files.stdout
 
+
 def test_IPv6_only_link_local(Pihole):
-    ''' confirms IPv6 blocking is disabled for Link-local address '''
+    '''
+    confirms IPv6 blocking is disabled for Link-local address
+    '''
     # mock ip -6 address to return Link-local address
-    mock_command_2('ip', {'-6 address':('inet6 fe80::d210:52fa:fe00:7ad7/64 scope link', '0')}, Pihole)
+    mock_command_2(
+        'ip',
+        {
+            '-6 address': (
+                'inet6 fe80::d210:52fa:fe00:7ad7/64 scope link',
+                '0'
+            )
+        },
+        Pihole
+    )
     detectPlatform = Pihole.run('''
     source /opt/pihole/basic-install.sh
     useIPv6dialog
     ''')
-    expected_stdout = 'Unable to find IPv6 ULA/GUA address, IPv6 adblocking will not be enabled'
+    expected_stdout = ('Unable to find IPv6 ULA/GUA address, '
+                       'IPv6 adblocking will not be enabled')
     assert expected_stdout in detectPlatform.stdout
 
+
 def test_IPv6_only_ULA(Pihole):
-    ''' confirms IPv6 blocking is enabled for ULA addresses '''
+    '''
+    confirms IPv6 blocking is enabled for ULA addresses
+    '''
     # mock ip -6 address to return ULA address
-    mock_command_2('ip', {'-6 address':('inet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global', '0')}, Pihole)
+    mock_command_2(
+        'ip',
+        {
+            '-6 address': (
+                'inet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global',
+                '0'
+            )
+        },
+        Pihole
+    )
     detectPlatform = Pihole.run('''
     source /opt/pihole/basic-install.sh
     useIPv6dialog
@@ -356,10 +581,22 @@ def test_IPv6_only_ULA(Pihole):
     expected_stdout = 'Found IPv6 ULA address, using it for blocking IPv6 ads'
     assert expected_stdout in detectPlatform.stdout
 
+
 def test_IPv6_only_GUA(Pihole):
-    ''' confirms IPv6 blocking is enabled for GUA addresses '''
+    '''
+    confirms IPv6 blocking is enabled for GUA addresses
+    '''
     # mock ip -6 address to return GUA address
-    mock_command_2('ip', {'-6 address':('inet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global', '0')}, Pihole)
+    mock_command_2(
+        'ip',
+        {
+            '-6 address': (
+                'inet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global',
+                '0'
+            )
+        },
+        Pihole
+    )
     detectPlatform = Pihole.run('''
     source /opt/pihole/basic-install.sh
     useIPv6dialog
@@ -367,10 +604,23 @@ def test_IPv6_only_GUA(Pihole):
     expected_stdout = 'Found IPv6 GUA address, using it for blocking IPv6 ads'
     assert expected_stdout in detectPlatform.stdout
 
+
 def test_IPv6_GUA_ULA_test(Pihole):
-    ''' confirms IPv6 blocking is enabled for GUA and ULA addresses '''
+    '''
+    confirms IPv6 blocking is enabled for GUA and ULA addresses
+    '''
     # mock ip -6 address to return GUA and ULA addresses
-    mock_command_2('ip', {'-6 address':('inet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global\ninet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global', '0')}, Pihole)
+    mock_command_2(
+        'ip',
+        {
+            '-6 address': (
+                'inet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global\n'
+                'inet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global',
+                '0'
+            )
+        },
+        Pihole
+    )
     detectPlatform = Pihole.run('''
     source /opt/pihole/basic-install.sh
     useIPv6dialog
@@ -378,61 +628,26 @@ def test_IPv6_GUA_ULA_test(Pihole):
     expected_stdout = 'Found IPv6 ULA address, using it for blocking IPv6 ads'
     assert expected_stdout in detectPlatform.stdout
 
+
 def test_IPv6_ULA_GUA_test(Pihole):
-    ''' confirms IPv6 blocking is enabled for GUA and ULA addresses '''
+    '''
+    confirms IPv6 blocking is enabled for GUA and ULA addresses
+    '''
     # mock ip -6 address to return ULA and GUA addresses
-    mock_command_2('ip', {'-6 address':('inet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global\ninet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global', '0')}, Pihole)
+    mock_command_2(
+        'ip',
+        {
+            '-6 address': (
+                'inet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global\n'
+                'inet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global',
+                '0'
+            )
+        },
+        Pihole
+    )
     detectPlatform = Pihole.run('''
     source /opt/pihole/basic-install.sh
     useIPv6dialog
     ''')
     expected_stdout = 'Found IPv6 ULA address, using it for blocking IPv6 ads'
     assert expected_stdout in detectPlatform.stdout
-
-# Helper functions
-def mock_command(script, args, container):
-    ''' Allows for setup of commands we don't really want to have to run for real in unit tests '''
-    full_script_path = '/usr/local/bin/{}'.format(script)
-    mock_script = dedent('''\
-    #!/bin/bash -e
-    echo "\$0 \$@" >> /var/log/{script}
-    case "\$1" in'''.format(script=script))
-    for k, v in args.iteritems():
-        case = dedent('''
-        {arg})
-        echo {res}
-        exit {retcode}
-        ;;'''.format(arg=k, res=v[0], retcode=v[1]))
-        mock_script += case
-    mock_script += dedent('''
-    esac''')
-    container.run('''
-    cat <<EOF> {script}\n{content}\nEOF
-    chmod +x {script}
-    rm -f /var/log/{scriptlog}'''.format(script=full_script_path, content=mock_script, scriptlog=script))
-
-def mock_command_2(script, args, container):
-    ''' Allows for setup of commands we don't really want to have to run for real in unit tests '''
-    full_script_path = '/usr/local/bin/{}'.format(script)
-    mock_script = dedent('''\
-    #!/bin/bash -e
-    echo "\$0 \$@" >> /var/log/{script}
-    case "\$1 \$2" in'''.format(script=script))
-    for k, v in args.iteritems():
-        case = dedent('''
-        \"{arg}\")
-        echo \"{res}\"
-        exit {retcode}
-        ;;'''.format(arg=k, res=v[0], retcode=v[1]))
-        mock_script += case
-    mock_script += dedent('''
-    esac''')
-    container.run('''
-    cat <<EOF> {script}\n{content}\nEOF
-    chmod +x {script}
-    rm -f /var/log/{scriptlog}'''.format(script=full_script_path, content=mock_script, scriptlog=script))
-
-def run_script(Pihole, script):
-    result = Pihole.run(script)
-    assert result.rc == 0
-    return result
diff --git a/test/test_centos_fedora_support.py b/test/test_centos_fedora_support.py
new file mode 100644
index 0000000000000000000000000000000000000000..8318e44a6c21eefc5363b8f42e311c1f94139fb7
--- /dev/null
+++ b/test/test_centos_fedora_support.py
@@ -0,0 +1,209 @@
+import pytest
+from conftest import (
+    tick_box,
+    info_box,
+    cross_box,
+    mock_command,
+    mock_command_2,
+)
+
+
+@pytest.mark.parametrize("tag", [('fedora'), ])
+def test_epel_and_remi_not_installed_fedora(Pihole):
+    '''
+    confirms installer does not attempt to install EPEL/REMI repositories
+    on Fedora
+    '''
+    distro_check = Pihole.run('''
+    source /opt/pihole/basic-install.sh
+    distro_check
+    ''')
+    assert distro_check.stdout == ''
+
+    epel_package = Pihole.package('epel-release')
+    assert not epel_package.is_installed
+    remi_package = Pihole.package('remi-release')
+    assert not remi_package.is_installed
+
+
+@pytest.mark.parametrize("tag", [('centos'), ])
+def test_release_supported_version_check_centos(Pihole):
+    '''
+    confirms installer exits on unsupported releases of CentOS
+    '''
+    # mock CentOS release < 7 (unsupported)
+    mock_command_2(
+        'rpm',
+        {"-q --queryformat '%{VERSION}' centos-release'": (
+            '5',
+            '0'
+        )},
+        Pihole
+    )
+    distro_check = Pihole.run('''
+    source /opt/pihole/basic-install.sh
+    distro_check
+    ''')
+    expected_stdout = cross_box + (' CentOS  is not suported.')
+    assert expected_stdout in distro_check.stdout
+    expected_stdout = 'Please update to CentOS release 7 or later'
+    assert expected_stdout in distro_check.stdout
+
+
+@pytest.mark.parametrize("tag", [('centos'), ])
+def test_enable_epel_repository_centos(Pihole):
+    '''
+    confirms the EPEL package repository is enabled when installed on CentOS
+    '''
+    distro_check = Pihole.run('''
+    source /opt/pihole/basic-install.sh
+    distro_check
+    ''')
+    expected_stdout = info_box + (' Enabling EPEL package repository '
+                                  '(https://fedoraproject.org/wiki/EPEL)')
+    assert expected_stdout in distro_check.stdout
+    expected_stdout = tick_box + ' Installed epel-release'
+    assert expected_stdout in distro_check.stdout
+    epel_package = Pihole.package('epel-release')
+    assert epel_package.is_installed
+
+
+@pytest.mark.parametrize("tag", [('centos'), ])
+def test_php_upgrade_default_optout_centos(Pihole):
+    '''
+    confirms the default behavior to opt-out of installing PHP7 from REMI
+    '''
+    distro_check = Pihole.run('''
+    source /opt/pihole/basic-install.sh
+    distro_check
+    ''')
+    expected_stdout = info_box + (' User opt-out of PHP 7 upgrade on CentOS. '
+                                  'Deprecated PHP may be in use.')
+    assert expected_stdout in distro_check.stdout
+    remi_package = Pihole.package('remi-release')
+    assert not remi_package.is_installed
+
+
+@pytest.mark.parametrize("tag", [('centos'), ])
+def test_php_upgrade_user_optout_centos(Pihole):
+    '''
+    confirms installer behavior when user opt-out of installing PHP7 from REMI
+    (php not currently installed)
+    '''
+    # Whiptail dialog returns Cancel for user prompt
+    mock_command('whiptail', {'*': ('', '1')}, Pihole)
+    distro_check = Pihole.run('''
+    source /opt/pihole/basic-install.sh
+    distro_check
+    ''')
+    expected_stdout = info_box + (' User opt-out of PHP 7 upgrade on CentOS. '
+                                  'Deprecated PHP may be in use.')
+    assert expected_stdout in distro_check.stdout
+    remi_package = Pihole.package('remi-release')
+    assert not remi_package.is_installed
+
+
+@pytest.mark.parametrize("tag", [('centos'), ])
+def test_php_upgrade_user_optin_centos(Pihole):
+    '''
+    confirms installer behavior when user opt-in to installing PHP7 from REMI
+    (php not currently installed)
+    '''
+    # Whiptail dialog returns Continue for user prompt
+    mock_command('whiptail', {'*': ('', '0')}, Pihole)
+    distro_check = Pihole.run('''
+    source /opt/pihole/basic-install.sh
+    distro_check
+    ''')
+    assert 'opt-out' not in distro_check.stdout
+    expected_stdout = info_box + (' Enabling Remi\'s RPM repository '
+                                  '(https://rpms.remirepo.net)')
+    assert expected_stdout in distro_check.stdout
+    expected_stdout = tick_box + (' Remi\'s RPM repository has '
+                                  'been enabled for PHP7')
+    assert expected_stdout in distro_check.stdout
+    remi_package = Pihole.package('remi-release')
+    assert remi_package.is_installed
+
+
+@pytest.mark.parametrize("tag", [('centos'), ])
+def test_php_version_lt_7_detected_upgrade_default_optout_centos(Pihole):
+    '''
+    confirms the default behavior to opt-out of upgrading to PHP7 from REMI
+    '''
+    # first we will install the default php version to test installer behavior
+    php_install = Pihole.run('yum install -y php')
+    assert php_install.rc == 0
+    php_package = Pihole.package('php')
+    default_centos_php_version = php_package.version.split('.')[0]
+    if int(default_centos_php_version) >= 7:  # PHP7 is supported/recommended
+        pytest.skip("Test deprecated . Detected default PHP version >= 7")
+    distro_check = Pihole.run('''
+    source /opt/pihole/basic-install.sh
+    distro_check
+    ''')
+    expected_stdout = info_box + (' User opt-out of PHP 7 upgrade on CentOS. '
+                                  'Deprecated PHP may be in use.')
+    assert expected_stdout in distro_check.stdout
+    remi_package = Pihole.package('remi-release')
+    assert not remi_package.is_installed
+
+
+@pytest.mark.parametrize("tag", [('centos'), ])
+def test_php_version_lt_7_detected_upgrade_user_optout_centos(Pihole):
+    '''
+    confirms installer behavior when user opt-out to upgrade to PHP7 via REMI
+    '''
+    # first we will install the default php version to test installer behavior
+    php_install = Pihole.run('yum install -y php')
+    assert php_install.rc == 0
+    php_package = Pihole.package('php')
+    default_centos_php_version = php_package.version.split('.')[0]
+    if int(default_centos_php_version) >= 7:  # PHP7 is supported/recommended
+        pytest.skip("Test deprecated . Detected default PHP version >= 7")
+    # Whiptail dialog returns Cancel for user prompt
+    mock_command('whiptail', {'*': ('', '1')}, Pihole)
+    distro_check = Pihole.run('''
+    source /opt/pihole/basic-install.sh
+    distro_check
+    ''')
+    expected_stdout = info_box + (' User opt-out of PHP 7 upgrade on CentOS. '
+                                  'Deprecated PHP may be in use.')
+    assert expected_stdout in distro_check.stdout
+    remi_package = Pihole.package('remi-release')
+    assert not remi_package.is_installed
+
+
+@pytest.mark.parametrize("tag", [('centos'), ])
+def test_php_version_lt_7_detected_upgrade_user_optin_centos(Pihole):
+    '''
+    confirms installer behavior when user opt-in to upgrade to PHP7 via REMI
+    '''
+    # first we will install the default php version to test installer behavior
+    php_install = Pihole.run('yum install -y php')
+    assert php_install.rc == 0
+    php_package = Pihole.package('php')
+    default_centos_php_version = php_package.version.split('.')[0]
+    if int(default_centos_php_version) >= 7:  # PHP7 is supported/recommended
+        pytest.skip("Test deprecated . Detected default PHP version >= 7")
+    # Whiptail dialog returns Continue for user prompt
+    mock_command('whiptail', {'*': ('', '0')}, Pihole)
+    distro_check = Pihole.run('''
+    source /opt/pihole/basic-install.sh
+    distro_check
+    install_dependent_packages PIHOLE_WEB_DEPS[@]
+    ''')
+    expected_stdout = info_box + (' User opt-out of PHP 7 upgrade on CentOS. '
+                                  'Deprecated PHP may be in use.')
+    assert expected_stdout not in distro_check.stdout
+    expected_stdout = info_box + (' Enabling Remi\'s RPM repository '
+                                  '(https://rpms.remirepo.net)')
+    assert expected_stdout in distro_check.stdout
+    expected_stdout = tick_box + (' Remi\'s RPM repository has '
+                                  'been enabled for PHP7')
+    assert expected_stdout in distro_check.stdout
+    remi_package = Pihole.package('remi-release')
+    assert remi_package.is_installed
+    updated_php_package = Pihole.package('php')
+    updated_php_version = updated_php_package.version.split('.')[0]
+    assert int(updated_php_version) == 7
diff --git a/test/test_shellcheck.py b/test/test_shellcheck.py
index 5b1a8961976a8e4c96569cbb14cce1ddc651ef80..43e8ad6fcc332a5dc023cb12c9d8486a1c75574e 100644
--- a/test/test_shellcheck.py
+++ b/test/test_shellcheck.py
@@ -1,13 +1,18 @@
-import pytest
 import testinfra
 
 run_local = testinfra.get_backend(
     "local://"
 ).get_module("Command").run
 
+
 def test_scripts_pass_shellcheck():
-    ''' Make sure shellcheck does not find anything wrong with our shell scripts '''
-    shellcheck = "find . -type f -name 'update.sh' | while read file; do shellcheck -x \"$file\" -e SC1090,SC1091; done;"
+    '''
+    Make sure shellcheck does not find anything wrong with our shell scripts
+    '''
+    shellcheck = ("find . -type f -name 'update.sh' "
+                  "| while read file; do "
+                  "shellcheck -x \"$file\" -e SC1090,SC1091; "
+                  "done;")
     results = run_local(shellcheck)
     print results.stdout
     assert '' == results.stdout
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000000000000000000000000000000000000..e7916e044addcf27362177f8788fb29feaee7e37
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,10 @@
+[tox]
+envlist = py27
+
+[testenv]
+whitelist_externals = docker
+deps = -rrequirements.txt
+commands = docker build -f test/debian.Dockerfile -t pytest_pihole:debian .
+           docker build -f test/centos.Dockerfile -t pytest_pihole:centos .
+           docker build -f test/fedora.Dockerfile -t pytest_pihole:fedora .
+           pytest {posargs:-vv -n auto} -m "not build_stage" ./test/