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