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