Advertisement
Guest User

Untitled

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