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