· 5 years ago · Mar 08, 2020, 04:10 PM
1#!/usr/bin/env bash
2# shellcheck disable=SC1090
3
4# Pi-hole: A black hole for Internet advertisements
5# (c) 2017-2018 Pi-hole, LLC (https://pi-hole.net)
6# Network-wide ad blocking via your own hardware.
7#
8# Installs and Updates Pi-hole
9#
10# This file is copyright under the latest version of the EUPL.
11# Please see LICENSE file for your rights under this license.
12
13# pi-hole.net/donate
14#
15# Install with this command (from your Linux machine):
16#
17# curl -sSL https://install.pi-hole.net | bash
18
19# -e option instructs bash to immediately exit if any command [1] has a non-zero exit status
20# We do not want users to end up with a partially working install, so we exit the script
21# instead of continuing the installation with something broken
22set -e
23
24######## VARIABLES #########
25# For better maintainability, we store as much information that can change in variables
26# This allows us to make a change in one place that can propagate to all instances of the variable
27# These variables should all be GLOBAL variables, written in CAPS
28# Local variables will be in lowercase and will exist only within functions
29# It's still a work in progress, so you may see some variance in this guideline until it is complete
30
31# List of supported DNS servers
32DNS_SERVERS=$(cat << EOM
33Google (ECS);8.8.8.8;8.8.4.4;2001:4860:4860:0:0:0:0:8888;2001:4860:4860:0:0:0:0:8844
34OpenDNS (ECS);208.67.222.222;208.67.220.220;2620:119:35::35;2620:119:53::53
35Level3;4.2.2.1;4.2.2.2;;
36Comodo;8.26.56.26;8.20.247.20;;
37DNS.WATCH;84.200.69.80;84.200.70.40;2001:1608:10:25:0:0:1c04:b12f;2001:1608:10:25:0:0:9249:d69b
38Quad9 (filtered, DNSSEC);9.9.9.9;149.112.112.112;2620:fe::fe;2620:fe::9
39Quad9 (unfiltered, no DNSSEC);9.9.9.10;149.112.112.10;2620:fe::10;2620:fe::fe:10
40Quad9 (filtered + ECS);9.9.9.11;149.112.112.11;2620:fe::11;
41Cloudflare;1.1.1.1;1.0.0.1;2606:4700:4700::1111;2606:4700:4700::1001
42EOM
43)
44
45# Location for final installation log storage
46installLogLoc=/etc/pihole/install.log
47# This is an important file as it contains information specific to the machine it's being installed on
48setupVars=/etc/pihole/setupVars.conf
49# Pi-hole uses lighttpd as a Web server, and this is the config file for it
50# shellcheck disable=SC2034
51lighttpdConfig=/etc/lighttpd/lighttpd.conf
52# This is a file used for the colorized output
53coltable=/opt/pihole/COL_TABLE
54
55# Root of the web server
56webroot="/var/www/html"
57
58# We store several other directories and
59webInterfaceGitUrl="https://github.com/pi-hole/AdminLTE.git"
60webInterfaceDir="${webroot}/admin"
61piholeGitUrl="https://github.com/pi-hole/pi-hole.git"
62PI_HOLE_LOCAL_REPO="/etc/.pihole"
63# These are the names of pi-holes files, stored in an array
64PI_HOLE_FILES=(chronometer list piholeDebug piholeLogFlush setupLCD update version gravity uninstall webpage)
65# This directory is where the Pi-hole scripts will be installed
66PI_HOLE_INSTALL_DIR="/opt/pihole"
67PI_HOLE_CONFIG_DIR="/etc/pihole"
68PI_HOLE_BIN_DIR="/usr/local/bin"
69PI_HOLE_BLOCKPAGE_DIR="${webroot}/pihole"
70useUpdateVars=false
71
72adlistFile="/etc/pihole/adlists.list"
73regexFile="/etc/pihole/regex.list"
74# Pi-hole needs an IP address; to begin, these variables are empty since we don't know what the IP is until
75# this script can run
76IPV4_ADDRESS=""
77IPV6_ADDRESS=""
78# By default, query logging is enabled and the dashboard is set to be installed
79QUERY_LOGGING=true
80INSTALL_WEB_INTERFACE=true
81PRIVACY_LEVEL=0
82
83if [ -z "${USER}" ]; then
84 USER="$(id -un)"
85fi
86
87
88# Check if we are running on a real terminal and find the rows and columns
89# If there is no real terminal, we will default to 80x24
90if [ -t 0 ] ; then
91 screen_size=$(stty size)
92else
93 screen_size="24 80"
94fi
95# Set rows variable to contain first number
96printf -v rows '%d' "${screen_size%% *}"
97# Set columns variable to contain second number
98printf -v columns '%d' "${screen_size##* }"
99
100# Divide by two so the dialogs take up half of the screen, which looks nice.
101r=$(( rows / 2 ))
102c=$(( columns / 2 ))
103# Unless the screen is tiny
104r=$(( r < 20 ? 20 : r ))
105c=$(( c < 70 ? 70 : c ))
106
107######## Undocumented Flags. Shhh ########
108# These are undocumented flags; some of which we can use when repairing an installation
109# The runUnattended flag is one example of this
110skipSpaceCheck=false
111reconfigure=false
112runUnattended=false
113INSTALL_WEB_SERVER=true
114# Check arguments for the undocumented flags
115for var in "$@"; do
116 case "$var" in
117 "--reconfigure" ) reconfigure=true;;
118 "--i_do_not_follow_recommendations" ) skipSpaceCheck=true;;
119 "--unattended" ) runUnattended=true;;
120 "--disable-install-webserver" ) INSTALL_WEB_SERVER=false;;
121 esac
122done
123
124# If the color table file exists,
125if [[ -f "${coltable}" ]]; then
126 # source it
127 source ${coltable}
128# Otherwise,
129else
130 # Set these values so the installer can still run in color
131 COL_NC='\e[0m' # No Color
132 COL_LIGHT_GREEN='\e[1;32m'
133 COL_LIGHT_RED='\e[1;31m'
134 TICK="[${COL_LIGHT_GREEN}✓${COL_NC}]"
135 CROSS="[${COL_LIGHT_RED}✗${COL_NC}]"
136 INFO="[i]"
137 # shellcheck disable=SC2034
138 DONE="${COL_LIGHT_GREEN} done!${COL_NC}"
139 OVER="\\r\\033[K"
140fi
141
142# A simple function that just echoes out our logo in ASCII format
143# This lets users know that it is a Pi-hole, LLC product
144show_ascii_berry() {
145 echo -e "
146 ${COL_LIGHT_GREEN}.;;,.
147 .ccccc:,.
148 :cccclll:. ..,,
149 :ccccclll. ;ooodc
150 'ccll:;ll .oooodc
151 .;cll.;;looo:.
152 ${COL_LIGHT_RED}.. ','.
153 .',,,,,,'.
154 .',,,,,,,,,,.
155 .',,,,,,,,,,,,....
156 ....''',,,,,,,'.......
157 ......... .... .........
158 .......... ..........
159 .......... ..........
160 ......... .... .........
161 ........,,,,,,,'......
162 ....',,,,,,,,,,,,.
163 .',,,,,,,,,'.
164 .',,,,,,'.
165 ..'''.${COL_NC}
166"
167}
168
169is_command() {
170 # Checks for existence of string passed in as only function argument.
171 # Exit value of 0 when exists, 1 if not exists. Value is the result
172 # of the `command` shell built-in call.
173 local check_command="$1"
174
175 command -v "${check_command}" >/dev/null 2>&1
176}
177
178# Compatibility
179distro_check() {
180# If apt-get is installed, then we know it's part of the Debian family
181if is_command apt-get ; then
182 # Set some global variables here
183 # We don't set them earlier since the family might be Red Hat, so these values would be different
184 PKG_MANAGER="apt-get"
185 # A variable to store the command used to update the package cache
186 UPDATE_PKG_CACHE="${PKG_MANAGER} update"
187 # An array for something...
188 PKG_INSTALL=(${PKG_MANAGER} --yes --no-install-recommends install)
189 # grep -c will return 1 retVal on 0 matches, block this throwing the set -e with an OR TRUE
190 PKG_COUNT="${PKG_MANAGER} -s -o Debug::NoLocking=true upgrade | grep -c ^Inst || true"
191 # Some distros vary slightly so these fixes for dependencies may apply
192 # on Ubuntu 18.04.1 LTS we need to add the universe repository to gain access to dialog and dhcpcd5
193 APT_SOURCES="/etc/apt/sources.list"
194 if awk 'BEGIN{a=1;b=0}/bionic main/{a=0}/bionic.*universe/{b=1}END{exit a + b}' ${APT_SOURCES}; then
195 if ! whiptail --defaultno --title "Dependencies Require Update to Allowed Repositories" --yesno "Would you like to enable 'universe' repository?\\n\\nThis repository is required by the following packages:\\n\\n- dhcpcd5\\n- dialog" ${r} ${c}; then
196 printf " %b Aborting installation: dependencies could not be installed.\\n" "${CROSS}"
197 exit # exit the installer
198 else
199 printf " %b Enabling universe package repository for Ubuntu Bionic\\n" "${INFO}"
200 cp ${APT_SOURCES} ${APT_SOURCES}.backup # Backup current repo list
201 printf " %b Backed up current configuration to %s\\n" "${TICK}" "${APT_SOURCES}.backup"
202 add-apt-repository universe
203 printf " %b Enabled %s\\n" "${TICK}" "'universe' repository"
204 fi
205 fi
206 # Debian 7 doesn't have iproute2 so if the dry run install is successful,
207 if ${PKG_MANAGER} install --dry-run iproute2 > /dev/null 2>&1; then
208 # we can install it
209 iproute_pkg="iproute2"
210 # Otherwise,
211 else
212 # use iproute
213 iproute_pkg="iproute"
214 fi
215 # Check for and determine version number (major and minor) of current php install
216 if is_command php ; then
217 printf " %b Existing PHP installation detected : PHP version %s\\n" "${INFO}" "$(php <<< "<?php echo PHP_VERSION ?>")"
218 printf -v phpInsMajor "%d" "$(php <<< "<?php echo PHP_MAJOR_VERSION ?>")"
219 printf -v phpInsMinor "%d" "$(php <<< "<?php echo PHP_MINOR_VERSION ?>")"
220 # Is installed php version 7.0 or greater
221 if [ "${phpInsMajor}" -ge 7 ]; then
222 phpInsNewer=true
223 fi
224 fi
225 # Check if installed php is v 7.0, or newer to determine packages to install
226 if [[ "$phpInsNewer" != true ]]; then
227 # Prefer the php metapackage if it's there
228 if ${PKG_MANAGER} install --dry-run php > /dev/null 2>&1; then
229 phpVer="php"
230 # fall back on the php5 packages
231 else
232 phpVer="php5"
233 fi
234 else
235 # Newer php is installed, its common, cgi & sqlite counterparts are deps
236 phpVer="php$phpInsMajor.$phpInsMinor"
237 fi
238 # We also need the correct version for `php-sqlite` (which differs across distros)
239 if ${PKG_MANAGER} install --dry-run ${phpVer}-sqlite3 > /dev/null 2>&1; then
240 phpSqlite="sqlite3"
241 else
242 phpSqlite="sqlite"
243 fi
244 # Since our install script is so large, we need several other programs to successfully get a machine provisioned
245 # These programs are stored in an array so they can be looped through later
246 INSTALLER_DEPS=(apt-utils dialog debconf dhcpcd5 git ${iproute_pkg} whiptail)
247 # Pi-hole itself has several dependencies that also need to be installed
248 PIHOLE_DEPS=(cron curl dnsutils iputils-ping lsof netcat psmisc sudo unzip wget idn2 sqlite3 libcap2-bin dns-root-data resolvconf libcap2)
249 # The Web dashboard has some that also need to be installed
250 # It's useful to separate the two since our repos are also setup as "Core" code and "Web" code
251 PIHOLE_WEB_DEPS=(lighttpd ${phpVer}-common ${phpVer}-cgi ${phpVer}-${phpSqlite})
252 # The Web server user,
253 LIGHTTPD_USER="www-data"
254 # group,
255 LIGHTTPD_GROUP="www-data"
256 # and config file
257 LIGHTTPD_CFG="lighttpd.conf.debian"
258
259 # A function to check...
260 test_dpkg_lock() {
261 # An iterator used for counting loop iterations
262 i=0
263 # fuser is a program to show which processes use the named files, sockets, or filesystems
264 # So while the command is true
265 while fuser /var/lib/dpkg/lock >/dev/null 2>&1 ; do
266 # Wait half a second
267 sleep 0.5
268 # and increase the iterator
269 ((i=i+1))
270 done
271 # Always return success, since we only return if there is no
272 # lock (anymore)
273 return 0
274 }
275
276# If apt-get is not found, check for rpm to see if it's a Red Hat family OS
277elif is_command rpm ; then
278 # Then check if dnf or yum is the package manager
279 if is_command dnf ; then
280 PKG_MANAGER="dnf"
281 else
282 PKG_MANAGER="yum"
283 fi
284
285 # Fedora and family update cache on every PKG_INSTALL call, no need for a separate update.
286 UPDATE_PKG_CACHE=":"
287 PKG_INSTALL=(${PKG_MANAGER} install -y)
288 PKG_COUNT="${PKG_MANAGER} check-update | egrep '(.i686|.x86|.noarch|.arm|.src)' | wc -l"
289 INSTALLER_DEPS=(dialog git iproute newt procps-ng which chkconfig)
290 PIHOLE_DEPS=(bind-utils cronie curl findutils nmap-ncat sudo unzip wget libidn2 psmisc sqlite libcap)
291 PIHOLE_WEB_DEPS=(lighttpd lighttpd-fastcgi php-common php-cli php-pdo)
292 LIGHTTPD_USER="lighttpd"
293 LIGHTTPD_GROUP="lighttpd"
294 LIGHTTPD_CFG="lighttpd.conf.fedora"
295 # If the host OS is Fedora,
296 if grep -qiE 'fedora|fedberry' /etc/redhat-release; then
297 # all required packages should be available by default with the latest fedora release
298 # ensure 'php-json' is installed on Fedora (installed as dependency on CentOS7 + Remi repository)
299 PIHOLE_WEB_DEPS+=('php-json')
300 # or if host OS is CentOS,
301 elif grep -qiE 'centos|scientific' /etc/redhat-release; then
302 # Pi-Hole currently supports CentOS 7+ with PHP7+
303 SUPPORTED_CENTOS_VERSION=7
304 SUPPORTED_CENTOS_PHP_VERSION=7
305 # Check current CentOS major release version
306 CURRENT_CENTOS_VERSION=$(grep -oP '(?<= )[0-9]+(?=\.)' /etc/redhat-release)
307 # Check if CentOS version is supported
308 if [[ $CURRENT_CENTOS_VERSION -lt $SUPPORTED_CENTOS_VERSION ]]; then
309 printf " %b CentOS %s is not supported.\\n" "${CROSS}" "${CURRENT_CENTOS_VERSION}"
310 printf " Please update to CentOS release %s or later.\\n" "${SUPPORTED_CENTOS_VERSION}"
311 # exit the installer
312 exit
313 fi
314 # on CentOS we need to add the EPEL repository to gain access to Fedora packages
315 EPEL_PKG="epel-release"
316 rpm -q ${EPEL_PKG} &> /dev/null || rc=$?
317 if [[ $rc -ne 0 ]]; then
318 printf " %b Enabling EPEL package repository (https://fedoraproject.org/wiki/EPEL)\\n" "${INFO}"
319 "${PKG_INSTALL[@]}" ${EPEL_PKG} &> /dev/null
320 printf " %b Installed %s\\n" "${TICK}" "${EPEL_PKG}"
321 fi
322
323 # The default php on CentOS 7.x is 5.4 which is EOL
324 # Check if the version of PHP available via installed repositories is >= to PHP 7
325 AVAILABLE_PHP_VERSION=$(${PKG_MANAGER} info php | grep -i version | grep -o '[0-9]\+' | head -1)
326 if [[ $AVAILABLE_PHP_VERSION -ge $SUPPORTED_CENTOS_PHP_VERSION ]]; then
327 # Since PHP 7 is available by default, install via default PHP package names
328 : # do nothing as PHP is current
329 else
330 REMI_PKG="remi-release"
331 REMI_REPO="remi-php72"
332 rpm -q ${REMI_PKG} &> /dev/null || rc=$?
333 if [[ $rc -ne 0 ]]; then
334 # The PHP version available via default repositories is older than version 7
335 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
336 # User decided to NOT update PHP from REMI, attempt to install the default available PHP version
337 printf " %b User opt-out of PHP 7 upgrade on CentOS. Deprecated PHP may be in use.\\n" "${INFO}"
338 : # continue with unsupported php version
339 else
340 printf " %b Enabling Remi's RPM repository (https://rpms.remirepo.net)\\n" "${INFO}"
341 "${PKG_INSTALL[@]}" "https://rpms.remirepo.net/enterprise/${REMI_PKG}-$(rpm -E '%{rhel}').rpm" &> /dev/null
342 # enable the PHP 7 repository via yum-config-manager (provided by yum-utils)
343 "${PKG_INSTALL[@]}" "yum-utils" &> /dev/null
344 yum-config-manager --enable ${REMI_REPO} &> /dev/null
345 printf " %b Remi's RPM repository has been enabled for PHP7\\n" "${TICK}"
346 # trigger an install/update of PHP to ensure previous version of PHP is updated from REMI
347 if "${PKG_INSTALL[@]}" "php-cli" &> /dev/null; then
348 printf " %b PHP7 installed/updated via Remi's RPM repository\\n" "${TICK}"
349 else
350 printf " %b There was a problem updating to PHP7 via Remi's RPM repository\\n" "${CROSS}"
351 exit 1
352 fi
353 fi
354 fi
355 fi
356 else
357 # Warn user of unsupported version of Fedora or CentOS
358 if ! whiptail --defaultno --title "Unsupported RPM based distribution" --yesno "Would you like to continue installation on an unsupported RPM based distribution?\\n\\nPlease ensure the following packages have been installed manually:\\n\\n- lighttpd\\n- lighttpd-fastcgi\\n- PHP version 7+" ${r} ${c}; then
359 printf " %b Aborting installation due to unsupported RPM based distribution\\n" "${CROSS}"
360 exit # exit the installer
361 else
362 printf " %b Continuing installation with unsupported RPM based distribution\\n" "${INFO}"
363 fi
364 fi
365
366# If neither apt-get or yum/dnf package managers were found
367else
368 # it's not an OS we can support,
369 printf " %b OS distribution not supported\\n" "${CROSS}"
370 # so exit the installer
371 exit
372fi
373}
374
375# A function for checking if a directory is a git repository
376is_repo() {
377 # Use a named, local variable instead of the vague $1, which is the first argument passed to this function
378 # These local variables should always be lowercase
379 local directory="${1}"
380 # A local variable for the current directory
381 local curdir
382 # A variable to store the return code
383 local rc
384 # Assign the current directory variable by using pwd
385 curdir="${PWD}"
386 # If the first argument passed to this function is a directory,
387 if [[ -d "${directory}" ]]; then
388 # move into the directory
389 cd "${directory}"
390 # Use git to check if the directory is a repo
391 # git -C is not used here to support git versions older than 1.8.4
392 git status --short &> /dev/null || rc=$?
393 # If the command was not successful,
394 else
395 # Set a non-zero return code if directory does not exist
396 rc=1
397 fi
398 # Move back into the directory the user started in
399 cd "${curdir}"
400 # Return the code; if one is not set, return 0
401 return "${rc:-0}"
402}
403
404# A function to clone a repo
405make_repo() {
406 # Set named variables for better readability
407 local directory="${1}"
408 local remoteRepo="${2}"
409 # The message to display when this function is running
410 str="Clone ${remoteRepo} into ${directory}"
411 # Display the message and use the color table to preface the message with an "info" indicator
412 printf " %b %s..." "${INFO}" "${str}"
413 # If the directory exists,
414 if [[ -d "${directory}" ]]; then
415 # delete everything in it so git can clone into it
416 rm -rf "${directory}"
417 fi
418 # Clone the repo and return the return code from this command
419 git clone -q --depth 20 "${remoteRepo}" "${directory}" &> /dev/null || return $?
420 # Show a colored message showing it's status
421 printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}"
422 # Always return 0? Not sure this is correct
423 return 0
424}
425
426# 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
427update_repo() {
428 # Use named, local variables
429 # As you can see, these are the same variable names used in the last function,
430 # but since they are local, their scope does not go beyond this function
431 # This helps prevent the wrong value from being assigned if you were to set the variable as a GLOBAL one
432 local directory="${1}"
433 local curdir
434
435 # A variable to store the message we want to display;
436 # Again, it's useful to store these in variables in case we need to reuse or change the message;
437 # we only need to make one change here
438 local str="Update repo in ${1}"
439
440 # Make sure we know what directory we are in so we can move back into it
441 curdir="${PWD}"
442 # Move into the directory that was passed as an argument
443 cd "${directory}" &> /dev/null || return 1
444 # Let the user know what's happening
445 printf " %b %s..." "${INFO}" "${str}"
446 # Stash any local commits as they conflict with our working code
447 git stash --all --quiet &> /dev/null || true # Okay for stash failure
448 git clean --quiet --force -d || true # Okay for already clean directory
449 # Pull the latest commits
450 git pull --quiet &> /dev/null || return $?
451 # Show a completion message
452 printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}"
453 # Move back into the original directory
454 cd "${curdir}" &> /dev/null || return 1
455 return 0
456}
457
458# A function that combines the functions previously made
459getGitFiles() {
460 # Setup named variables for the git repos
461 # We need the directory
462 local directory="${1}"
463 # as well as the repo URL
464 local remoteRepo="${2}"
465 # A local variable containing the message to be displayed
466 local str="Check for existing repository in ${1}"
467 # Show the message
468 printf " %b %s..." "${INFO}" "${str}"
469 # Check if the directory is a repository
470 if is_repo "${directory}"; then
471 # Show that we're checking it
472 printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}"
473 # Update the repo, returning an error message on failure
474 update_repo "${directory}" || { printf "\\n %b: Could not update local repository. Contact support.%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"; exit 1; }
475 # If it's not a .git repo,
476 else
477 # Show an error
478 printf "%b %b %s\\n" "${OVER}" "${CROSS}" "${str}"
479 # Attempt to make the repository, showing an error on failure
480 make_repo "${directory}" "${remoteRepo}" || { printf "\\n %bError: Could not update local repository. Contact support.%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"; exit 1; }
481 fi
482 # echo a blank line
483 echo ""
484 # and return success?
485 return 0
486}
487
488# Reset a repo to get rid of any local changed
489resetRepo() {
490 # Use named variables for arguments
491 local directory="${1}"
492 # Move into the directory
493 cd "${directory}" &> /dev/null || return 1
494 # Store the message in a variable
495 str="Resetting repository within ${1}..."
496 # Show the message
497 printf " %b %s..." "${INFO}" "${str}"
498 # Use git to remove the local changes
499 git reset --hard &> /dev/null || return $?
500 # And show the status
501 printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}"
502 # Returning success anyway?
503 return 0
504}
505
506find_IPv4_information() {
507 # Detects IPv4 address used for communication to WAN addresses.
508 # Accepts no arguments, returns no values.
509
510 # Named, local variables
511 local route
512 local IPv4bare
513
514 # Find IP used to route to outside world by checking the the route to Google's public DNS server
515 route=$(ip route get 8.8.8.8)
516
517 # Get just the interface IPv4 address
518 # shellcheck disable=SC2059,SC2086
519 # disabled as we intentionally want to split on whitespace and have printf populate
520 # the variable with just the first field.
521 printf -v IPv4bare "$(printf ${route#*src })"
522 # Get the default gateway IPv4 address (the way to reach the Internet)
523 # shellcheck disable=SC2059,SC2086
524 printf -v IPv4gw "$(printf ${route#*via })"
525
526 if ! valid_ip "${IPv4bare}" ; then
527 IPv4bare="127.0.0.1"
528 fi
529
530 # Append the CIDR notation to the IP address, if valid_ip fails this should return 127.0.0.1/8
531 IPV4_ADDRESS=$(ip -oneline -family inet address show | grep "${IPv4bare}/" | awk '{print $4}' | awk 'END {print}')
532}
533
534# Get available interfaces that are UP
535get_available_interfaces() {
536 # There may be more than one so it's all stored in a variable
537 availableInterfaces=$(ip --oneline link show up | grep -v "lo" | awk '{print $2}' | cut -d':' -f1 | cut -d'@' -f1)
538}
539
540# A function for displaying the dialogs the user sees when first running the installer
541welcomeDialogs() {
542 # Display the welcome dialog using an appropriately sized window via the calculation conducted earlier in the script
543 whiptail --msgbox --backtitle "Welcome" --title "Pi-hole automated installer" "\\n\\nThis installer will transform your device into a network-wide ad blocker!" ${r} ${c}
544
545 # Request that users donate if they enjoy the software since we all work on it in our free time
546 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}
547
548 # Explain the need for a static address
549 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.
550
551In the next section, you can choose to use your current network settings (DHCP) or to manually edit them." ${r} ${c}
552}
553
554# We need to make sure there is enough space before installing, so there is a function to check this
555verifyFreeDiskSpace() {
556 # 50MB is the minimum space needed (45MB install (includes web admin bootstrap/jquery libraries etc) + 5MB one day of logs.)
557 # - Fourdee: Local ensures the variable is only created, and accessible within this function/void. Generally considered a "good" coding practice for non-global variables.
558 local str="Disk space check"
559 # Required space in KB
560 local required_free_kilobytes=51200
561 # Calculate existing free space on this machine
562 local existing_free_kilobytes
563 existing_free_kilobytes=$(df -Pk | grep -m1 '\/$' | awk '{print $4}')
564
565 # If the existing space is not an integer,
566 if ! [[ "${existing_free_kilobytes}" =~ ^([0-9])+$ ]]; then
567 # show an error that we can't determine the free space
568 printf " %b %s\\n" "${CROSS}" "${str}"
569 printf " %b Unknown free disk space! \\n" "${INFO}"
570 printf " We were unable to determine available free disk space on this system.\\n"
571 printf " You may override this check, however, it is not recommended.\\n"
572 printf " The option '%b--i_do_not_follow_recommendations%b' can override this.\\n" "${COL_LIGHT_RED}" "${COL_NC}"
573 printf " e.g: curl -L https://install.pi-hole.net | bash /dev/stdin %b<option>%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"
574 # exit with an error code
575 exit 1
576 # If there is insufficient free disk space,
577 elif [[ "${existing_free_kilobytes}" -lt "${required_free_kilobytes}" ]]; then
578 # show an error message
579 printf " %b %s\\n" "${CROSS}" "${str}"
580 printf " %b Your system disk appears to only have %s KB free\\n" "${INFO}" "${existing_free_kilobytes}"
581 printf " It is recommended to have a minimum of %s KB to run the Pi-hole\\n" "${required_free_kilobytes}"
582 # if the vcgencmd command exists,
583 if is_command vcgencmd ; then
584 # it's probably a Raspbian install, so show a message about expanding the filesystem
585 printf " If this is a new install you may need to expand your disk\\n"
586 printf " Run 'sudo raspi-config', and choose the 'expand file system' option\\n"
587 printf " After rebooting, run this installation again\\n"
588 printf " e.g: curl -L https://install.pi-hole.net | bash\\n"
589 fi
590 # Show there is not enough free space
591 printf "\\n %bInsufficient free space, exiting...%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"
592 # and exit with an error
593 exit 1
594 # Otherwise,
595 else
596 # Show that we're running a disk space check
597 printf " %b %s\\n" "${TICK}" "${str}"
598 fi
599}
600
601# A function that let's the user pick an interface to use with Pi-hole
602chooseInterface() {
603 # Turn the available interfaces into an array so it can be used with a whiptail dialog
604 local interfacesArray=()
605 # Number of available interfaces
606 local interfaceCount
607 # Whiptail variable storage
608 local chooseInterfaceCmd
609 # Temporary Whiptail options storage
610 local chooseInterfaceOptions
611 # Loop sentinel variable
612 local firstLoop=1
613
614 # Find out how many interfaces are available to choose from
615 interfaceCount=$(wc -l <<< "${availableInterfaces}")
616
617 # If there is one interface,
618 if [[ "${interfaceCount}" -eq 1 ]]; then
619 # Set it as the interface to use since there is no other option
620 PIHOLE_INTERFACE="${availableInterfaces}"
621 # Otherwise,
622 else
623 # While reading through the available interfaces
624 while read -r line; do
625 # use a variable to set the option as OFF to begin with
626 mode="OFF"
627 # If it's the first loop,
628 if [[ "${firstLoop}" -eq 1 ]]; then
629 # set this as the interface to use (ON)
630 firstLoop=0
631 mode="ON"
632 fi
633 # Put all these interfaces into an array
634 interfacesArray+=("${line}" "available" "${mode}")
635 # Feed the available interfaces into this while loop
636 done <<< "${availableInterfaces}"
637 # The whiptail command that will be run, stored in a variable
638 chooseInterfaceCmd=(whiptail --separate-output --radiolist "Choose An Interface (press space to select)" ${r} ${c} ${interfaceCount})
639 # Now run the command using the interfaces saved into the array
640 chooseInterfaceOptions=$("${chooseInterfaceCmd[@]}" "${interfacesArray[@]}" 2>&1 >/dev/tty) || \
641 # If the user chooses Cancel, exit
642 { printf " %bCancel was selected, exiting installer%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"; exit 1; }
643 # For each interface
644 for desiredInterface in ${chooseInterfaceOptions}; do
645 # Set the one the user selected as the interface to use
646 PIHOLE_INTERFACE=${desiredInterface}
647 # and show this information to the user
648 printf " %b Using interface: %s\\n" "${INFO}" "${PIHOLE_INTERFACE}"
649 done
650 fi
651}
652
653# This lets us prefer ULA addresses over GUA
654# This caused problems for some users when their ISP changed their IPv6 addresses
655# See https://github.com/pi-hole/pi-hole/issues/1473#issuecomment-301745953
656testIPv6() {
657 # first will contain fda2 (ULA)
658 printf -v first "%s" "${1%%:*}"
659 # value1 will contain 253 which is the decimal value corresponding to 0xfd
660 value1=$(( (0x$first)/256 ))
661 # will contain 162 which is the decimal value corresponding to 0xa2
662 value2=$(( (0x$first)%256 ))
663 # the ULA test is testing for fc00::/7 according to RFC 4193
664 if (( (value1&254)==252 )); then
665 # echoing result to calling function as return value
666 echo "ULA"
667 fi
668 # the GUA test is testing for 2000::/3 according to RFC 4291
669 if (( (value1&112)==32 )); then
670 # echoing result to calling function as return value
671 echo "GUA"
672 fi
673 # the LL test is testing for fe80::/10 according to RFC 4193
674 if (( (value1)==254 )) && (( (value2&192)==128 )); then
675 # echoing result to calling function as return value
676 echo "Link-local"
677 fi
678}
679
680# A dialog for showing the user about IPv6 blocking
681useIPv6dialog() {
682 # Determine the IPv6 address used for blocking
683 IPV6_ADDRESSES=($(ip -6 address | grep 'scope global' | awk '{print $2}'))
684
685 # For each address in the array above, determine the type of IPv6 address it is
686 for i in "${IPV6_ADDRESSES[@]}"; do
687 # Check if it's ULA, GUA, or LL by using the function created earlier
688 result=$(testIPv6 "$i")
689 # If it's a ULA address, use it and store it as a global variable
690 [[ "${result}" == "ULA" ]] && ULA_ADDRESS="${i%/*}"
691 # If it's a GUA address, we can still use it si store it as a global variable
692 [[ "${result}" == "GUA" ]] && GUA_ADDRESS="${i%/*}"
693 done
694
695 # Determine which address to be used: Prefer ULA over GUA or don't use any if none found
696 # If the ULA_ADDRESS contains a value,
697 if [[ ! -z "${ULA_ADDRESS}" ]]; then
698 # set the IPv6 address to the ULA address
699 IPV6_ADDRESS="${ULA_ADDRESS}"
700 # Show this info to the user
701 printf " %b Found IPv6 ULA address, using it for blocking IPv6 ads\\n" "${INFO}"
702 # Otherwise, if the GUA_ADDRESS has a value,
703 elif [[ ! -z "${GUA_ADDRESS}" ]]; then
704 # Let the user know
705 printf " %b Found IPv6 GUA address, using it for blocking IPv6 ads\\n" "${INFO}"
706 # And assign it to the global variable
707 IPV6_ADDRESS="${GUA_ADDRESS}"
708 # If none of those work,
709 else
710 # explain that IPv6 blocking will not be used
711 printf " %b Unable to find IPv6 ULA/GUA address, IPv6 adblocking will not be enabled\\n" "${INFO}"
712 # So set the variable to be empty
713 IPV6_ADDRESS=""
714 fi
715
716 # If the IPV6_ADDRESS contains a value
717 if [[ ! -z "${IPV6_ADDRESS}" ]]; then
718 # Display that IPv6 is supported and will be used
719 whiptail --msgbox --backtitle "IPv6..." --title "IPv6 Supported" "$IPV6_ADDRESS will be used to block ads." ${r} ${c}
720 fi
721}
722
723# A function to check if we should use IPv4 and/or IPv6 for blocking ads
724use4andor6() {
725 # Named local variables
726 local useIPv4
727 local useIPv6
728 # Let use select IPv4 and/or IPv6 via a checklist
729 cmd=(whiptail --separate-output --checklist "Select Protocols (press space to select)" ${r} ${c} 2)
730 # In an array, show the options available:
731 # IPv4 (on by default)
732 options=(IPv4 "Block ads over IPv4" on
733 # or IPv6 (on by default if available)
734 IPv6 "Block ads over IPv6" on)
735 # In a variable, show the choices available; exit if Cancel is selected
736 choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) || { printf " %bCancel was selected, exiting installer%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"; exit 1; }
737 # For each choice available,
738 for choice in ${choices}
739 do
740 # Set the values to true
741 case ${choice} in
742 IPv4 ) useIPv4=true;;
743 IPv6 ) useIPv6=true;;
744 esac
745 done
746 # If IPv4 is to be used,
747 if [[ "${useIPv4}" ]]; then
748 # Run our function to get the information we need
749 find_IPv4_information
750 getStaticIPv4Settings
751 setStaticIPv4
752 fi
753 # If IPv6 is to be used,
754 if [[ "${useIPv6}" ]]; then
755 # Run our function to get this information
756 useIPv6dialog
757 fi
758 # Echo the information to the user
759 printf " %b IPv4 address: %s\\n" "${INFO}" "${IPV4_ADDRESS}"
760 printf " %b IPv6 address: %s\\n" "${INFO}" "${IPV6_ADDRESS}"
761 # If neither protocol is selected,
762 if [[ ! "${useIPv4}" ]] && [[ ! "${useIPv6}" ]]; then
763 # Show an error in red
764 printf " %bError: Neither IPv4 or IPv6 selected%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"
765 # and exit with an error
766 exit 1
767 fi
768}
769
770#
771getStaticIPv4Settings() {
772 # Local, named variables
773 local ipSettingsCorrect
774 # Ask if the user wants to use DHCP settings as their static IP
775 # This is useful for users that are using DHCP reservations; then we can just use the information gathered via our functions
776 if whiptail --backtitle "Calibrating network interface" --title "Static IP Address" --yesno "Do you want to use your current network settings as a static address?
777 IP address: ${IPV4_ADDRESS}
778 Gateway: ${IPv4gw}" ${r} ${c}; then
779 # If they choose yes, let the user know that the IP address will not be available via DHCP and may cause a conflict.
780 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.
781If you are worried, either manually set the address, or modify the DHCP reservation pool so it does not include the IP you want.
782It 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}
783 # Nothing else to do since the variables are already set above
784 else
785 # Otherwise, we need to ask the user to input their desired settings.
786 # Start by getting the IPv4 address (pre-filling it with info gathered from DHCP)
787 # Start a loop to let the user enter their information with the chance to go back and edit it if necessary
788 until [[ "${ipSettingsCorrect}" = True ]]; do
789
790 # Ask for the IPv4 address
791 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) || \
792 # Cancelling IPv4 settings window
793 { ipSettingsCorrect=False; echo -e " ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; }
794 printf " %b Your static IPv4 address: %s\\n" "${INFO}" "${IPV4_ADDRESS}"
795
796 # Ask for the gateway
797 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) || \
798 # Cancelling gateway settings window
799 { ipSettingsCorrect=False; echo -e " ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; }
800 printf " %b Your static IPv4 gateway: %s\\n" "${INFO}" "${IPv4gw}"
801
802 # Give the user a chance to review their settings before moving on
803 if whiptail --backtitle "Calibrating network interface" --title "Static IP Address" --yesno "Are these settings correct?
804 IP address: ${IPV4_ADDRESS}
805 Gateway: ${IPv4gw}" ${r} ${c}; then
806 # After that's done, the loop ends and we move on
807 ipSettingsCorrect=True
808 else
809 # If the settings are wrong, the loop continues
810 ipSettingsCorrect=False
811 fi
812 done
813 # End the if statement for DHCP vs. static
814 fi
815}
816
817# configure networking via dhcpcd
818setDHCPCD() {
819 # check if the IP is already in the file
820 if grep -q "${IPV4_ADDRESS}" /etc/dhcpcd.conf; then
821 printf " %b Static IP already configured\\n" "${INFO}"
822 # If it's not,
823 else
824 # we can append these lines to dhcpcd.conf to enable a static IP
825 echo "interface ${PIHOLE_INTERFACE}
826 static ip_address=${IPV4_ADDRESS}
827 static routers=${IPv4gw}
828 static domain_name_servers=127.0.0.1" | tee -a /etc/dhcpcd.conf >/dev/null
829 # Then use the ip command to immediately set the new address
830 ip addr replace dev "${PIHOLE_INTERFACE}" "${IPV4_ADDRESS}"
831 # Also give a warning that the user may need to reboot their system
832 printf " %b Set IP address to %s \\n You may need to restart after the install is complete\\n" "${TICK}" "${IPV4_ADDRESS%/*}"
833 fi
834}
835
836# configure networking ifcfg-xxxx file found at /etc/sysconfig/network-scripts/
837# this function requires the full path of an ifcfg file passed as an argument
838setIFCFG() {
839 # Local, named variables
840 local IFCFG_FILE
841 local IPADDR
842 local CIDR
843 IFCFG_FILE=$1
844 printf -v IPADDR "%s" "${IPV4_ADDRESS%%/*}"
845 # check if the desired IP is already set
846 if grep -Eq "${IPADDR}(\\b|\\/)" "${IFCFG_FILE}"; then
847 printf " %b Static IP already configured\\n" "${INFO}"
848 # Otherwise,
849 else
850 # Put the IP in variables without the CIDR notation
851 printf -v CIDR "%s" "${IPV4_ADDRESS##*/}"
852 # Backup existing interface configuration:
853 cp "${IFCFG_FILE}" "${IFCFG_FILE}".pihole.orig
854 # Build Interface configuration file using the GLOBAL variables we have
855 {
856 echo "# Configured via Pi-hole installer"
857 echo "DEVICE=$PIHOLE_INTERFACE"
858 echo "BOOTPROTO=none"
859 echo "ONBOOT=yes"
860 echo "IPADDR=$IPADDR"
861 echo "PREFIX=$CIDR"
862 echo "GATEWAY=$IPv4gw"
863 echo "DNS1=$PIHOLE_DNS_1"
864 echo "DNS2=$PIHOLE_DNS_2"
865 echo "USERCTL=no"
866 }> "${IFCFG_FILE}"
867 # Use ip to immediately set the new address
868 ip addr replace dev "${PIHOLE_INTERFACE}" "${IPV4_ADDRESS}"
869 # If NetworkMangler command line interface exists and ready to mangle,
870 if is_command nmcli && nmcli general status &> /dev/null; then
871 # Tell NetworkManagler to read our new sysconfig file
872 nmcli con load "${IFCFG_FILE}" > /dev/null
873 fi
874 # Show a warning that the user may need to restart
875 printf " %b Set IP address to %s\\n You may need to restart after the install is complete\\n" "${TICK}" "${IPV4_ADDRESS%%/*}"
876 fi
877}
878
879setStaticIPv4() {
880 # Local, named variables
881 local IFCFG_FILE
882 local CONNECTION_NAME
883
884 # If a static interface is already configured, we are done.
885 if [[ -r "/etc/sysconfig/network/ifcfg-${PIHOLE_INTERFACE}" ]]; then
886 if grep -q '^BOOTPROTO=.static.' "/etc/sysconfig/network/ifcfg-${PIHOLE_INTERFACE}"; then
887 return 0
888 fi
889 fi
890 # For the Debian family, if dhcpcd.conf exists,
891 if [[ -f "/etc/dhcpcd.conf" ]]; then
892 # configure networking via dhcpcd
893 setDHCPCD
894 return 0
895 fi
896 # If a DHCPCD config file was not found, check for an ifcfg config file based on interface name
897 if [[ -f "/etc/sysconfig/network-scripts/ifcfg-${PIHOLE_INTERFACE}" ]];then
898 # If it exists,
899 IFCFG_FILE=/etc/sysconfig/network-scripts/ifcfg-${PIHOLE_INTERFACE}
900 setIFCFG "${IFCFG_FILE}"
901 return 0
902 fi
903 # if an ifcfg config does not exists for the interface name, try the connection name via network manager
904 if is_command nmcli && nmcli general status &> /dev/null; then
905 CONNECTION_NAME=$(nmcli dev show "${PIHOLE_INTERFACE}" | grep 'GENERAL.CONNECTION' | cut -d: -f2 | sed 's/^System//' | xargs | tr ' ' '_')
906 if [[ -f "/etc/sysconfig/network-scripts/ifcfg-${CONNECTION_NAME}" ]];then
907 # If it exists,
908 IFCFG_FILE=/etc/sysconfig/network-scripts/ifcfg-${CONNECTION_NAME}
909 setIFCFG "${IFCFG_FILE}"
910 return 0
911 fi
912 fi
913 # If previous conditions failed, show an error and exit
914 printf " %b Warning: Unable to locate configuration file to set static IPv4 address\\n" "${INFO}"
915 exit 1
916}
917
918# Check an IP address to see if it is a valid one
919valid_ip() {
920 # Local, named variables
921 local ip=${1}
922 local stat=1
923
924 # If the IP matches the format xxx.xxx.xxx.xxx,
925 if [[ "${ip}" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
926 # Save the old Internal Field Separator in a variable
927 OIFS=$IFS
928 # and set the new one to a dot (period)
929 IFS='.'
930 # Put the IP into an array
931 ip=(${ip})
932 # Restore the IFS to what it was
933 IFS=${OIFS}
934 ## Evaluate each octet by checking if it's less than or equal to 255 (the max for each octet)
935 [[ "${ip[0]}" -le 255 && "${ip[1]}" -le 255 \
936 && "${ip[2]}" -le 255 && "${ip[3]}" -le 255 ]]
937 # Save the exit code
938 stat=$?
939 fi
940 # Return the exit code
941 return ${stat}
942}
943
944# A function to choose the upstream DNS provider(s)
945setDNS() {
946 # Local, named variables
947 local DNSSettingsCorrect
948
949 # In an array, list the available upstream providers
950 DNSChooseOptions=()
951 local DNSServerCount=0
952 # Save the old Internal Field Separator in a variable
953 OIFS=$IFS
954 # and set the new one to newline
955 IFS=$'\n'
956 # Put the DNS Servers into an array
957 for DNSServer in ${DNS_SERVERS}
958 do
959 DNSName="$(cut -d';' -f1 <<< "${DNSServer}")"
960 DNSChooseOptions[DNSServerCount]="${DNSName}"
961 (( DNSServerCount=DNSServerCount+1 ))
962 DNSChooseOptions[DNSServerCount]=""
963 (( DNSServerCount=DNSServerCount+1 ))
964 done
965 DNSChooseOptions[DNSServerCount]="Custom"
966 (( DNSServerCount=DNSServerCount+1 ))
967 DNSChooseOptions[DNSServerCount]=""
968 # Restore the IFS to what it was
969 IFS=${OIFS}
970 # In a whiptail dialog, show the options
971 DNSchoices=$(whiptail --separate-output --menu "Select Upstream DNS Provider. To use your own, select Custom." ${r} ${c} 7 \
972 "${DNSChooseOptions[@]}" 2>&1 >/dev/tty) || \
973 # exit if Cancel is selected
974 { printf " %bCancel was selected, exiting installer%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"; exit 1; }
975
976 # Display the selection
977 printf " %b Using " "${INFO}"
978 # Depending on the user's choice, set the GLOBAl variables to the IP of the respective provider
979 if [[ "${DNSchoices}" == "Custom" ]]
980 then
981 # Until the DNS settings are selected,
982 until [[ "${DNSSettingsCorrect}" = True ]]; do
983 #
984 strInvalid="Invalid"
985 # If the first
986 if [[ ! "${PIHOLE_DNS_1}" ]]; then
987 # and second upstream servers do not exist
988 if [[ ! "${PIHOLE_DNS_2}" ]]; then
989 prePopulate=""
990 # Otherwise,
991 else
992 prePopulate=", ${PIHOLE_DNS_2}"
993 fi
994 elif [[ "${PIHOLE_DNS_1}" ]] && [[ ! "${PIHOLE_DNS_2}" ]]; then
995 prePopulate="${PIHOLE_DNS_1}"
996 elif [[ "${PIHOLE_DNS_1}" ]] && [[ "${PIHOLE_DNS_2}" ]]; then
997 prePopulate="${PIHOLE_DNS_1}, ${PIHOLE_DNS_2}"
998 fi
999
1000 # Dialog for the user to enter custom upstream servers
1001 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) || \
1002 { printf " %bCancel was selected, exiting installer%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"; exit 1; }
1003 # Clean user input and replace whitespace with comma.
1004 piholeDNS=$(sed 's/[, \t]\+/,/g' <<< "${piholeDNS}")
1005
1006 printf -v PIHOLE_DNS_1 "%s" "${piholeDNS%%,*}"
1007 printf -v PIHOLE_DNS_2 "%s" "${piholeDNS##*,}"
1008
1009 # If the IP is valid,
1010 if ! valid_ip "${PIHOLE_DNS_1}" || [[ ! "${PIHOLE_DNS_1}" ]]; then
1011 # store it in the variable so we can use it
1012 PIHOLE_DNS_1=${strInvalid}
1013 fi
1014 # Do the same for the secondary server
1015 if ! valid_ip "${PIHOLE_DNS_2}" && [[ "${PIHOLE_DNS_2}" ]]; then
1016 PIHOLE_DNS_2=${strInvalid}
1017 fi
1018 # If either of the DNS servers are invalid,
1019 if [[ "${PIHOLE_DNS_1}" == "${strInvalid}" ]] || [[ "${PIHOLE_DNS_2}" == "${strInvalid}" ]]; then
1020 # explain this to the user
1021 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}
1022 # and set the variables back to nothing
1023 if [[ "${PIHOLE_DNS_1}" == "${strInvalid}" ]]; then
1024 PIHOLE_DNS_1=""
1025 fi
1026 if [[ "${PIHOLE_DNS_2}" == "${strInvalid}" ]]; then
1027 PIHOLE_DNS_2=""
1028 fi
1029 # Since the settings will not work, stay in the loop
1030 DNSSettingsCorrect=False
1031 # Otherwise,
1032 else
1033 # Show the settings
1034 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
1035 # and break from the loop since the servers are valid
1036 DNSSettingsCorrect=True
1037 # Otherwise,
1038 else
1039 # If the settings are wrong, the loop continues
1040 DNSSettingsCorrect=False
1041 fi
1042 fi
1043 done
1044 else
1045 # Save the old Internal Field Separator in a variable
1046 OIFS=$IFS
1047 # and set the new one to newline
1048 IFS=$'\n'
1049 for DNSServer in ${DNS_SERVERS}
1050 do
1051 DNSName="$(cut -d';' -f1 <<< "${DNSServer}")"
1052 if [[ "${DNSchoices}" == "${DNSName}" ]]
1053 then
1054 printf "%s\\n" "${DNSName}"
1055 PIHOLE_DNS_1="$(cut -d';' -f2 <<< "${DNSServer}")"
1056 PIHOLE_DNS_2="$(cut -d';' -f3 <<< "${DNSServer}")"
1057 break
1058 fi
1059 done
1060 # Restore the IFS to what it was
1061 IFS=${OIFS}
1062 fi
1063}
1064
1065# Allow the user to enable/disable logging
1066setLogging() {
1067 # Local, named variables
1068 local LogToggleCommand
1069 local LogChooseOptions
1070 local LogChoices
1071
1072 # Ask if the user wants to log queries
1073 LogToggleCommand=(whiptail --separate-output --radiolist "Do you want to log queries?" "${r}" "${c}" 6)
1074 # The default selection is on
1075 LogChooseOptions=("On (Recommended)" "" on
1076 Off "" off)
1077 # Get the user's choice
1078 LogChoices=$("${LogToggleCommand[@]}" "${LogChooseOptions[@]}" 2>&1 >/dev/tty) || (printf " %bCancel was selected, exiting installer%b\\n" "${COL_LIGHT_RED}" "${COL_NC}" && exit 1)
1079 case ${LogChoices} in
1080 # If it's on
1081 "On (Recommended)")
1082 printf " %b Logging On.\\n" "${INFO}"
1083 # Set the GLOBAL variable to true so we know what they selected
1084 QUERY_LOGGING=true
1085 ;;
1086 # Otherwise, it's off,
1087 Off)
1088 printf " %b Logging Off.\\n" "${INFO}"
1089 # So set it to false
1090 QUERY_LOGGING=false
1091 ;;
1092 esac
1093}
1094
1095# Allow the user to set their FTL privacy level
1096setPrivacyLevel() {
1097 local LevelCommand
1098 local LevelOptions
1099
1100 LevelCommand=(whiptail --separate-output --radiolist "Select a privacy mode for FTL. https://docs.pi-hole.net/ftldns/privacylevels/" "${r}" "${c}" 6)
1101
1102 # The default selection is level 0
1103 LevelOptions=(
1104 "0" "Show everything" on
1105 "1" "Hide domains" off
1106 "2" "Hide domains and clients" off
1107 "3" "Anonymous mode" off
1108 "4" "Disabled statistics" off
1109 )
1110
1111 # Get the user's choice
1112 PRIVACY_LEVEL=$("${LevelCommand[@]}" "${LevelOptions[@]}" 2>&1 >/dev/tty) || (echo -e " ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}" && exit 1)
1113
1114 printf " %b Privacy level %d" "${INFO}" "${PRIVACY_LEVEL}"
1115}
1116
1117# Function to ask the user if they want to install the dashboard
1118setAdminFlag() {
1119 # Local, named variables
1120 local WebToggleCommand
1121 local WebChooseOptions
1122 local WebChoices
1123
1124 # Similar to the logging function, ask what the user wants
1125 WebToggleCommand=(whiptail --separate-output --radiolist "Do you wish to install the web admin interface?" ${r} ${c} 6)
1126 # with the default being enabled
1127 WebChooseOptions=("On (Recommended)" "" on
1128 Off "" off)
1129 WebChoices=$("${WebToggleCommand[@]}" "${WebChooseOptions[@]}" 2>&1 >/dev/tty) || (printf " %bCancel was selected, exiting installer%b\\n" "${COL_LIGHT_RED}" "${COL_NC}" && exit 1)
1130 # Depending on their choice
1131 case ${WebChoices} in
1132 "On (Recommended)")
1133 printf " %b Web Interface On\\n" "${INFO}"
1134 # Set it to true
1135 INSTALL_WEB_INTERFACE=true
1136 ;;
1137 Off)
1138 printf " %b Web Interface Off\\n" "${INFO}"
1139 # or false
1140 INSTALL_WEB_INTERFACE=false
1141 ;;
1142 esac
1143
1144 # Request user to install web server, if --disable-install-webserver has not been used (INSTALL_WEB_SERVER=true is default).
1145 if [[ "${INSTALL_WEB_SERVER}" == true ]]; then
1146 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)
1147 # with the default being enabled
1148 WebChooseOptions=("On (Recommended)" "" on
1149 Off "" off)
1150 WebChoices=$("${WebToggleCommand[@]}" "${WebChooseOptions[@]}" 2>&1 >/dev/tty) || (printf " %bCancel was selected, exiting installer%b\\n" "${COL_LIGHT_RED}" "${COL_NC}" && exit 1)
1151 # Depending on their choice
1152 case ${WebChoices} in
1153 "On (Recommended)")
1154 printf " %b Web Server On\\n" "${INFO}"
1155 # set it to true, as clearly seen below.
1156 INSTALL_WEB_SERVER=true
1157 ;;
1158 Off)
1159 printf " %b Web Server Off\\n" "${INFO}"
1160 # or false
1161 INSTALL_WEB_SERVER=false
1162 ;;
1163 esac
1164 fi
1165}
1166
1167# A function to display a list of example blocklists for users to select
1168chooseBlocklists() {
1169 # Back up any existing adlist file, on the off chance that it exists. Useful in case of a reconfigure.
1170 if [[ -f "${adlistFile}" ]]; then
1171 mv "${adlistFile}" "${adlistFile}.old"
1172 fi
1173 # Let user select (or not) blocklists via a checklist
1174 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}" 6)
1175 # In an array, show the options available (all off by default):
1176 options=(StevenBlack "StevenBlack's Unified Hosts List" on
1177 MalwareDom "MalwareDomains" on
1178 Cameleon "Cameleon" on
1179 DisconTrack "Disconnect.me Tracking" on
1180 DisconAd "Disconnect.me Ads" on
1181 HostsFile "Hosts-file.net Ads" on)
1182
1183 # In a variable, show the choices available; exit if Cancel is selected
1184 choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) || { printf " %bCancel was selected, exiting installer%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"; rm "${adlistFile}" ;exit 1; }
1185 # For each choice available,
1186 for choice in ${choices}
1187 do
1188 appendToListsFile "${choice}"
1189 done
1190}
1191
1192# Accept a string parameter, it must be one of the default lists
1193# This function allow to not duplicate code in chooseBlocklists and
1194# in installDefaultBlocklists
1195appendToListsFile() {
1196 case $1 in
1197 StevenBlack ) echo "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts" >> "${adlistFile}";;
1198 MalwareDom ) echo "https://mirror1.malwaredomains.com/files/justdomains" >> "${adlistFile}";;
1199 Cameleon ) echo "http://sysctl.org/cameleon/hosts" >> "${adlistFile}";;
1200 DisconTrack ) echo "https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt" >> "${adlistFile}";;
1201 DisconAd ) echo "https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt" >> "${adlistFile}";;
1202 HostsFile ) echo "https://hosts-file.net/ad_servers.txt" >> "${adlistFile}";;
1203 esac
1204}
1205
1206# Used only in unattended setup
1207# If there is already the adListFile, we keep it, else we create it using all default lists
1208installDefaultBlocklists() {
1209 # In unattended setup, could be useful to use userdefined blocklist.
1210 # If this file exists, we avoid overriding it.
1211 if [[ -f "${adlistFile}" ]]; then
1212 return;
1213 fi
1214 appendToListsFile StevenBlack
1215 appendToListsFile MalwareDom
1216 appendToListsFile Cameleon
1217 appendToListsFile DisconTrack
1218 appendToListsFile DisconAd
1219 appendToListsFile HostsFile
1220}
1221
1222# Check if /etc/dnsmasq.conf is from pi-hole. If so replace with an original and install new in .d directory
1223version_check_dnsmasq() {
1224 # Local, named variables
1225 local dnsmasq_conf="/etc/dnsmasq.conf"
1226 local dnsmasq_conf_orig="/etc/dnsmasq.conf.orig"
1227 local dnsmasq_pihole_id_string="addn-hosts=/etc/pihole/gravity.list"
1228 local dnsmasq_original_config="${PI_HOLE_LOCAL_REPO}/advanced/dnsmasq.conf.original"
1229 local dnsmasq_pihole_01_snippet="${PI_HOLE_LOCAL_REPO}/advanced/01-pihole.conf"
1230 local dnsmasq_pihole_01_location="/etc/dnsmasq.d/01-pihole.conf"
1231
1232 # If the dnsmasq config file exists
1233 if [[ -f "${dnsmasq_conf}" ]]; then
1234 printf " %b Existing dnsmasq.conf found..." "${INFO}"
1235 # If gravity.list is found within this file, we presume it's from older versions on Pi-hole,
1236 if grep -q ${dnsmasq_pihole_id_string} ${dnsmasq_conf}; then
1237 printf " it is from a previous Pi-hole install.\\n"
1238 printf " %b Backing up dnsmasq.conf to dnsmasq.conf.orig..." "${INFO}"
1239 # so backup the original file
1240 mv -f ${dnsmasq_conf} ${dnsmasq_conf_orig}
1241 printf "%b %b Backing up dnsmasq.conf to dnsmasq.conf.orig...\\n" "${OVER}" "${TICK}"
1242 printf " %b Restoring default dnsmasq.conf..." "${INFO}"
1243 # and replace it with the default
1244 cp ${dnsmasq_original_config} ${dnsmasq_conf}
1245 printf "%b %b Restoring default dnsmasq.conf...\\n" "${OVER}" "${TICK}"
1246 # Otherwise,
1247 else
1248 # Don't to anything
1249 printf " it is not a Pi-hole file, leaving alone!\\n"
1250 fi
1251 else
1252 # If a file cannot be found,
1253 printf " %b No dnsmasq.conf found... restoring default dnsmasq.conf..." "${INFO}"
1254 # restore the default one
1255 cp ${dnsmasq_original_config} ${dnsmasq_conf}
1256 printf "%b %b No dnsmasq.conf found... restoring default dnsmasq.conf...\\n" "${OVER}" "${TICK}"
1257 fi
1258
1259 printf " %b Copying 01-pihole.conf to /etc/dnsmasq.d/01-pihole.conf..." "${INFO}"
1260 # Check to see if dnsmasq directory exists (it may not due to being a fresh install and dnsmasq no longer being a dependency)
1261 if [[ ! -d "/etc/dnsmasq.d" ]];then
1262 mkdir "/etc/dnsmasq.d"
1263 fi
1264 # Copy the new Pi-hole DNS config file into the dnsmasq.d directory
1265 cp ${dnsmasq_pihole_01_snippet} ${dnsmasq_pihole_01_location}
1266 printf "%b %b Copying 01-pihole.conf to /etc/dnsmasq.d/01-pihole.conf\\n" "${OVER}" "${TICK}"
1267 # Replace our placeholder values with the GLOBAL DNS variables that we populated earlier
1268 # First, swap in the interface to listen on
1269 sed -i "s/@INT@/$PIHOLE_INTERFACE/" ${dnsmasq_pihole_01_location}
1270 if [[ "${PIHOLE_DNS_1}" != "" ]]; then
1271 # Then swap in the primary DNS server
1272 sed -i "s/@DNS1@/$PIHOLE_DNS_1/" ${dnsmasq_pihole_01_location}
1273 else
1274 #
1275 sed -i '/^server=@DNS1@/d' ${dnsmasq_pihole_01_location}
1276 fi
1277 if [[ "${PIHOLE_DNS_2}" != "" ]]; then
1278 # Then swap in the primary DNS server
1279 sed -i "s/@DNS2@/$PIHOLE_DNS_2/" ${dnsmasq_pihole_01_location}
1280 else
1281 #
1282 sed -i '/^server=@DNS2@/d' ${dnsmasq_pihole_01_location}
1283 fi
1284
1285 #
1286 sed -i 's/^#conf-dir=\/etc\/dnsmasq.d$/conf-dir=\/etc\/dnsmasq.d/' ${dnsmasq_conf}
1287
1288 # If the user does not want to enable logging,
1289 if [[ "${QUERY_LOGGING}" == false ]] ; then
1290 # Disable it by commenting out the directive in the DNS config file
1291 sed -i 's/^log-queries/#log-queries/' ${dnsmasq_pihole_01_location}
1292 # Otherwise,
1293 else
1294 # enable it by uncommenting the directive in the DNS config file
1295 sed -i 's/^#log-queries/log-queries/' ${dnsmasq_pihole_01_location}
1296 fi
1297}
1298
1299# Clean an existing installation to prepare for upgrade/reinstall
1300clean_existing() {
1301 # Local, named variables
1302 # ${1} Directory to clean
1303 local clean_directory="${1}"
1304 # Make ${2} the new one?
1305 shift
1306 # ${2} Array of files to remove
1307 local old_files=( "$@" )
1308
1309 # For each script found in the old files array
1310 for script in "${old_files[@]}"; do
1311 # Remove them
1312 rm -f "${clean_directory}/${script}.sh"
1313 done
1314}
1315
1316# Install the scripts from repository to their various locations
1317installScripts() {
1318 # Local, named variables
1319 local str="Installing scripts from ${PI_HOLE_LOCAL_REPO}"
1320 printf " %b %s..." "${INFO}" "${str}"
1321
1322 # Clear out script files from Pi-hole scripts directory.
1323 clean_existing "${PI_HOLE_INSTALL_DIR}" "${PI_HOLE_FILES[@]}"
1324
1325 # Install files from local core repository
1326 if is_repo "${PI_HOLE_LOCAL_REPO}"; then
1327 # move into the directory
1328 cd "${PI_HOLE_LOCAL_REPO}"
1329 # Install the scripts by:
1330 # -o setting the owner to the user
1331 # -Dm755 create all leading components of destination except the last, then copy the source to the destination and setting the permissions to 755
1332 #
1333 # This first one is the directory
1334 install -o "${USER}" -Dm755 -d "${PI_HOLE_INSTALL_DIR}"
1335 # The rest are the scripts Pi-hole needs
1336 install -o "${USER}" -Dm755 -t "${PI_HOLE_INSTALL_DIR}" gravity.sh
1337 install -o "${USER}" -Dm755 -t "${PI_HOLE_INSTALL_DIR}" ./advanced/Scripts/*.sh
1338 install -o "${USER}" -Dm755 -t "${PI_HOLE_INSTALL_DIR}" ./automated\ install/uninstall.sh
1339 install -o "${USER}" -Dm755 -t "${PI_HOLE_INSTALL_DIR}" ./advanced/Scripts/COL_TABLE
1340 install -o "${USER}" -Dm755 -t "${PI_HOLE_BIN_DIR}" pihole
1341 install -Dm644 ./advanced/bash-completion/pihole /etc/bash_completion.d/pihole
1342 printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}"
1343
1344 # Otherwise,
1345 else
1346 # Show an error and exit
1347 printf "%b %b %s\\n" "${OVER}" "${CROSS}" "${str}"
1348 printf "\\t\\t%bError: Local repo %s not found, exiting installer%b\\n" "${COL_LIGHT_RED}" "${PI_HOLE_LOCAL_REPO}" "${COL_NC}"
1349 return 1
1350 fi
1351}
1352
1353# Install the configs from PI_HOLE_LOCAL_REPO to their various locations
1354installConfigs() {
1355 printf "\\n %b Installing configs from %s...\\n" "${INFO}" "${PI_HOLE_LOCAL_REPO}"
1356 # Make sure Pi-hole's config files are in place
1357 version_check_dnsmasq
1358
1359 # Install list of DNS servers
1360 # Format: Name;Primary IPv4;Secondary IPv4;Primary IPv6;Secondary IPv6
1361 # Some values may be empty (for example: DNS servers without IPv6 support)
1362 echo "${DNS_SERVERS}" > "${PI_HOLE_CONFIG_DIR}/dns-servers.conf"
1363
1364 # Install empty file if it does not exist
1365 if [[ ! -r "${PI_HOLE_CONFIG_DIR}/pihole-FTL.conf" ]]; then
1366 install -d -m 0755 ${PI_HOLE_CONFIG_DIR}
1367 if ! install -o pihole -m 664 /dev/null "${PI_HOLE_CONFIG_DIR}/pihole-FTL.conf" &>/dev/null; then
1368 printf " %bError: Unable to initialize configuration file %s/pihole-FTL.conf\\n" "${COL_LIGHT_RED}" "${PI_HOLE_CONFIG_DIR}"
1369 return 1
1370 fi
1371 fi
1372 # Install an empty regex file
1373 if [[ ! -f "${regexFile}" ]]; then
1374 # Let PHP edit the regex file, if installed
1375 install -o pihole -g "${LIGHTTPD_GROUP:-pihole}" -m 664 /dev/null "${regexFile}"
1376 fi
1377 # If the user chose to install the dashboard,
1378 if [[ "${INSTALL_WEB_SERVER}" == true ]]; then
1379 # and if the Web server conf directory does not exist,
1380 if [[ ! -d "/etc/lighttpd" ]]; then
1381 # make it
1382 mkdir /etc/lighttpd
1383 # and set the owners
1384 chown "${USER}":root /etc/lighttpd
1385 # Otherwise, if the config file already exists
1386 elif [[ -f "/etc/lighttpd/lighttpd.conf" ]]; then
1387 # back up the original
1388 mv /etc/lighttpd/lighttpd.conf /etc/lighttpd/lighttpd.conf.orig
1389 fi
1390 # and copy in the config file Pi-hole needs
1391 cp ${PI_HOLE_LOCAL_REPO}/advanced/${LIGHTTPD_CFG} /etc/lighttpd/lighttpd.conf
1392 # Make sure the external.conf file exists, as lighttpd v1.4.50 crashes without it
1393 touch /etc/lighttpd/external.conf
1394 # if there is a custom block page in the html/pihole directory, replace 404 handler in lighttpd config
1395 if [[ -f "${PI_HOLE_BLOCKPAGE_DIR}/custom.php" ]]; then
1396 sed -i 's/^\(server\.error-handler-404\s*=\s*\).*$/\1"pihole\/custom\.php"/' /etc/lighttpd/lighttpd.conf
1397 fi
1398 # Make the directories if they do not exist and set the owners
1399 mkdir -p /var/run/lighttpd
1400 chown ${LIGHTTPD_USER}:${LIGHTTPD_GROUP} /var/run/lighttpd
1401 mkdir -p /var/cache/lighttpd/compress
1402 chown ${LIGHTTPD_USER}:${LIGHTTPD_GROUP} /var/cache/lighttpd/compress
1403 mkdir -p /var/cache/lighttpd/uploads
1404 chown ${LIGHTTPD_USER}:${LIGHTTPD_GROUP} /var/cache/lighttpd/uploads
1405 fi
1406}
1407
1408install_manpage() {
1409 # Copy Pi-hole man pages and call mandb to update man page database
1410 # Default location for man files for /usr/local/bin is /usr/local/share/man
1411 # on lightweight systems may not be present, so check before copying.
1412 printf " %b Testing man page installation" "${INFO}"
1413 if ! is_command mandb ; then
1414 # if mandb is not present, no manpage support
1415 printf "%b %b man not installed\\n" "${OVER}" "${INFO}"
1416 return
1417 elif [[ ! -d "/usr/local/share/man" ]]; then
1418 # appropriate directory for Pi-hole's man page is not present
1419 printf "%b %b man pages not installed\\n" "${OVER}" "${INFO}"
1420 return
1421 fi
1422 if [[ ! -d "/usr/local/share/man/man8" ]]; then
1423 # if not present, create man8 directory
1424 mkdir /usr/local/share/man/man8
1425 fi
1426 if [[ ! -d "/usr/local/share/man/man5" ]]; then
1427 # if not present, create man8 directory
1428 mkdir /usr/local/share/man/man5
1429 fi
1430 # Testing complete, copy the files & update the man db
1431 cp ${PI_HOLE_LOCAL_REPO}/manpages/pihole.8 /usr/local/share/man/man8/pihole.8
1432 cp ${PI_HOLE_LOCAL_REPO}/manpages/pihole-FTL.8 /usr/local/share/man/man8/pihole-FTL.8
1433 cp ${PI_HOLE_LOCAL_REPO}/manpages/pihole-FTL.conf.5 /usr/local/share/man/man5/pihole-FTL.conf.5
1434 if mandb -q &>/dev/null; then
1435 # Updated successfully
1436 printf "%b %b man pages installed and database updated\\n" "${OVER}" "${TICK}"
1437 return
1438 else
1439 # Something is wrong with the system's man installation, clean up
1440 # our files, (leave everything how we found it).
1441 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
1442 printf "%b %b man page db not updated, man pages not installed\\n" "${OVER}" "${CROSS}"
1443 fi
1444}
1445
1446stop_service() {
1447 # Stop service passed in as argument.
1448 # Can softfail, as process may not be installed when this is called
1449 local str="Stopping ${1} service"
1450 printf " %b %s..." "${INFO}" "${str}"
1451 if is_command systemctl ; then
1452 systemctl stop "${1}" &> /dev/null || true
1453 else
1454 service "${1}" stop &> /dev/null || true
1455 fi
1456 printf "%b %b %s...\\n" "${OVER}" "${TICK}" "${str}"
1457}
1458
1459# Start/Restart service passed in as argument
1460restart_service() {
1461 # Local, named variables
1462 local str="Restarting ${1} service"
1463 printf " %b %s..." "${INFO}" "${str}"
1464 # If systemctl exists,
1465 if is_command systemctl ; then
1466 # use that to restart the service
1467 systemctl restart "${1}" &> /dev/null
1468 # Otherwise,
1469 else
1470 # fall back to the service command
1471 service "${1}" restart &> /dev/null
1472 fi
1473 printf "%b %b %s...\\n" "${OVER}" "${TICK}" "${str}"
1474}
1475
1476# Enable service so that it will start with next reboot
1477enable_service() {
1478 # Local, named variables
1479 local str="Enabling ${1} service to start on reboot"
1480 printf " %b %s..." "${INFO}" "${str}"
1481 # If systemctl exists,
1482 if is_command systemctl ; then
1483 # use that to enable the service
1484 systemctl enable "${1}" &> /dev/null
1485 # Otherwise,
1486 else
1487 # use update-rc.d to accomplish this
1488 update-rc.d "${1}" defaults &> /dev/null
1489 fi
1490 printf "%b %b %s...\\n" "${OVER}" "${TICK}" "${str}"
1491}
1492
1493# Disable service so that it will not with next reboot
1494disable_service() {
1495 # Local, named variables
1496 local str="Disabling ${1} service"
1497 printf " %b %s..." "${INFO}" "${str}"
1498 # If systemctl exists,
1499 if is_command systemctl ; then
1500 # use that to disable the service
1501 systemctl disable "${1}" &> /dev/null
1502 # Otherwise,
1503 else
1504 # use update-rc.d to accomplish this
1505 update-rc.d "${1}" disable &> /dev/null
1506 fi
1507 printf "%b %b %s...\\n" "${OVER}" "${TICK}" "${str}"
1508}
1509
1510check_service_active() {
1511 # If systemctl exists,
1512 if is_command systemctl ; then
1513 # use that to check the status of the service
1514 systemctl is-enabled "${1}" &> /dev/null
1515 # Otherwise,
1516 else
1517 # fall back to service command
1518 service "${1}" status &> /dev/null
1519 fi
1520}
1521
1522# Systemd-resolved's DNSStubListener and dnsmasq can't share port 53.
1523disable_resolved_stublistener() {
1524 printf " %b Testing if systemd-resolved is enabled\\n" "${INFO}"
1525 # Check if Systemd-resolved's DNSStubListener is enabled and active on port 53
1526 if check_service_active "systemd-resolved"; then
1527 # Check if DNSStubListener is enabled
1528 printf " %b %b Testing if systemd-resolved DNSStub-Listener is active" "${OVER}" "${INFO}"
1529 if ( grep -E '#?DNSStubListener=yes' /etc/systemd/resolved.conf &> /dev/null ); then
1530 # Disable the DNSStubListener to unbind it from port 53
1531 # Note that this breaks dns functionality on host until dnsmasq/ftl are up and running
1532 printf "%b %b Disabling systemd-resolved DNSStubListener" "${OVER}" "${TICK}"
1533 # Make a backup of the original /etc/systemd/resolved.conf
1534 # (This will need to be restored on uninstallation)
1535 sed -r -i.orig 's/#?DNSStubListener=yes/DNSStubListener=no/g' /etc/systemd/resolved.conf
1536 printf " and restarting systemd-resolved\\n"
1537 systemctl reload-or-restart systemd-resolved
1538 else
1539 printf "%b %b Systemd-resolved does not need to be restarted\\n" "${OVER}" "${INFO}"
1540 fi
1541 else
1542 printf "%b %b Systemd-resolved is not enabled\\n" "${OVER}" "${INFO}"
1543 fi
1544}
1545
1546update_package_cache() {
1547 # Running apt-get update/upgrade with minimal output can cause some issues with
1548 # requiring user input (e.g password for phpmyadmin see #218)
1549
1550 # Update package cache on apt based OSes. Do this every time since
1551 # it's quick and packages can be updated at any time.
1552
1553 # Local, named variables
1554 local str="Update local cache of available packages"
1555 printf " %b %s..." "${INFO}" "${str}"
1556 # Create a command from the package cache variable
1557 if eval "${UPDATE_PKG_CACHE}" &> /dev/null; then
1558 printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}"
1559 # Otherwise,
1560 else
1561 # show an error and exit
1562 printf "%b %b %s\\n" "${OVER}" "${CROSS}" "${str}"
1563 printf " %bError: Unable to update package cache. Please try \"%s\"%b" "${COL_LIGHT_RED}" "${UPDATE_PKG_CACHE}" "${COL_NC}"
1564 return 1
1565 fi
1566}
1567
1568# Let user know if they have outdated packages on their system and
1569# advise them to run a package update at soonest possible.
1570notify_package_updates_available() {
1571 # Local, named variables
1572 local str="Checking ${PKG_MANAGER} for upgraded packages"
1573 printf "\\n %b %s..." "${INFO}" "${str}"
1574 # Store the list of packages in a variable
1575 updatesToInstall=$(eval "${PKG_COUNT}")
1576
1577 if [[ -d "/lib/modules/$(uname -r)" ]]; then
1578 if [[ "${updatesToInstall}" -eq 0 ]]; then
1579 printf "%b %b %s... up to date!\\n\\n" "${OVER}" "${TICK}" "${str}"
1580 else
1581 printf "%b %b %s... %s updates available\\n" "${OVER}" "${TICK}" "${str}" "${updatesToInstall}"
1582 printf " %b %bIt is recommended to update your OS after installing the Pi-hole!%b\\n\\n" "${INFO}" "${COL_LIGHT_GREEN}" "${COL_NC}"
1583 fi
1584 else
1585 printf "%b %b %s\\n" "${OVER}" "${CROSS}" "${str}"
1586 printf " Kernel update detected. If the install fails, please reboot and try again\\n"
1587 fi
1588}
1589
1590# What's this doing outside of a function in the middle of nowhere?
1591counter=0
1592
1593install_dependent_packages() {
1594 # Local, named variables should be used here, especially for an iterator
1595 # Add one to the counter
1596 counter=$((counter+1))
1597 # If it equals 1,
1598 if [[ "${counter}" == 1 ]]; then
1599 #
1600 printf " %b Installer Dependency checks...\\n" "${INFO}"
1601 else
1602 #
1603 printf " %b Main Dependency checks...\\n" "${INFO}"
1604 fi
1605
1606 # Install packages passed in via argument array
1607 # No spinner - conflicts with set -e
1608 declare -a installArray
1609
1610 # Debian based package install - debconf will download the entire package list
1611 # so we just create an array of packages not currently installed to cut down on the
1612 # amount of download traffic.
1613 # NOTE: We may be able to use this installArray in the future to create a list of package that were
1614 # installed by us, and remove only the installed packages, and not the entire list.
1615 if is_command debconf-apt-progress ; then
1616 # For each package,
1617 for i in "$@"; do
1618 printf " %b Checking for %s..." "${INFO}" "${i}"
1619 if dpkg-query -W -f='${Status}' "${i}" 2>/dev/null | grep "ok installed" &> /dev/null; then
1620 printf "%b %b Checking for %s\\n" "${OVER}" "${TICK}" "${i}"
1621 else
1622 echo -e "${OVER} ${INFO} Checking for $i (will be installed)"
1623 installArray+=("${i}")
1624 fi
1625 done
1626 if [[ "${#installArray[@]}" -gt 0 ]]; then
1627 test_dpkg_lock
1628 debconf-apt-progress -- "${PKG_INSTALL[@]}" "${installArray[@]}"
1629 return
1630 fi
1631 printf "\\n"
1632 return 0
1633 fi
1634
1635 # Install Fedora/CentOS packages
1636 for i in "$@"; do
1637 printf " %b Checking for %s..." "${INFO}" "${i}"
1638 if ${PKG_MANAGER} -q list installed "${i}" &> /dev/null; then
1639 printf "%b %b Checking for %s" "${OVER}" "${TICK}" "${i}"
1640 else
1641 printf "%b %b Checking for %s (will be installed)" "${OVER}" "${INFO}" "${i}"
1642 installArray+=("${i}")
1643 fi
1644 done
1645 if [[ "${#installArray[@]}" -gt 0 ]]; then
1646 "${PKG_INSTALL[@]}" "${installArray[@]}" &> /dev/null
1647 return
1648 fi
1649 printf "\\n"
1650 return 0
1651}
1652
1653# Install the Web interface dashboard
1654installPiholeWeb() {
1655 printf "\\n %b Installing blocking page...\\n" "${INFO}"
1656
1657 local str="Creating directory for blocking page, and copying files"
1658 printf " %b %s..." "${INFO}" "${str}"
1659 # Install the directory
1660 install -d -m 0755 ${PI_HOLE_BLOCKPAGE_DIR}
1661 # and the blockpage
1662 install -D ${PI_HOLE_LOCAL_REPO}/advanced/{index,blockingpage}.* ${PI_HOLE_BLOCKPAGE_DIR}/
1663
1664 # Remove superseded file
1665 if [[ -e "${PI_HOLE_BLOCKPAGE_DIR}/index.js" ]]; then
1666 rm "${PI_HOLE_BLOCKPAGE_DIR}/index.js"
1667 fi
1668
1669 printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}"
1670
1671 local str="Backing up index.lighttpd.html"
1672 printf " %b %s..." "${INFO}" "${str}"
1673 # If the default index file exists,
1674 if [[ -f "${webroot}/index.lighttpd.html" ]]; then
1675 # back it up
1676 mv ${webroot}/index.lighttpd.html ${webroot}/index.lighttpd.orig
1677 printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}"
1678 # Otherwise,
1679 else
1680 # don't do anything
1681 printf "%b %b %s\\n" "${OVER}" "${CROSS}" "${str}"
1682 printf " No default index.lighttpd.html file found... not backing up\\n"
1683 fi
1684
1685 # Install Sudoers file
1686 local str="Installing sudoer file"
1687 printf "\\n %b %s..." "${INFO}" "${str}"
1688 # Make the .d directory if it doesn't exist
1689 mkdir -p /etc/sudoers.d/
1690 # and copy in the pihole sudoers file
1691 install -m 0640 ${PI_HOLE_LOCAL_REPO}/advanced/Templates/pihole.sudo /etc/sudoers.d/pihole
1692 # Add lighttpd user (OS dependent) to sudoers file
1693 echo "${LIGHTTPD_USER} ALL=NOPASSWD: ${PI_HOLE_BIN_DIR}/pihole" >> /etc/sudoers.d/pihole
1694
1695 # If the Web server user is lighttpd,
1696 if [[ "$LIGHTTPD_USER" == "lighttpd" ]]; then
1697 # Allow executing pihole via sudo with Fedora
1698 # Usually /usr/local/bin ${PI_HOLE_BIN_DIR} is not permitted as directory for sudoable programs
1699 echo "Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin:${PI_HOLE_BIN_DIR}" >> /etc/sudoers.d/pihole
1700 fi
1701 # Set the strict permissions on the file
1702 chmod 0440 /etc/sudoers.d/pihole
1703 printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}"
1704}
1705
1706# Installs a cron file
1707installCron() {
1708 # Install the cron job
1709 local str="Installing latest Cron script"
1710 printf "\\n %b %s..." "${INFO}" "${str}"
1711 # Copy the cron file over from the local repo
1712 cp ${PI_HOLE_LOCAL_REPO}/advanced/Templates/pihole.cron /etc/cron.d/pihole
1713 # Randomize gravity update time
1714 sed -i "s/59 1 /$((1 + RANDOM % 58)) $((3 + RANDOM % 2))/" /etc/cron.d/pihole
1715 # Randomize update checker time
1716 sed -i "s/59 17/$((1 + RANDOM % 58)) $((12 + RANDOM % 8))/" /etc/cron.d/pihole
1717 printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}"
1718}
1719
1720# Gravity is a very important script as it aggregates all of the domains into a single HOSTS formatted list,
1721# which is what Pi-hole needs to begin blocking ads
1722runGravity() {
1723 # Run gravity in the current shell
1724 { /opt/pihole/gravity.sh --force; }
1725}
1726
1727# Check if the pihole user exists and create if it does not
1728create_pihole_user() {
1729 local str="Checking for user 'pihole'"
1730 printf " %b %s..." "${INFO}" "${str}"
1731 # If the user pihole exists,
1732 if id -u pihole &> /dev/null; then
1733 # just show a success
1734 printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}"
1735 # Otherwise,
1736 else
1737 printf "%b %b %s" "${OVER}" "${CROSS}" "${str}"
1738 local str="Creating user 'pihole'"
1739 printf "%b %b %s..." "${OVER}" "${INFO}" "${str}"
1740 # create her with the useradd command
1741 if useradd -r -s /usr/sbin/nologin pihole; then
1742 printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}"
1743 else
1744 printf "%b %b %s\\n" "${OVER}" "${CROSS}" "${str}"
1745 fi
1746 fi
1747}
1748
1749# Allow HTTP and DNS traffic
1750configureFirewall() {
1751 printf "\\n"
1752 # If a firewall is running,
1753 if firewall-cmd --state &> /dev/null; then
1754 # ask if the user wants to install Pi-hole's default firewall rules
1755 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} || \
1756 { printf " %b Not installing firewall rulesets.\\n" "${INFO}"; return 0; }
1757 printf " %b Configuring FirewallD for httpd and pihole-FTL\\n" "${TICK}"
1758 # Allow HTTP and DNS traffic
1759 firewall-cmd --permanent --add-service=http --add-service=dns
1760 # Reload the firewall to apply these changes
1761 firewall-cmd --reload
1762 return 0
1763 # Check for proper kernel modules to prevent failure
1764 elif modinfo ip_tables &> /dev/null && is_command iptables ; then
1765 # If chain Policy is not ACCEPT or last Rule is not ACCEPT
1766 # then check and insert our Rules above the DROP/REJECT Rule.
1767 if iptables -S INPUT | head -n1 | grep -qv '^-P.*ACCEPT$' || iptables -S INPUT | tail -n1 | grep -qv '^-\(A\|P\).*ACCEPT$'; then
1768 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} || \
1769 { printf " %b Not installing firewall rulesets.\\n" "${INFO}"; return 0; }
1770 printf " %b Installing new IPTables firewall rulesets\\n" "${TICK}"
1771 # Check chain first, otherwise a new rule will duplicate old ones
1772 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
1773 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
1774 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
1775 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
1776 return 0
1777 fi
1778 # Otherwise,
1779 else
1780 # no firewall is running
1781 printf " %b No active firewall detected.. skipping firewall configuration\\n" "${INFO}"
1782 # so just exit
1783 return 0
1784 fi
1785 printf " %b Skipping firewall configuration\\n" "${INFO}"
1786}
1787
1788#
1789finalExports() {
1790 # If the Web interface is not set to be installed,
1791 if [[ "${INSTALL_WEB_INTERFACE}" == false ]]; then
1792 # and if there is not an IPv4 address,
1793 if [[ "${IPV4_ADDRESS}" ]]; then
1794 # there is no block page, so set IPv4 to 0.0.0.0 (all IP addresses)
1795 IPV4_ADDRESS="0.0.0.0"
1796 fi
1797 if [[ "${IPV6_ADDRESS}" ]]; then
1798 # and IPv6 to ::/0
1799 IPV6_ADDRESS="::/0"
1800 fi
1801 fi
1802
1803 # If the setup variable file exists,
1804 if [[ -e "${setupVars}" ]]; then
1805 # update the variables in the file
1806 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}"
1807 fi
1808 # echo the information to the user
1809 {
1810 echo "PIHOLE_INTERFACE=${PIHOLE_INTERFACE}"
1811 echo "IPV4_ADDRESS=${IPV4_ADDRESS}"
1812 echo "IPV6_ADDRESS=${IPV6_ADDRESS}"
1813 echo "PIHOLE_DNS_1=${PIHOLE_DNS_1}"
1814 echo "PIHOLE_DNS_2=${PIHOLE_DNS_2}"
1815 echo "QUERY_LOGGING=${QUERY_LOGGING}"
1816 echo "INSTALL_WEB_SERVER=${INSTALL_WEB_SERVER}"
1817 echo "INSTALL_WEB_INTERFACE=${INSTALL_WEB_INTERFACE}"
1818 echo "LIGHTTPD_ENABLED=${LIGHTTPD_ENABLED}"
1819 }>> "${setupVars}"
1820
1821 # Set the privacy level
1822 sed -i '/PRIVACYLEVEL/d' "${PI_HOLE_CONFIG_DIR}/pihole-FTL.conf"
1823 echo "PRIVACYLEVEL=${PRIVACY_LEVEL}" >> "${PI_HOLE_CONFIG_DIR}/pihole-FTL.conf"
1824
1825 # Bring in the current settings and the functions to manipulate them
1826 source "${setupVars}"
1827 source "${PI_HOLE_LOCAL_REPO}/advanced/Scripts/webpage.sh"
1828
1829 # Look for DNS server settings which would have to be reapplied
1830 ProcessDNSSettings
1831
1832 # Look for DHCP server settings which would have to be reapplied
1833 ProcessDHCPSettings
1834}
1835
1836# Install the logrotate script
1837installLogrotate() {
1838
1839 local str="Installing latest logrotate script"
1840 printf "\\n %b %s..." "${INFO}" "${str}"
1841 # Copy the file over from the local repo
1842 cp ${PI_HOLE_LOCAL_REPO}/advanced/Templates/logrotate /etc/pihole/logrotate
1843 # Different operating systems have different user / group
1844 # settings for logrotate that makes it impossible to create
1845 # a static logrotate file that will work with e.g.
1846 # Rasbian and Ubuntu at the same time. Hence, we have to
1847 # customize the logrotate script here in order to reflect
1848 # the local properties of the /var/log directory
1849 logusergroup="$(stat -c '%U %G' /var/log)"
1850 # If the variable has a value,
1851 if [[ ! -z "${logusergroup}" ]]; then
1852 #
1853 sed -i "s/# su #/su ${logusergroup}/g;" /etc/pihole/logrotate
1854 fi
1855 printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}"
1856}
1857
1858# At some point in the future this list can be pruned, for now we'll need it to ensure updates don't break.
1859# Refactoring of install script has changed the name of a couple of variables. Sort them out here.
1860accountForRefactor() {
1861 sed -i 's/piholeInterface/PIHOLE_INTERFACE/g' ${setupVars}
1862 sed -i 's/IPv4_address/IPV4_ADDRESS/g' ${setupVars}
1863 sed -i 's/IPv4addr/IPV4_ADDRESS/g' ${setupVars}
1864 sed -i 's/IPv6_address/IPV6_ADDRESS/g' ${setupVars}
1865 sed -i 's/piholeIPv6/IPV6_ADDRESS/g' ${setupVars}
1866 sed -i 's/piholeDNS1/PIHOLE_DNS_1/g' ${setupVars}
1867 sed -i 's/piholeDNS2/PIHOLE_DNS_2/g' ${setupVars}
1868 sed -i 's/^INSTALL_WEB=/INSTALL_WEB_INTERFACE=/' ${setupVars}
1869 # Add 'INSTALL_WEB_SERVER', if its not been applied already: https://github.com/pi-hole/pi-hole/pull/2115
1870 if ! grep -q '^INSTALL_WEB_SERVER=' ${setupVars}; then
1871 local webserver_installed=false
1872 if grep -q '^INSTALL_WEB_INTERFACE=true' ${setupVars}; then
1873 webserver_installed=true
1874 fi
1875 echo -e "INSTALL_WEB_SERVER=$webserver_installed" >> ${setupVars}
1876 fi
1877}
1878
1879# Install base files and web interface
1880installPihole() {
1881 # Create the pihole user
1882 create_pihole_user
1883
1884 # If the user wants to install the Web interface,
1885 if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
1886 if [[ ! -d "${webroot}" ]]; then
1887 # make the Web directory if necessary
1888 install -d -m 0755 ${webroot}
1889 fi
1890
1891 if [[ "${INSTALL_WEB_SERVER}" == true ]]; then
1892 # Set the owner and permissions
1893 chown ${LIGHTTPD_USER}:${LIGHTTPD_GROUP} ${webroot}
1894 chmod 0775 ${webroot}
1895 # Give pihole access to the Web server group
1896 usermod -a -G ${LIGHTTPD_GROUP} pihole
1897 # If the lighttpd command is executable,
1898 if is_command lighty-enable-mod ; then
1899 # enable fastcgi and fastcgi-php
1900 lighty-enable-mod fastcgi fastcgi-php > /dev/null || true
1901 else
1902 # Otherwise, show info about installing them
1903 printf " %b Warning: 'lighty-enable-mod' utility not found\\n" "${INFO}"
1904 printf " Please ensure fastcgi is enabled if you experience issues\\n"
1905 fi
1906 fi
1907 fi
1908 # For updates and unattended install.
1909 if [[ "${useUpdateVars}" == true ]]; then
1910 accountForRefactor
1911 fi
1912 # Install base files and web interface
1913 if ! installScripts; then
1914 printf " %b Failure in dependent script copy function.\\n" "${CROSS}"
1915 exit 1
1916 fi
1917 # Install config files
1918 if ! installConfigs; then
1919 printf " %b Failure in dependent config copy function.\\n" "${CROSS}"
1920 exit 1
1921 fi
1922 # If the user wants to install the dashboard,
1923 if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
1924 # do so
1925 installPiholeWeb
1926 fi
1927 # Install the cron file
1928 installCron
1929 # Install the logrotate file
1930 installLogrotate
1931 # Check if dnsmasq is present. If so, disable it and back up any possible
1932 # config file
1933 disable_dnsmasq
1934 # Configure the firewall
1935 if [[ "${useUpdateVars}" == false ]]; then
1936 configureFirewall
1937 fi
1938
1939 # install a man page entry for pihole
1940 install_manpage
1941
1942 # Update setupvars.conf with any variables that may or may not have been changed during the install
1943 finalExports
1944}
1945
1946# SELinux
1947checkSelinux() {
1948 # If the getenforce command exists,
1949 if is_command getenforce ; then
1950 # Store the current mode in a variable
1951 enforceMode=$(getenforce)
1952 printf "\\n %b SELinux mode detected: %s\\n" "${INFO}" "${enforceMode}"
1953
1954 # If it's enforcing,
1955 if [[ "${enforceMode}" == "Enforcing" ]]; then
1956 # Explain Pi-hole does not support it yet
1957 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} || \
1958 { printf "\\n %bSELinux Enforcing detected, exiting installer%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"; exit 1; }
1959 printf " %b Continuing installation with SELinux Enforcing\\n" "${INFO}"
1960 printf " %b Please refer to official SELinux documentation to create a custom policy\\n" "${INFO}"
1961 fi
1962 fi
1963}
1964
1965# Installation complete message with instructions for the user
1966displayFinalMessage() {
1967 # If
1968 if [[ "${#1}" -gt 0 ]] ; then
1969 pwstring="$1"
1970 # else, if the dashboard password in the setup variables exists,
1971 elif [[ $(grep 'WEBPASSWORD' -c /etc/pihole/setupVars.conf) -gt 0 ]]; then
1972 # set a variable for evaluation later
1973 pwstring="unchanged"
1974 else
1975 # set a variable for evaluation later
1976 pwstring="NOT SET"
1977 fi
1978 # If the user wants to install the dashboard,
1979 if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
1980 # Store a message in a variable and display it
1981 additional="View the web interface at http://pi.hole/admin or http://${IPV4_ADDRESS%/*}/admin
1982
1983Your Admin Webpage login password is ${pwstring}"
1984 fi
1985
1986 # Final completion message to user
1987 whiptail --msgbox --backtitle "Make it so." --title "Installation Complete!" "Configure your devices to use the Pi-hole as their DNS server using:
1988
1989IPv4: ${IPV4_ADDRESS%/*}
1990IPv6: ${IPV6_ADDRESS:-"Not Configured"}
1991
1992If you set a new IP address, you should restart the Pi.
1993
1994The install log is in /etc/pihole.
1995
1996${additional}" ${r} ${c}
1997}
1998
1999update_dialogs() {
2000 # If pihole -r "reconfigure" option was selected,
2001 if [[ "${reconfigure}" = true ]]; then
2002 # set some variables that will be used
2003 opt1a="Repair"
2004 opt1b="This will retain existing settings"
2005 strAdd="You will remain on the same version"
2006 # Otherwise,
2007 else
2008 # set some variables with different values
2009 opt1a="Update"
2010 opt1b="This will retain existing settings."
2011 strAdd="You will be updated to the latest version."
2012 fi
2013 opt2a="Reconfigure"
2014 opt2b="This will reset your Pi-hole and allow you to enter new settings."
2015
2016 # Display the information to the user
2017 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 \
2018 "${opt1a}" "${opt1b}" \
2019 "${opt2a}" "${opt2b}" 3>&2 2>&1 1>&3) || \
2020 { printf " %bCancel was selected, exiting installer%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"; exit 1; }
2021
2022 # Set the variable based on if the user chooses
2023 case ${UpdateCmd} in
2024 # repair, or
2025 ${opt1a})
2026 printf " %b %s option selected\\n" "${INFO}" "${opt1a}"
2027 useUpdateVars=true
2028 ;;
2029 # reconfigure,
2030 ${opt2a})
2031 printf " %b %s option selected\\n" "${INFO}" "${opt2a}"
2032 useUpdateVars=false
2033 ;;
2034 esac
2035}
2036
2037check_download_exists() {
2038 status=$(curl --head --silent "https://ftl.pi-hole.net/${1}" | head -n 1)
2039 if grep -q "404" <<< "$status"; then
2040 return 1
2041 else
2042 return 0
2043 fi
2044}
2045
2046fully_fetch_repo() {
2047 # Add upstream branches to shallow clone
2048 local directory="${1}"
2049
2050 cd "${directory}" || return 1
2051 if is_repo "${directory}"; then
2052 git remote set-branches origin '*' || return 1
2053 git fetch --quiet || return 1
2054 else
2055 return 1
2056 fi
2057 return 0
2058}
2059
2060get_available_branches() {
2061 # Return available branches
2062 local directory
2063 directory="${1}"
2064 local output
2065
2066 cd "${directory}" || return 1
2067 # Get reachable remote branches, but store STDERR as STDOUT variable
2068 output=$( { git ls-remote --heads --quiet | cut -d'/' -f3- -; } 2>&1 )
2069 # echo status for calling function to capture
2070 echo "$output"
2071 return
2072}
2073
2074fetch_checkout_pull_branch() {
2075 # Check out specified branch
2076 local directory
2077 directory="${1}"
2078 local branch
2079 branch="${2}"
2080
2081 # Set the reference for the requested branch, fetch, check it put and pull it
2082 cd "${directory}" || return 1
2083 git remote set-branches origin "${branch}" || return 1
2084 git stash --all --quiet &> /dev/null || true
2085 git clean --quiet --force -d || true
2086 git fetch --quiet || return 1
2087 checkout_pull_branch "${directory}" "${branch}" || return 1
2088}
2089
2090checkout_pull_branch() {
2091 # Check out specified branch
2092 local directory
2093 directory="${1}"
2094 local branch
2095 branch="${2}"
2096 local oldbranch
2097
2098 cd "${directory}" || return 1
2099
2100 oldbranch="$(git symbolic-ref HEAD)"
2101
2102 str="Switching to branch: '${branch}' from '${oldbranch}'"
2103 printf " %b %s" "${INFO}" "$str"
2104 git checkout "${branch}" --quiet || return 1
2105 printf "%b %b %s\\n" "${OVER}" "${TICK}" "$str"
2106
2107 git_pull=$(git pull || return 1)
2108
2109 if [[ "$git_pull" == *"up-to-date"* ]]; then
2110 printf " %b %s\\n" "${INFO}" "${git_pull}"
2111 else
2112 printf "%s\\n" "$git_pull"
2113 fi
2114
2115 return 0
2116}
2117
2118clone_or_update_repos() {
2119 # If the user wants to reconfigure,
2120 if [[ "${reconfigure}" == true ]]; then
2121 printf " %b Performing reconfiguration, skipping download of local repos\\n" "${INFO}"
2122 # Reset the Core repo
2123 resetRepo ${PI_HOLE_LOCAL_REPO} || \
2124 { printf " %bUnable to reset %s, exiting installer%b\\n" "${COL_LIGHT_RED}" "${PI_HOLE_LOCAL_REPO}" "${COL_NC}"; \
2125 exit 1; \
2126 }
2127 # If the Web interface was installed,
2128 if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
2129 # reset it's repo
2130 resetRepo ${webInterfaceDir} || \
2131 { printf " %bUnable to reset %s, exiting installer%b\\n" "${COL_LIGHT_RED}" "${webInterfaceDir}" "${COL_NC}"; \
2132 exit 1; \
2133 }
2134 fi
2135 # Otherwise, a repair is happening
2136 else
2137 # so get git files for Core
2138 getGitFiles ${PI_HOLE_LOCAL_REPO} ${piholeGitUrl} || \
2139 { printf " %bUnable to clone %s into %s, unable to continue%b\\n" "${COL_LIGHT_RED}" "${piholeGitUrl}" "${PI_HOLE_LOCAL_REPO}" "${COL_NC}"; \
2140 exit 1; \
2141 }
2142 # If the Web interface was installed,
2143 if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
2144 # get the Web git files
2145 getGitFiles ${webInterfaceDir} ${webInterfaceGitUrl} || \
2146 { printf " %bUnable to clone %s into ${webInterfaceDir}, exiting installer%b\\n" "${COL_LIGHT_RED}" "${webInterfaceGitUrl}" "${COL_NC}"; \
2147 exit 1; \
2148 }
2149 fi
2150 fi
2151}
2152
2153# Download FTL binary to random temp directory and install FTL binary
2154# Disable directive for SC2120 a value _can_ be passed to this function, but it is passed from an external script that sources this one
2155# shellcheck disable=SC2120
2156FTLinstall() {
2157
2158 # Local, named variables
2159 local latesttag
2160 local str="Downloading and Installing FTL"
2161 printf " %b %s..." "${INFO}" "${str}"
2162
2163 # Move into the temp ftl directory
2164 pushd "$(mktemp -d)" > /dev/null || { printf "Unable to make temporary directory for FTL binary download\\n"; return 1; }
2165
2166 # Always replace pihole-FTL.service
2167 install -T -m 0755 "${PI_HOLE_LOCAL_REPO}/advanced/Templates/pihole-FTL.service" "/etc/init.d/pihole-FTL"
2168
2169 local ftlBranch
2170 local url
2171
2172 if [[ -f "/etc/pihole/ftlbranch" ]];then
2173 ftlBranch=$(</etc/pihole/ftlbranch)
2174 else
2175 ftlBranch="master"
2176 fi
2177
2178 local binary
2179 binary="${1}"
2180
2181 # Determine which version of FTL to download
2182 if [[ "${ftlBranch}" == "master" ]];then
2183 url="https://github.com/pi-hole/ftl/releases/latest/download"
2184 else
2185 url="https://ftl.pi-hole.net/${ftlBranch}"
2186 fi
2187
2188 # If the download worked,
2189 if curl -sSL --fail "${url}/${binary}" -o "${binary}"; then
2190 # get sha1 of the binary we just downloaded for verification.
2191 curl -sSL --fail "${url}/${binary}.sha1" -o "${binary}.sha1"
2192
2193 # If we downloaded binary file (as opposed to text),
2194 if sha1sum --status --quiet -c "${binary}".sha1; then
2195 printf "transferred... "
2196
2197 # Before stopping FTL, we download the macvendor database
2198 curl -sSL "https://ftl.pi-hole.net/macvendor.db" -o "${PI_HOLE_CONFIG_DIR}/macvendor.db" || true
2199
2200 # Stop pihole-FTL service if available
2201 stop_service pihole-FTL &> /dev/null
2202
2203 # Install the new version with the correct permissions
2204 install -T -m 0755 "${binary}" /usr/bin/pihole-FTL
2205
2206 # Move back into the original directory the user was in
2207 popd > /dev/null || { printf "Unable to return to original directory after FTL binary download.\\n"; return 1; }
2208
2209 # Installed the FTL service
2210 printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}"
2211 return 0
2212 # Otherwise,
2213 else
2214 # the download failed, so just go back to the original directory
2215 popd > /dev/null || { printf "Unable to return to original directory after FTL binary download.\\n"; return 1; }
2216 printf "%b %b %s\\n" "${OVER}" "${CROSS}" "${str}"
2217 printf " %bError: Download of %s/%s failed (checksum error)%b\\n" "${COL_LIGHT_RED}" "${url}" "${binary}" "${COL_NC}"
2218 return 1
2219 fi
2220 # Otherwise,
2221 else
2222 popd > /dev/null || { printf "Unable to return to original directory after FTL binary download.\\n"; return 1; }
2223 printf "%b %b %s\\n" "${OVER}" "${CROSS}" "${str}"
2224 # The URL could not be found
2225 printf " %bError: URL %s/%s not found%b\\n" "${COL_LIGHT_RED}" "${url}" "${binary}" "${COL_NC}"
2226 return 1
2227 fi
2228}
2229
2230disable_dnsmasq() {
2231 # dnsmasq can now be stopped and disabled if it exists
2232 if which dnsmasq &> /dev/null; then
2233 if check_service_active "dnsmasq";then
2234 printf " %b FTL can now resolve DNS Queries without dnsmasq running separately\\n" "${INFO}"
2235 stop_service dnsmasq
2236 disable_service dnsmasq
2237 fi
2238 fi
2239
2240 # Backup existing /etc/dnsmasq.conf if present and ensure that
2241 # /etc/dnsmasq.conf contains only "conf-dir=/etc/dnsmasq.d"
2242 local conffile="/etc/dnsmasq.conf"
2243 if [[ -f "${conffile}" ]]; then
2244 printf " %b Backing up %s to %s.old\\n" "${INFO}" "${conffile}" "${conffile}"
2245 mv "${conffile}" "${conffile}.old"
2246 fi
2247 # Create /etc/dnsmasq.conf
2248 echo "conf-dir=/etc/dnsmasq.d" > "${conffile}"
2249}
2250
2251get_binary_name() {
2252 # This gives the machine architecture which may be different from the OS architecture...
2253 local machine
2254 machine=$(uname -m)
2255
2256 local l_binary
2257
2258 local str="Detecting architecture"
2259 printf " %b %s..." "${INFO}" "${str}"
2260 # If the machine is arm or aarch
2261 if [[ "${machine}" == "arm"* || "${machine}" == *"aarch"* ]]; then
2262 # ARM
2263 #
2264 local rev
2265 rev=$(uname -m | sed "s/[^0-9]//g;")
2266 #
2267 local lib
2268 lib=$(ldd /bin/ls | grep -E '^\s*/lib' | awk '{ print $1 }')
2269 #
2270 if [[ "${lib}" == "/lib/ld-linux-aarch64.so.1" ]]; then
2271 printf "%b %b Detected ARM-aarch64 architecture\\n" "${OVER}" "${TICK}"
2272 # set the binary to be used
2273 l_binary="pihole-FTL-aarch64-linux-gnu"
2274 #
2275 elif [[ "${lib}" == "/lib/ld-linux-armhf.so.3" ]]; then
2276 #
2277 if [[ "${rev}" -gt 6 ]]; then
2278 printf "%b %b Detected ARM-hf architecture (armv7+)\\n" "${OVER}" "${TICK}"
2279 # set the binary to be used
2280 l_binary="pihole-FTL-arm-linux-gnueabihf"
2281 # Otherwise,
2282 else
2283 printf "%b %b Detected ARM-hf architecture (armv6 or lower) Using ARM binary\\n" "${OVER}" "${TICK}"
2284 # set the binary to be used
2285 l_binary="pihole-FTL-arm-linux-gnueabi"
2286 fi
2287 else
2288 printf "%b %b Detected ARM architecture\\n" "${OVER}" "${TICK}"
2289 # set the binary to be used
2290 l_binary="pihole-FTL-arm-linux-gnueabi"
2291 fi
2292 elif [[ "${machine}" == "x86_64" ]]; then
2293 # This gives the architecture of packages dpkg installs (for example, "i386")
2294 local dpkgarch
2295 dpkgarch=$(dpkg --print-architecture 2> /dev/null || true)
2296
2297 # Special case: This is a 32 bit OS, installed on a 64 bit machine
2298 # -> change machine architecture to download the 32 bit executable
2299 # We only check this for Debian-based systems as this has been an issue
2300 # in the past (see https://github.com/pi-hole/pi-hole/pull/2004)
2301 if [[ "${dpkgarch}" == "i386" ]]; then
2302 printf "%b %b Detected 32bit (i686) architecture\\n" "${OVER}" "${TICK}"
2303 l_binary="pihole-FTL-linux-x86_32"
2304 else
2305 # 64bit
2306 printf "%b %b Detected x86_64 architecture\\n" "${OVER}" "${TICK}"
2307 # set the binary to be used
2308 l_binary="pihole-FTL-linux-x86_64"
2309 fi
2310 else
2311 # Something else - we try to use 32bit executable and warn the user
2312 if [[ ! "${machine}" == "i686" ]]; then
2313 printf "%b %b %s...\\n" "${OVER}" "${CROSS}" "${str}"
2314 printf " %b %bNot able to detect architecture (unknown: %s), trying 32bit executable%b\\n" "${INFO}" "${COL_LIGHT_RED}" "${machine}" "${COL_NC}"
2315 printf " %b Contact Pi-hole Support if you experience issues (e.g: FTL not running)\\n" "${INFO}"
2316 else
2317 printf "%b %b Detected 32bit (i686) architecture\\n" "${OVER}" "${TICK}"
2318 fi
2319 l_binary="pihole-FTL-linux-x86_32"
2320 fi
2321
2322 echo ${l_binary}
2323}
2324
2325FTLcheckUpdate() {
2326 #In the next section we check to see if FTL is already installed (in case of pihole -r).
2327 #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
2328 printf " %b Checking for existing FTL binary...\\n" "${INFO}"
2329
2330 local ftlLoc
2331 ftlLoc=$(which pihole-FTL 2>/dev/null)
2332
2333 local ftlBranch
2334
2335 if [[ -f "/etc/pihole/ftlbranch" ]];then
2336 ftlBranch=$(</etc/pihole/ftlbranch)
2337 else
2338 ftlBranch="master"
2339 fi
2340
2341 local binary
2342 binary="${1}"
2343
2344 local remoteSha1
2345 local localSha1
2346
2347 # if dnsmasq exists and is running at this point, force reinstall of FTL Binary
2348 if which dnsmasq &> /dev/null; then
2349 if check_service_active "dnsmasq";then
2350 return 0
2351 fi
2352 fi
2353
2354 if [[ ! "${ftlBranch}" == "master" ]]; then
2355 #Check whether or not the binary for this FTL branch actually exists. If not, then there is no update!
2356 local path
2357 path="${ftlBranch}/${binary}"
2358 # shellcheck disable=SC1090
2359 if ! check_download_exists "$path"; then
2360 printf " %b Branch \"%s\" is not available.\\n" "${INFO}" "${ftlBranch}"
2361 printf " %b Use %bpihole checkout ftl [branchname]%b to switch to a valid branch.\\n" "${INFO}" "${COL_LIGHT_GREEN}" "${COL_NC}"
2362 return 2
2363 fi
2364
2365 if [[ ${ftlLoc} ]]; then
2366 # We already have a pihole-FTL binary downloaded.
2367 # 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
2368 remoteSha1=$(curl -sSL --fail "https://ftl.pi-hole.net/${ftlBranch}/${binary}.sha1" | cut -d ' ' -f 1)
2369 localSha1=$(sha1sum "$(which pihole-FTL)" | cut -d ' ' -f 1)
2370
2371 if [[ "${remoteSha1}" != "${localSha1}" ]]; then
2372 printf " %b Checksums do not match, downloading from ftl.pi-hole.net.\\n" "${INFO}"
2373 return 0
2374 else
2375 printf " %b Checksum of installed binary matches remote. No need to download!\\n" "${INFO}"
2376 return 1
2377 fi
2378 else
2379 return 0
2380 fi
2381 else
2382 if [[ ${ftlLoc} ]]; then
2383 local FTLversion
2384 FTLversion=$(/usr/bin/pihole-FTL tag)
2385 local FTLlatesttag
2386
2387 if ! FTLlatesttag=$(curl -sI https://github.com/pi-hole/FTL/releases/latest | grep --color=never -i Location | awk -F / '{print $NF}' | tr -d '[:cntrl:]'); then
2388 # There was an issue while retrieving the latest version
2389 printf " %b Failed to retrieve latest FTL release metadata" "${CROSS}"
2390 return 3
2391 fi
2392
2393 if [[ "${FTLversion}" != "${FTLlatesttag}" ]]; then
2394 return 0
2395 else
2396 printf " %b Latest FTL Binary already installed (%s). Confirming Checksum...\\n" "${INFO}" "${FTLlatesttag}"
2397
2398 remoteSha1=$(curl -sSL --fail "https://github.com/pi-hole/FTL/releases/download/${FTLversion%$'\r'}/${binary}.sha1" | cut -d ' ' -f 1)
2399 localSha1=$(sha1sum "$(which pihole-FTL)" | cut -d ' ' -f 1)
2400
2401 if [[ "${remoteSha1}" != "${localSha1}" ]]; then
2402 printf " %b Corruption detected...\\n" "${INFO}"
2403 return 0
2404 else
2405 printf " %b Checksum correct. No need to download!\\n" "${INFO}"
2406 return 1
2407 fi
2408 fi
2409 else
2410 return 0
2411 fi
2412 fi
2413}
2414
2415# Detect suitable FTL binary platform
2416FTLdetect() {
2417 printf "\\n %b FTL Checks...\\n\\n" "${INFO}"
2418
2419 printf " %b" "${2}"
2420
2421 if FTLcheckUpdate "${1}"; then
2422 FTLinstall "${1}" || return 1
2423 fi
2424}
2425
2426make_temporary_log() {
2427 # Create a random temporary file for the log
2428 TEMPLOG=$(mktemp /tmp/pihole_temp.XXXXXX)
2429 # Open handle 3 for templog
2430 # https://stackoverflow.com/questions/18460186/writing-outputs-to-log-file-and-console
2431 exec 3>"$TEMPLOG"
2432 # Delete templog, but allow for addressing via file handle
2433 # This lets us write to the log without having a temporary file on the drive, which
2434 # is meant to be a security measure so there is not a lingering file on the drive during the install process
2435 rm "$TEMPLOG"
2436}
2437
2438copy_to_install_log() {
2439 # Copy the contents of file descriptor 3 into the install log
2440 # Since we use color codes such as '\e[1;33m', they should be removed
2441 sed 's/[[0-9;]\{1,5\}m//g' < /proc/$$/fd/3 > "${installLogLoc}"
2442}
2443
2444main() {
2445 ######## FIRST CHECK ########
2446 # Must be root to install
2447 local str="Root user check"
2448 printf "\\n"
2449
2450 # If the user's id is zero,
2451 if [[ "${EUID}" -eq 0 ]]; then
2452 # they are root and all is good
2453 printf " %b %s\\n" "${TICK}" "${str}"
2454 # Show the Pi-hole logo so people know it's genuine since the logo and name are trademarked
2455 show_ascii_berry
2456 make_temporary_log
2457 # Otherwise,
2458 else
2459 # They do not have enough privileges, so let the user know
2460 printf " %b %s\\n" "${CROSS}" "${str}"
2461 printf " %b %bScript called with non-root privileges%b\\n" "${INFO}" "${COL_LIGHT_RED}" "${COL_NC}"
2462 printf " The Pi-hole requires elevated privileges to install and run\\n"
2463 printf " Please check the installer for any concerns regarding this requirement\\n"
2464 printf " Make sure to download this script from a trusted source\\n\\n"
2465 printf " %b Sudo utility check" "${INFO}"
2466
2467 # If the sudo command exists,
2468 if is_command sudo ; then
2469 printf "%b %b Sudo utility check\\n" "${OVER}" "${TICK}"
2470 # Download the install script and run it with admin rights
2471 exec curl -sSL https://raw.githubusercontent.com/pi-hole/pi-hole/master/automated%20install/basic-install.sh | sudo bash "$@"
2472 exit $?
2473 # Otherwise,
2474 else
2475 # Let them know they need to run it as root
2476 printf "%b %b Sudo utility check\\n" "${OVER}" "${CROSS}"
2477 printf " %b Sudo is needed for the Web Interface to run pihole commands\\n\\n" "${INFO}"
2478 printf " %b %bPlease re-run this installer as root${COL_NC}\\n" "${INFO}" "${COL_LIGHT_RED}"
2479 exit 1
2480 fi
2481 fi
2482
2483 # Check for supported distribution
2484 distro_check
2485
2486 # If the setup variable file exists,
2487 if [[ -f "${setupVars}" ]]; then
2488 # if it's running unattended,
2489 if [[ "${runUnattended}" == true ]]; then
2490 printf " %b Performing unattended setup, no whiptail dialogs will be displayed\\n" "${INFO}"
2491 # Use the setup variables
2492 useUpdateVars=true
2493 # also disable debconf-apt-progress dialogs
2494 export DEBIAN_FRONTEND="noninteractive"
2495 # Otherwise,
2496 else
2497 # show the available options (repair/reconfigure)
2498 update_dialogs
2499 fi
2500 fi
2501
2502 # Start the installer
2503 # Verify there is enough disk space for the install
2504 if [[ "${skipSpaceCheck}" == true ]]; then
2505 printf " %b Skipping free disk space verification\\n" "${INFO}"
2506 else
2507 verifyFreeDiskSpace
2508 fi
2509
2510 # Update package cache
2511 update_package_cache || exit 1
2512
2513 # Notify user of package availability
2514 notify_package_updates_available
2515
2516 # Install packages used by this installation script
2517 install_dependent_packages "${INSTALLER_DEPS[@]}"
2518
2519 # Check if SELinux is Enforcing
2520 checkSelinux
2521
2522 if [[ "${useUpdateVars}" == false ]]; then
2523 # Display welcome dialogs
2524 welcomeDialogs
2525 # Create directory for Pi-hole storage
2526 mkdir -p /etc/pihole/
2527 # Determine available interfaces
2528 get_available_interfaces
2529 # Find interfaces and let the user choose one
2530 chooseInterface
2531 # Decide what upstream DNS Servers to use
2532 setDNS
2533 # Give the user a choice of blocklists to include in their install. Or not.
2534 chooseBlocklists
2535 # Let the user decide if they want to block ads over IPv4 and/or IPv6
2536 use4andor6
2537 # Let the user decide if they want the web interface to be installed automatically
2538 setAdminFlag
2539 # Let the user decide if they want query logging enabled...
2540 setLogging
2541 # Let the user decide the FTL privacy level
2542 setPrivacyLevel
2543 else
2544 # Setup adlist file if not exists
2545 installDefaultBlocklists
2546
2547 # Source ${setupVars} to use predefined user variables in the functions
2548 source ${setupVars}
2549
2550 # Get the privacy level if it exists (default is 0)
2551 if [[ -f "${PI_HOLE_CONFIG_DIR}/pihole-FTL.conf" ]]; then
2552 PRIVACY_LEVEL=$(sed -ne 's/PRIVACYLEVEL=\(.*\)/\1/p' "${PI_HOLE_CONFIG_DIR}/pihole-FTL.conf")
2553
2554 # If no setting was found, default to 0
2555 PRIVACY_LEVEL="${PRIVACY_LEVEL:-0}"
2556 fi
2557 fi
2558 # Clone/Update the repos
2559 clone_or_update_repos
2560
2561 # Install the Core dependencies
2562 local dep_install_list=("${PIHOLE_DEPS[@]}")
2563 if [[ "${INSTALL_WEB_SERVER}" == true ]]; then
2564 # Install the Web dependencies
2565 dep_install_list+=("${PIHOLE_WEB_DEPS[@]}")
2566 fi
2567
2568 install_dependent_packages "${dep_install_list[@]}"
2569 unset dep_install_list
2570
2571 # On some systems, lighttpd is not enabled on first install. We need to enable it here if the user
2572 # has chosen to install the web interface, else the `LIGHTTPD_ENABLED` check will fail
2573 if [[ "${INSTALL_WEB_SERVER}" == true ]]; then
2574 enable_service lighttpd
2575 fi
2576 # Determine if lighttpd is correctly enabled
2577 if check_service_active "lighttpd"; then
2578 LIGHTTPD_ENABLED=true
2579 else
2580 LIGHTTPD_ENABLED=false
2581 fi
2582 # Create the pihole user
2583 create_pihole_user
2584
2585 # Check if FTL is installed - do this early on as FTL is a hard dependency for Pi-hole
2586 local funcOutput
2587 funcOutput=$(get_binary_name) #Store output of get_binary_name here
2588 local binary
2589 binary="pihole-FTL${funcOutput##*pihole-FTL}" #binary name will be the last line of the output of get_binary_name (it always begins with pihole-FTL)
2590 local theRest
2591 theRest="${funcOutput%pihole-FTL*}" # Print the rest of get_binary_name's output to display (cut out from first instance of "pihole-FTL")
2592 if ! FTLdetect "${binary}" "${theRest}"; then
2593 printf " %b FTL Engine not installed\\n" "${CROSS}"
2594 exit 1
2595 fi
2596
2597 # Install and log everything to a file
2598 installPihole | tee -a /proc/$$/fd/3
2599
2600 # Copy the temp log file into final log location for storage
2601 copy_to_install_log
2602
2603 if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
2604 # Add password to web UI if there is none
2605 pw=""
2606 # If no password is set,
2607 if [[ $(grep 'WEBPASSWORD' -c /etc/pihole/setupVars.conf) == 0 ]] ; then
2608 # generate a random password
2609 pw=$(tr -dc _A-Z-a-z-0-9 < /dev/urandom | head -c 8)
2610 # shellcheck disable=SC1091
2611 . /opt/pihole/webpage.sh
2612 echo "WEBPASSWORD=$(HashPassword ${pw})" >> ${setupVars}
2613 fi
2614 fi
2615
2616 # Check for and disable systemd-resolved-DNSStubListener before reloading resolved
2617 # DNSStubListener needs to remain in place for installer to download needed files,
2618 # so this change needs to be made after installation is complete,
2619 # but before starting or resarting the dnsmasq or ftl services
2620 disable_resolved_stublistener
2621
2622 # If the Web server was installed,
2623 if [[ "${INSTALL_WEB_SERVER}" == true ]]; then
2624
2625 if [[ "${LIGHTTPD_ENABLED}" == true ]]; then
2626 restart_service lighttpd
2627 enable_service lighttpd
2628 else
2629 printf " %b Lighttpd is disabled, skipping service restart\\n" "${INFO}"
2630 fi
2631 fi
2632
2633 printf " %b Restarting services...\\n" "${INFO}"
2634 # Start services
2635
2636 # Enable FTL
2637 # Ensure the service is enabled before trying to start it
2638 # Fixes a problem reported on Ubuntu 18.04 where trying to start
2639 # the service before enabling causes installer to exit
2640 enable_service pihole-FTL
2641 restart_service pihole-FTL
2642
2643 # Download and compile the aggregated block list
2644 runGravity
2645
2646 # Force an update of the updatechecker
2647 /opt/pihole/updatecheck.sh
2648 /opt/pihole/updatecheck.sh x remote
2649
2650 if [[ "${useUpdateVars}" == false ]]; then
2651 displayFinalMessage "${pw}"
2652 fi
2653
2654 # If the Web interface was installed,
2655 if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
2656 # If there is a password,
2657 if (( ${#pw} > 0 )) ; then
2658 # display the password
2659 printf " %b Web Interface password: %b%s%b\\n" "${INFO}" "${COL_LIGHT_GREEN}" "${pw}" "${COL_NC}"
2660 printf " %b This can be changed using 'pihole -a -p'\\n\\n" "${INFO}"
2661 fi
2662 fi
2663
2664 if [[ "${useUpdateVars}" == false ]]; then
2665 # If the Web interface was installed,
2666 if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
2667 printf " %b View the web interface at http://pi.hole/admin or http://%s/admin\\n\\n" "${INFO}" "${IPV4_ADDRESS%/*}"
2668 fi
2669 # Explain to the user how to use Pi-hole as their DNS server
2670 printf " %b You may now configure your devices to use the Pi-hole as their DNS server\\n" "${INFO}"
2671 [[ -n "${IPV4_ADDRESS%/*}" ]] && printf " %b Pi-hole DNS (IPv4): %s\\n" "${INFO}" "${IPV4_ADDRESS%/*}"
2672 [[ -n "${IPV6_ADDRESS}" ]] && printf " %b Pi-hole DNS (IPv6): %s\\n" "${INFO}" "${IPV6_ADDRESS}"
2673 printf " %b If you set a new IP address, please restart the server running the Pi-hole\\n" "${INFO}"
2674 INSTALL_TYPE="Installation"
2675 else
2676 INSTALL_TYPE="Update"
2677 fi
2678
2679 # Display where the log file is
2680 printf "\\n %b The install log is located at: %s\\n" "${INFO}" "${installLogLoc}"
2681 printf "%b%s Complete! %b\\n" "${COL_LIGHT_GREEN}" "${INSTALL_TYPE}" "${COL_NC}"
2682
2683 if [[ "${INSTALL_TYPE}" == "Update" ]]; then
2684 printf "\\n"
2685 "${PI_HOLE_BIN_DIR}"/pihole version --current
2686 fi
2687}
2688
2689if [[ "${PH_TEST}" != true ]] ; then
2690 main "$@"
2691fi