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