#!/usr/bin/env bash # Pi-hole: A black hole for Internet advertisements # (c) 2017 Pi-hole, LLC (https://pi-hole.net) # Network-wide ad blocking via your own hardware. # # Installs Pi-hole # # This file is copyright under the latest version of the EUPL. # Please see LICENSE file for your rights under this license. # pi-hole.net/donate # # Install with this command (from your Pi): # # curl -L install.pi-hole.net | bash set -e ######## VARIABLES ######### tmpLog=/tmp/pihole-install.log instalLogLoc=/etc/pihole/install.log setupVars=/etc/pihole/setupVars.conf lighttpdConfig=/etc/lighttpd/lighttpd.conf coltable=/opt/pihole/COL_TABLE webInterfaceGitUrl="https://github.com/pi-hole/AdminLTE.git" webInterfaceDir="/var/www/html/admin" piholeGitUrl="https://github.com/pi-hole/pi-hole.git" PI_HOLE_LOCAL_REPO="/etc/.pihole" PI_HOLE_FILES=(chronometer list piholeDebug piholeLogFlush setupLCD update version gravity uninstall webpage) PI_HOLE_INSTALL_DIR="/opt/pihole" useUpdateVars=false IPV4_ADDRESS="" IPV6_ADDRESS="" QUERY_LOGGING=true INSTALL_WEB=true # Find the rows and columns will default to 80x24 is it can not be detected screen_size=$(stty size 2>/dev/null || echo 24 80) rows=$(echo "${screen_size}" | awk '{print $1}') columns=$(echo "${screen_size}" | awk '{print $2}') # Divide by two so the dialogs take up half of the screen, which looks nice. r=$(( rows / 2 )) c=$(( columns / 2 )) # Unless the screen is tiny r=$(( r < 20 ? 20 : r )) c=$(( c < 70 ? 70 : c )) ######## Undocumented Flags. Shhh ######## skipSpaceCheck=false reconfigure=false runUnattended=false if [[ -f ${coltable} ]]; then source ${coltable} else COL_NC='\e[0m' # No Color COL_LIGHT_GREEN='\e[1;32m' COL_LIGHT_RED='\e[1;31m' TICK="[${COL_LIGHT_GREEN}✓${COL_NC}]" CROSS="[${COL_LIGHT_RED}✗${COL_NC}]" INFO="[i]" DONE="${COL_LIGHT_GREEN} done!${COL_NC}" OVER="\r\033[K" fi show_ascii_berry() { echo -e " ${COL_LIGHT_GREEN}.;;,. .ccccc:,. :cccclll:. ..,, :ccccclll. ;ooodc 'ccll:;ll .oooodc .;cll.;;looo:. ${COL_LIGHT_RED}.. ','. .',,,,,,'. .',,,,,,,,,,. .',,,,,,,,,,,,.... ....''',,,,,,,'....... ......... .... ......... .......... .......... .......... .......... ......... .... ......... ........,,,,,,,'...... ....',,,,,,,,,,,,. .',,,,,,,,,'. .',,,,,,'. ..'''.${COL_NC} " } # Compatibility distro_check() { if command -v apt-get &> /dev/null; then #Debian Family ############################################# PKG_MANAGER="apt-get" UPDATE_PKG_CACHE="${PKG_MANAGER} update" PKG_INSTALL=(${PKG_MANAGER} --yes --no-install-recommends install) # grep -c will return 1 retVal on 0 matches, block this throwing the set -e with an OR TRUE PKG_COUNT="${PKG_MANAGER} -s -o Debug::NoLocking=true upgrade | grep -c ^Inst || true" # ######################################### # fixes for dependency differences # Debian 7 doesn't have iproute2 use iproute if ${PKG_MANAGER} install --dry-run iproute2 > /dev/null 2>&1; then iproute_pkg="iproute2" else iproute_pkg="iproute" fi # Prefer the php metapackage if it's there, fall back on the php5 packages if ${PKG_MANAGER} install --dry-run php > /dev/null 2>&1; then phpVer="php" else phpVer="php5" fi # ######################################### INSTALLER_DEPS=(apt-utils dialog debconf dhcpcd5 git ${iproute_pkg} whiptail) PIHOLE_DEPS=(bc cron curl dnsmasq dnsutils iputils-ping lsof netcat sudo unzip wget) PIHOLE_WEB_DEPS=(lighttpd ${phpVer}-common ${phpVer}-cgi ${phpVer}-sqlite) LIGHTTPD_USER="www-data" LIGHTTPD_GROUP="www-data" LIGHTTPD_CFG="lighttpd.conf.debian" DNSMASQ_USER="dnsmasq" test_dpkg_lock() { i=0 while fuser /var/lib/dpkg/lock >/dev/null 2>&1 ; do sleep 0.5 ((i=i+1)) done # Always return success, since we only return if there is no # lock (anymore) return 0 } elif command -v rpm &> /dev/null; then # Fedora Family if command -v dnf &> /dev/null; then PKG_MANAGER="dnf" else PKG_MANAGER="yum" fi # Fedora and family update cache on every PKG_INSTALL call, no need for a separate update. UPDATE_PKG_CACHE=":" PKG_INSTALL=(${PKG_MANAGER} install -y) PKG_COUNT="${PKG_MANAGER} check-update | egrep '(.i686|.x86|.noarch|.arm|.src)' | wc -l" INSTALLER_DEPS=(dialog git iproute net-tools newt procps-ng) PIHOLE_DEPS=(bc bind-utils cronie curl dnsmasq findutils nmap-ncat sudo unzip wget) PIHOLE_WEB_DEPS=(lighttpd lighttpd-fastcgi php php-common php-cli php-pdo) if ! grep -q 'Fedora' /etc/redhat-release; then INSTALLER_DEPS=("${INSTALLER_DEPS[@]}" "epel-release"); fi LIGHTTPD_USER="lighttpd" LIGHTTPD_GROUP="lighttpd" LIGHTTPD_CFG="lighttpd.conf.fedora" DNSMASQ_USER="nobody" else echo -e " ${CROSS} OS distribution not supported" exit fi } is_repo() { # Use git to check if directory is currently under VCS, return the value 128 # if directory is not a repo. Return 1 if directory does not exist. local directory="${1}" local curdir local rc curdir="${PWD}" if [[ -d "${directory}" ]]; then # git -C is not used here to support git versions older than 1.8.4 cd "${directory}" git status --short &> /dev/null || rc=$? else # non-zero return code if directory does not exist rc=1 fi cd "${curdir}" return "${rc:-0}" } make_repo() { local directory="${1}" local remoteRepo="${2}" str="Clone ${remoteRepo} into ${directory}" echo -ne " ${INFO} ${str}..." # Clean out the directory if it exists for git to clone into if [[ -d "${directory}" ]]; then rm -rf "${directory}" fi git clone -q --depth 1 "${remoteRepo}" "${directory}" &> /dev/null || return $? echo -e "${OVER} ${TICK} ${str}" return 0 } update_repo() { local directory="${1}" local curdir local str="Update repo in ${1}" curdir="${PWD}" cd "${directory}" &> /dev/null || return 1 # Pull the latest commits echo -ne " ${INFO} ${str}..." git stash --all --quiet &> /dev/null || true # Okay for stash failure git clean --quiet --force -d || true # Okay for already clean directory git pull --quiet &> /dev/null || return $? echo -e "${OVER} ${TICK} ${str}" cd "${curdir}" &> /dev/null || return 1 return 0 } getGitFiles() { # Setup git repos for directory and repository passed # as arguments 1 and 2 local directory="${1}" local remoteRepo="${2}" local str="Check for existing repository in ${1}" echo -ne " ${INFO} ${str}..." if is_repo "${directory}"; then echo -e "${OVER} ${TICK} ${str}" update_repo "${directory}" || { echo -e "\n ${COL_LIGHT_RED}Error: Could not update local repository. Contact support.${COL_NC}"; exit 1; } else echo -e "${OVER} ${CROSS} ${str}" make_repo "${directory}" "${remoteRepo}" || { echo -e "\n ${COL_LIGHT_RED}Error: Could not update local repository. Contact support.${COL_NC}"; exit 1; } fi echo "" return 0 } resetRepo() { local directory="${1}" cd "${directory}" &> /dev/null || return 1 str="Resetting repository within ${1}..." echo -ne " ${INFO} ${str}" git reset --hard &> /dev/null || return $? echo -e "${OVER} ${TICK} ${str}" return 0 } find_IPv4_information() { local route # Find IP used to route to outside world route=$(ip route get 8.8.8.8) IPv4dev=$(awk '{for (i=1; i<=NF; i++) if ($i~/dev/) print $(i+1)}' <<< "${route}") IPv4bare=$(awk '{print $7}' <<< "${route}") IPV4_ADDRESS=$(ip -o -f inet addr show | grep "${IPv4bare}" | awk '{print $4}' | awk 'END {print}') IPv4gw=$(awk '{print $3}' <<< "${route}") } get_available_interfaces() { # Get available UP interfaces. availableInterfaces=$(ip --oneline link show up | grep -v "lo" | awk '{print $2}' | cut -d':' -f1 | cut -d'@' -f1) } welcomeDialogs() { # Display the welcome dialog whiptail --msgbox --backtitle "Welcome" --title "Pi-hole automated installer" "\n\nThis installer will transform your device into a network-wide ad blocker!" ${r} ${c} # Support for a part-time dev 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} # Explain the need for a static address 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. In the next section, you can choose to use your current network settings (DHCP) or to manually edit them." ${r} ${c} } verifyFreeDiskSpace() { # 50MB is the minimum space needed (45MB install (includes web admin bootstrap/jquery libraries etc) + 5MB one day of logs.) # - Fourdee: Local ensures the variable is only created, and accessible within this function/void. Generally considered a "good" coding practice for non-global variables. local str="Disk space check" local required_free_kilobytes=51200 local existing_free_kilobytes=$(df -Pk | grep -m1 '\/$' | awk '{print $4}') # - Unknown free disk space , not a integer if ! [[ "${existing_free_kilobytes}" =~ ^([0-9])+$ ]]; then echo -e " ${CROSS} ${str} Unknown free disk space! We were unable to determine available free disk space on this system. You may override this check, however, it is not recommended The option '${COL_LIGHT_RED}--i_do_not_follow_recommendations${COL_NC}' can override this e.g: curl -L https://install.pi-hole.net | bash /dev/stdin ${COL_LIGHT_RED}