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