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