diff --git a/.gitignore b/.gitignore index e43b0f98..c4b497a0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,7 @@ .DS_Store +*.pyc +*.swp +__pycache__ +.cache +.pullapprove.yml + diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml new file mode 100644 index 00000000..a571e63f --- /dev/null +++ b/.idea/codeStyleSettings.xml @@ -0,0 +1,22 @@ + + + + + + \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..2ca3b2d2 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +sudo: required +services: + - docker +language: python +python: + - "2.7" +install: + - pip install -r requirements.txt + +script: py.test -vv diff --git a/README.md b/README.md index b28b3f3e..496816fb 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Works on most Debian and CentOS/RHEL based distributions! ```bash wget -O basic-install.sh https://install.pi-hole.net -cat basic-install.sh | bash +bash basic-install.sh ``` If you wish to read over the script before running it, then after the [`wget`](https://linux.die.net/man/1/wget) command, run `nano basic-install.sh` to open the file in a text viewer. diff --git a/advanced/Scripts/update.sh b/advanced/Scripts/update.sh index a2220d57..10728cd8 100644 --- a/advanced/Scripts/update.sh +++ b/advanced/Scripts/update.sh @@ -3,7 +3,9 @@ # (c) 2015, 2016 by Jacob Salmela # Network-wide ad blocking via your Raspberry Pi # http://pi-hole.net -# Whitelists domains +# Check Pi-hole core and admin pages versions and determine what +# upgrade (if any) is required. Automatically updates and reinstalls +# application if update is detected. # # Pi-hole is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -12,131 +14,153 @@ # Variables -webInterfaceGitUrl="https://github.com/pi-hole/AdminLTE.git" -webInterfaceDir="/var/www/html/admin" -piholeGitUrl="https://github.com/pi-hole/pi-hole.git" -piholeFilesDir="/etc/.pihole" - -spinner() { - local pid=${1} - local delay=0.50 - local spinstr='/-\|' - while [ "$(ps a | awk '{print $1}' | grep "${pid}")" ]; do - local temp=${spinstr#?} - printf " [%c] " "${spinstr}" - local spinstr=${temp}${spinstr%"$temp"} - sleep ${delay} - printf "\b\b\b\b\b\b" - done - printf " \b\b\b\b" -} - -getGitFiles() { - # Setup git repos for directory and repository passed - # as arguments 1 and 2 - echo ":::" - echo "::: Checking for existing repository..." - if is_repo "${1}"; then - update_repo "${1}" - else - make_repo "${1}" "${2}" - fi -} +readonly ADMIN_INTERFACE_GIT_URL="https://github.com/pi-hole/AdminLTE.git" +readonly ADMIN_INTERFACE_DIR="/var/www/html/admin" +readonly PI_HOLE_GIT_URL="https://github.com/pi-hole/pi-hole.git" +readonly PI_HOLE_FILES_DIR="/etc/.pihole" is_repo() { - # Use git to check if directory is currently under VCS - echo -n "::: Checking $1 is a repo..." - cd "${1}" &> /dev/null || return 1 - git status &> /dev/null && echo " OK!"; return 0 || echo " not found!"; return 1 + # Use git to check if directory is currently under VCS, return the value + local directory="${1}" + + git -C "${directory}" status --short &> /dev/null + return +} + +prep_repo() { + # Prepare directory for local repository building + local directory="${1}" + + rm -rf "${directory}" &> /dev/null + return } make_repo() { - # Remove the non-repod interface and clone the interface - echo -n "::: Cloning $2 into $1..." - rm -rf "${1}" - git clone -q --depth 1 "${2}" "${1}" > /dev/null & spinner $! - echo " done!" + # Remove the non-repod interface and clone the interface + local remoteRepo="${2}" + local directory="${1}" + + (prep_repo "${directory}" && git clone -q --depth 1 "${remoteRepo}" "${directory}" > /dev/null) + return } update_repo() { -# Pull the latest commits - echo -n "::: Updating repo in $1..." - cd "${1}" || exit 1 - git stash -q > /dev/null & spinner $! - git pull -q > /dev/null & spinner $! - echo " done!" + local directory="${1}" + local retVal=0 + # Pull the latest commits + + # Stash all files not tracked for later retrieval + git -C "${directory}" stash --all --quiet &> /dev/null || ${retVal}=1 + # Force a clean working directory for cloning + git -C "${directory}" clean --force -d &> /dev/null || ${retVal}=1 + # Fetch latest changes and apply + git -C "${directory}" pull --quiet &> /dev/null || ${retVal}=1 + return ${retVal} } -if [ ! -d "/etc/.pihole" ]; then #This is unlikely - echo "::: Critical Error: Pi-Hole repo missing from system!" - echo "::: Please re-run install script from https://github.com/pi-hole/pi-hole" - exit 1; -fi -if [ ! -d "/var/www/html/admin" ]; then #This is unlikely - echo "::: Critical Error: Pi-Hole repo missing from system!" - echo "::: Please re-run install script from https://github.com/pi-hole/pi-hole" - exit 1; -fi +getGitFiles() { + # Setup git repos for directory and repository passed + # as arguments 1 and 2 + local directory="${1}" + local remoteRepo="${2}" + echo ":::" + echo "::: Checking for existing repository..." + if is_repo "${directory}"; then + echo -n "::: Updating repository in ${directory}..." + update_repo "${directory}" || (echo "*** Error: Could not update local repository. Contact support."; exit 1) + echo " done!" + else + echo -n "::: Cloning ${remoteRepo} into ${directory}..." + make_repo "${directory}" "${remoteRepo}" || (echo "Unable to clone repository, please contact support"; exit 1) + echo " done!" + fi +} -echo "::: Checking for updates..." -piholeVersion=$(pihole -v -p -c) -piholeVersionLatest=$(pihole -v -p -l) +main() { + local pihole_version_current + local pihole_version_latest + local web_version_current + local web_version_latest -webVersion=$(pihole -v -a -c) -webVersionLatest=$(pihole -v -a -l) + if ! is_repo "${PI_HOLE_FILES_DIR}" || ! is_repo "${ADMIN_INTERFACE_DIR}" ; then #This is unlikely + echo "::: Critical Error: One or more Pi-Hole repos are missing from system!" + echo "::: Please re-run install script from https://github.com/pi-hole/pi-hole" + exit 1; + fi -echo ":::" -echo "::: Pi-hole version is $piholeVersion (Latest version is $piholeVersionLatest)" -echo "::: Web Admin version is $webVersion (Latest version is $webVersionLatest)" -echo ":::" + echo "::: Checking for updates..." + # Checks Pi-hole version string in format vX.X.X + pihole_version_current="$(/usr/local/bin/pihole version --pihole --current)" + pihole_version_latest="$(/usr/local/bin/pihole version --pihole --latest)" + web_version_current="$(/usr/local/bin/pihole version --admin --current)" + web_version_latest="$(/usr/local/bin/pihole version --admin --latest)" -# Logic -# If latest versions are blank - we've probably hit Github rate limit (stop running `pihole -up so often!): -# Update anyway -# If Core up to date AND web up to date: -# Do nothing -# If Core up to date AND web NOT up to date: -# Pull web repo -# If Core NOT up to date AND web up to date: -# pull pihole repo, run install --unattended -- reconfigure -# if Core NOT up to date AND web NOT up to date: -# pull pihole repo run install --unattended + if [[ "${pihole_version_latest}" == "-1" || "${web_version_latest}" == "-1" ]]; then + echo "*** Unable to contact GitHub for latest version. Please try again later, contact support if this continues." + exit 1 + fi + # Logic + # If latest versions are blank - we've probably hit Github rate limit (stop running `pihole -up so often!): + # Update anyway + # If Core up to date AND web up to date: + # Do nothing + # If Core up to date AND web NOT up to date: + # Pull web repo + # If Core NOT up to date AND web up to date: + # pull pihole repo, run install --unattended -- reconfigure + # if Core NOT up to date AND web NOT up to date: + # pull pihole repo run install --unattended + if [[ "${pihole_version_current}" == "${pihole_version_latest}" ]] && [[ "${web_version_current}" == "${web_version_latest}" ]]; then + echo ":::" + echo "::: Pi-hole version is $pihole_version_current" + echo "::: Web Admin version is $web_version_current" + echo ":::" + echo "::: Everything is up to date!" + exit 0 -if [[ ${piholeVersion} == ${piholeVersionLatest} && ${webVersion} == ${webVersionLatest} ]]; then - echo "::: Everything is up to date!" - echo "" - exit 0 + elif [[ "${pihole_version_current}" == "${pihole_version_latest}" ]] && [[ "${web_version_current}" < "${web_version_latest}" ]]; then + echo ":::" + echo "::: Pi-hole Web Admin files out of date" + getGitFiles "${ADMIN_INTERFACE_DIR}" "${ADMIN_INTERFACE_GIT_URL}" -elif [[ ${piholeVersion} == ${piholeVersionLatest} && ${webVersion} != ${webVersionLatest} ]]; then - echo "::: Pi-hole Web Admin files out of date" - getGitFiles ${webInterfaceDir} ${webInterfaceGitUrl} - echo ":::" - webVersion=$(pihole -v -a -c) - echo "::: Web Admin version is now at ${webVersion}" - echo "::: If you had made any changes in '/var/www/html/admin', they have been stashed using 'git stash'" - echo "" -elif [[ ${piholeVersion} != ${piholeVersionLatest} && ${webVersion} == ${webVersionLatest} ]]; then - echo "::: Pi-hole core files out of date" - getGitFiles ${piholeFilesDir} ${piholeGitUrl} - /etc/.pihole/automated\ install/basic-install.sh --reconfigure --unattended - echo ":::" - piholeVersion=$(pihole -v -p -c) - echo "::: Pi-hole version is now at ${piholeVersion}" - echo "::: If you had made any changes in '/etc/.pihole', they have been stashed using 'git stash'" - echo "" -elif [[ ${piholeVersion} != ${piholeVersionLatest} && ${webVersion} != ${webVersionLatest} ]]; then - echo "::: Updating Everything" - getGitFiles ${piholeFilesDir} ${piholeGitUrl} - /etc/.pihole/automated\ install/basic-install.sh --unattended - webVersion=$(pihole -v -a -c) - piholeVersion=$(pihole -v -p -c) - echo ":::" - echo "::: Pi-hole version is now at ${piholeVersion}" - echo "::: If you had made any changes in '/etc/.pihole', they have been stashed using 'git stash'" - echo ":::" - echo "::: Pi-hole version is now at ${piholeVersion}" - echo "::: If you had made any changes in '/etc/.pihole', they have been stashed using 'git stash'" - echo "" -fi + web_updated=true + + elif [[ "${pihole_version_current}" < "${pihole_version_latest}" ]] && [[ "${web_version_current}" == "${web_version_latest}" ]]; then + echo "::: Pi-hole core files out of date" + getGitFiles "${PI_HOLE_FILES_DIR}" "${PI_HOLE_GIT_URL}" + /etc/.pihole/automated\ install/basic-install.sh --reconfigure --unattended || echo "Unable to complete update, contact Pi-hole" && exit 1 + core_updated=true + + elif [[ "${pihole_version_current}" < "${pihole_version_latest}" ]] && [[ "${web_version_current}" < "${web_version_latest}" ]]; then + echo "::: Updating Everything" + getGitFiles "${PI_HOLE_FILES_DIR}" "${PI_HOLE_GIT_URL}" + /etc/.pihole/automated\ install/basic-install.sh --unattended || echo "Unable to complete update, contact Pi-hole" && exit 1 + web_updated=true + core_updated=true + else + echo "*** Update script has malfunctioned, fallthrough reached. Please contact support" + exit 1 + fi + + if [[ "${web_updated}" == true ]]; then + web_version_current="$(/usr/local/bin/pihole version --admin --current)" + echo ":::" + echo "::: Web Admin version is now at ${web_version_current}" + echo "::: If you had made any changes in '/var/www/html/admin/', they have been stashed using 'git stash'" + fi + + if [[ "${core_updated}" == true ]]; then + pihole_version_current="$(/usr/local/bin/pihole version --pihole --current)" + echo ":::" + echo "::: Pi-hole version is now at ${pihole_version_current}" + echo "::: If you had made any changes in '/etc/.pihole/', they have been stashed using 'git stash'" + fi + + echo "" + exit 0 + +} + +main diff --git a/advanced/Scripts/version.sh b/advanced/Scripts/version.sh index ca78032a..fc74f8a0 100644 --- a/advanced/Scripts/version.sh +++ b/advanced/Scripts/version.sh @@ -14,6 +14,8 @@ latest=false current=false +DEFAULT="-1" + normalOutput() { piholeVersion=$(cd /etc/.pihole/ && git describe --tags --abbrev=0) webVersion=$(cd /var/www/html/admin/ && git describe --tags --abbrev=0) @@ -21,8 +23,8 @@ normalOutput() { piholeVersionLatest=$(curl -s https://api.github.com/repos/pi-hole/pi-hole/releases/latest | grep -Po '"tag_name":.*?[^\\]",' | perl -pe 's/"tag_name": "//; s/^"//; s/",$//') webVersionLatest=$(curl -s https://api.github.com/repos/pi-hole/AdminLTE/releases/latest | grep -Po '"tag_name":.*?[^\\]",' | perl -pe 's/"tag_name": "//; s/^"//; s/",$//') - echo "::: Pi-hole version is ${piholeVersion} (Latest version is ${piholeVersionLatest})" - echo "::: Web-Admin version is ${webVersion} (Latest version is ${webVersionLatest})" + echo "::: Pi-hole version is ${piholeVersion} (Latest version is ${piholeVersionLatest:-${DEFAULT}})" + echo "::: Web-Admin version is ${webVersion} (Latest version is ${webVersionLatest:-${DEFAULT}})" } webOutput() { @@ -36,14 +38,14 @@ webOutput() { if [[ "${latest}" == true && "${current}" == false ]]; then webVersionLatest=$(curl -s https://api.github.com/repos/pi-hole/AdminLTE/releases/latest | grep -Po '"tag_name":.*?[^\\]",' | perl -pe 's/"tag_name": "//; s/^"//; s/",$//') - echo ${webVersionLatest} + echo "${webVersionLatest:--1}" elif [[ "${latest}" == false && "${current}" == true ]]; then webVersion=$(cd /var/www/html/admin/ && git describe --tags --abbrev=0) - echo ${webVersion} + echo "${webVersion}" else webVersion=$(cd /var/www/html/admin/ && git describe --tags --abbrev=0) webVersionLatest=$(curl -s https://api.github.com/repos/pi-hole/AdminLTE/releases/latest | grep -Po '"tag_name":.*?[^\\]",' | perl -pe 's/"tag_name": "//; s/^"//; s/",$//') - echo "::: Web-Admin version is $webVersion (Latest version is $webVersionLatest)" + echo "::: Web-Admin version is ${webVersion} (Latest version is ${webVersionLatest:-${DEFAULT}})" fi } @@ -58,14 +60,14 @@ coreOutput() { if [[ "${latest}" == true && "${current}" == false ]]; then piholeVersionLatest=$(curl -s https://api.github.com/repos/pi-hole/pi-hole/releases/latest | grep -Po '"tag_name":.*?[^\\]",' | perl -pe 's/"tag_name": "//; s/^"//; s/",$//') - echo ${piholeVersionLatest} + echo "${piholeVersionLatest:--1}" elif [[ "${latest}" == false && "${current}" == true ]]; then piholeVersion=$(cd /etc/.pihole/ && git describe --tags --abbrev=0) - echo ${piholeVersion} + echo "${piholeVersion}" else piholeVersion=$(cd /etc/.pihole/ && git describe --tags --abbrev=0) piholeVersionLatest=$(curl -s https://api.github.com/repos/pi-hole/pi-hole/releases/latest | grep -Po '"tag_name":.*?[^\\]",' | perl -pe 's/"tag_name": "//; s/^"//; s/",$//') - echo "::: Pi-hole version is $piholeVersion (Latest version is $piholeVersionLatest)" + echo "::: Pi-hole version is ${piholeVersion} (Latest version is ${piholeVersionLatest:-${DEFAULT}})" fi } diff --git a/automated install/basic-install.sh b/automated install/basic-install.sh index 6489a5fc..2e8ae77d 100755 --- a/automated install/basic-install.sh +++ b/automated install/basic-install.sh @@ -25,15 +25,16 @@ setupVars=/etc/pihole/setupVars.conf webInterfaceGitUrl="https://github.com/pi-hole/AdminLTE.git" webInterfaceDir="/var/www/html/admin" piholeGitUrl="https://github.com/pi-hole/pi-hole.git" -piholeFilesDir="/etc/.pihole" - +PI_HOLE_LOCAL_REPO="/etc/.pihole" +PI_HOLE_FILES=(chronometer list piholeDebug piholeLogFlush setupLCD update version) useUpdateVars=false -IPv4_address="" -IPv6_address="" +IPV4_ADDRESS="" +IPV6_ADDRESS="" +QUERY_LOGGING=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) +screen_size=$(stty size 2>/dev/null || echo 24 80) rows=$(echo $screen_size | awk '{print $1}') columns=$(echo $screen_size | awk '{print $2}') @@ -53,101 +54,146 @@ runUnattended=false # Must be root to install echo ":::" if [[ ${EUID} -eq 0 ]]; then - echo "::: You are root." + echo "::: You are root." else - echo "::: Script called with non-root privileges. The Pi-hole installs server packages and configures" - echo "::: system networking, it requires elevated rights. Please check the contents of the script for" - echo "::: any concerns with this requirement. Please be sure to download this script from a trusted source." - echo ":::" - echo "::: Detecting the presence of the sudo utility for continuation of this install..." + echo "::: Script called with non-root privileges. The Pi-hole installs server packages and configures" + echo "::: system networking, it requires elevated rights. Please check the contents of the script for" + echo "::: any concerns with this requirement. Please be sure to download this script from a trusted source." + echo ":::" + echo "::: Detecting the presence of the sudo utility for continuation of this install..." - if [ -x "$(command -v sudo)" ]; then - echo "::: Utility sudo located." - exec curl -sSL https://install.pi-hole.net | sudo bash "$@" - exit $? - else - echo "::: sudo is needed for the Web interface to run pihole commands. Please run this script as root and it will be automatically installed." - exit 1 - fi + if [ -x "$(command -v sudo)" ]; then + echo "::: Utility sudo located." + exec curl -sSL https://install.pi-hole.net | sudo bash "$@" + exit $? + else + echo "::: sudo is needed for the Web interface to run pihole commands. Please run this script as root and it will be automatically installed." + exit 1 + fi fi # Compatibility -if [ -x "$(command -v apt-get)" ]; then - #Debian Family - ############################################# - PKG_MANAGER="apt-get" - PKG_CACHE="/var/lib/apt/lists/" - UPDATE_PKG_CACHE="${PKG_MANAGER} update" - PKG_UPDATE="${PKG_MANAGER} upgrade" - PKG_INSTALL="${PKG_MANAGER} --yes --fix-missing 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 dependancy differences - # Debian 7 doesn't have iproute2 use iproute - ${PKG_MANAGER} install --dry-run iproute2 > /dev/null 2>&1 && IPROUTE_PKG="iproute2" || IPROUTE_PKG="iproute" - # Ubuntu 16.04 LTS php / php5 fix - ${PKG_MANAGER} install --dry-run php5 > /dev/null 2>&1 && phpVer="php5" || phpVer="php" - # ######################################### - INSTALLER_DEPS=( apt-utils whiptail git dhcpcd5) - PIHOLE_DEPS=( dnsutils bc dnsmasq lighttpd ${phpVer}-common ${phpVer}-cgi curl unzip wget sudo netcat cron ${IPROUTE_PKG} ) - LIGHTTPD_USER="www-data" - LIGHTTPD_GROUP="www-data" - LIGHTTPD_CFG="lighttpd.conf.debian" - DNSMASQ_USER="dnsmasq" - package_check_install() { - dpkg-query -W -f='${Status}' "${1}" 2>/dev/null | grep -c "ok installed" || ${PKG_INSTALL} "${1}" - } -elif [ -x "$(command -v rpm)" ]; then - # Fedora Family - if [ -x "$(command -v dnf)" ]; then - PKG_MANAGER="dnf" - else - PKG_MANAGER="yum" - fi - PKG_CACHE="/var/cache/${PKG_MANAGER}" - UPDATE_PKG_CACHE="${PKG_MANAGER} check-update" - PKG_UPDATE="${PKG_MANAGER} update -y" - PKG_INSTALL="${PKG_MANAGER} install -y" - PKG_COUNT="${PKG_MANAGER} check-update | egrep '(.i686|.x86|.noarch|.arm|.src)' | wc -l" - INSTALLER_DEPS=( iproute net-tools procps-ng newt git ) - PIHOLE_DEPS=( epel-release bind-utils bc dnsmasq lighttpd lighttpd-fastcgi php-common php-cli php curl unzip wget findutils cronie sudo nmap-ncat ) - if grep -q 'Fedora' /etc/redhat-release; then - remove_deps=(epel-release); - PIHOLE_DEPS=( ${PIHOLE_DEPS[@]/$remove_deps} ); - fi - LIGHTTPD_USER="lighttpd" - LIGHTTPD_GROUP="lighttpd" - LIGHTTPD_CFG="lighttpd.conf.fedora" - DNSMASQ_USER="nobody" - package_check_install() { - rpm -qa | grep ^"${1}"- > /dev/null || ${PKG_INSTALL} "${1}" - } +if [[ $(command -v apt-get) ]]; then + #Debian Family + ############################################# + PKG_MANAGER="apt-get" + PKG_CACHE="/var/lib/apt/lists/" + UPDATE_PKG_CACHE="${PKG_MANAGER} update" + PKG_UPDATE="${PKG_MANAGER} upgrade" + PKG_INSTALL="${PKG_MANAGER} --yes --fix-missing 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 dependancy differences + # Debian 7 doesn't have iproute2 use iproute + ${PKG_MANAGER} install --dry-run iproute2 > /dev/null 2>&1 && IPROUTE_PKG="iproute2" || IPROUTE_PKG="iproute" + # Ubuntu 16.04 LTS php / php5 fix + ${PKG_MANAGER} install --dry-run php5 > /dev/null 2>&1 && phpVer="php5" || phpVer="php" + # ######################################### + INSTALLER_DEPS=( apt-utils whiptail git dhcpcd5) + PIHOLE_DEPS=( dnsutils bc dnsmasq lighttpd ${phpVer}-common ${phpVer}-cgi curl unzip wget sudo netcat cron ${IPROUTE_PKG} ) + LIGHTTPD_USER="www-data" + LIGHTTPD_GROUP="www-data" + LIGHTTPD_CFG="lighttpd.conf.debian" + DNSMASQ_USER="dnsmasq" + + package_check_install() { + dpkg-query -W -f='${Status}' "${1}" 2>/dev/null | grep -c "ok installed" || ${PKG_INSTALL} "${1}" + } +elif [ $(command -v rpm) ]; then + # Fedora Family + if [ $(command -v dnf) ]; then + PKG_MANAGER="dnf" + else + PKG_MANAGER="yum" + fi + PKG_CACHE="/var/cache/${PKG_MANAGER}" + UPDATE_PKG_CACHE="${PKG_MANAGER} check-update" + PKG_UPDATE="${PKG_MANAGER} update -y" + PKG_INSTALL="${PKG_MANAGER} install -y" + PKG_COUNT="${PKG_MANAGER} check-update | egrep '(.i686|.x86|.noarch|.arm|.src)' | wc -l" + INSTALLER_DEPS=( iproute net-tools procps-ng newt git ) + PIHOLE_DEPS=( epel-release bind-utils bc dnsmasq lighttpd lighttpd-fastcgi php-common php-cli php curl unzip wget findutils cronie sudo nmap-ncat ) + + if grep -q 'Fedora' /etc/redhat-release; then + remove_deps=(epel-release); + PIHOLE_DEPS=( ${PIHOLE_DEPS[@]/$remove_deps} ); + fi + LIGHTTPD_USER="lighttpd" + LIGHTTPD_GROUP="lighttpd" + LIGHTTPD_CFG="lighttpd.conf.fedora" + DNSMASQ_USER="nobody" + + package_check_install() { + rpm -qa | grep ^"${1}"- > /dev/null || ${PKG_INSTALL} "${1}" + } else - echo "OS distribution not supported" - exit + echo "OS distribution not supported" + exit fi ####### FUNCTIONS ########## spinner() { - local pid=$1 - local delay=0.50 - local spinstr='/-\|' - while [ "$(ps a | awk '{print $1}' | grep "${pid}")" ]; do - local temp=${spinstr#?} - printf " [%c] " "${spinstr}" - local spinstr=${temp}${spinstr%"$temp"} - sleep ${delay} - printf "\b\b\b\b\b\b" - done - printf " \b\b\b\b" + local pid=$1 + local delay=0.50 + local spinstr='/-\|' + + while [ "$(ps a | awk '{print $1}' | grep "${pid}")" ]; do + local temp=${spinstr#?} + printf " [%c] " "${spinstr}" + local spinstr=${temp}${spinstr%"$temp"} + sleep ${delay} + printf "\b\b\b\b\b\b" + done + printf " \b\b\b\b" +} + +is_repo() { + # Use git to check if directory is currently under VCS, return the value + local directory="${1}" + git -C "${directory}" status --short &> /dev/null + return +} + +make_repo() { + local directory="${1}" + local remoteRepo="${2}" + # Remove the non-repod interface and clone the interface + echo -n "::: Cloning $remoteRepo into $directory..." + rm -rf "${directory}" + git clone -q --depth 1 "${remoteRepo}" "${directory}" > /dev/null & spinner $! + echo " done!" +} + +update_repo() { + local directory="${1}" + # Pull the latest commits + echo -n "::: Updating repo in $1..." + cd "${directory}" || exit 1 + git stash -q > /dev/null & spinner $! + git pull -q > /dev/null & spinner $! + echo " done!" +} + +getGitFiles() { + # Setup git repos for directory and repository passed + # as arguments 1 and 2 + local directory="${1}" + local remoteRepo="${2}" + echo ":::" + echo "::: Checking for existing repository..." + if is_repo "${directory}"; then + update_repo "${directory}" + else + make_repo "${directory}" "${remoteRepo}" + fi } find_IPv4_information() { # Find IP used to route to outside world IPv4dev=$(ip route get 8.8.8.8 | awk '{for(i=1;i<=NF;i++)if($i~/dev/)print $(i+1)}') - IPv4_address=$(ip -o -f inet addr show dev "$IPv4dev" | awk '{print $4}' | awk 'END {print}') + IPV4_ADDRESS=$(ip -o -f inet addr show dev "$IPv4dev" | awk '{print $4}' | awk 'END {print}') IPv4gw=$(ip route get 8.8.8.8 | awk '{print $3}') } @@ -169,7 +215,6 @@ welcomeDialogs() { 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.) @@ -230,8 +275,8 @@ chooseInterface() { chooseInterfaceOptions=$("${chooseInterfaceCmd[@]}" "${interfacesArray[@]}" 2>&1 >/dev/tty) if [[ $? = 0 ]]; then for desiredInterface in ${chooseInterfaceOptions}; do - piholeInterface=${desiredInterface} - echo "::: Using interface: $piholeInterface" + PIHOLE_INTERFACE=${desiredInterface} + echo "::: Using interface: $PIHOLE_INTERFACE" done else echo "::: Cancel selected, exiting...." @@ -241,8 +286,8 @@ chooseInterface() { useIPv6dialog() { # Show the IPv6 address used for blocking - IPv6_address=$(ip -6 route get 2001:4860:4860::8888 | awk -F " " '{ for(i=1;i<=NF;i++) if ($i == "src") print $(i+1) }') - whiptail --msgbox --backtitle "IPv6..." --title "IPv6 Supported" "$IPv6_address will be used to block ads." ${r} ${c} + IPV6_ADDRESS=$(ip -6 route get 2001:4860:4860::8888 | awk -F " " '{ for(i=1;i<=NF;i++) if ($i == "src") print $(i+1) }') + whiptail --msgbox --backtitle "IPv6..." --title "IPv6 Supported" "$IPV6_ADDRESS will be used to block ads." ${r} ${c} } @@ -270,8 +315,8 @@ use4andor6() { if [[ ${useIPv6} ]]; then useIPv6dialog fi - echo "::: IPv4 address: ${IPv4_address}" - echo "::: IPv6 address: ${IPv6_address}" + echo "::: IPv4 address: ${IPV4_ADDRESS}" + echo "::: IPv6 address: ${IPV6_ADDRESS}" if [ ! ${useIPv4} ] && [ ! ${useIPv6} ]; then echo "::: Cannot continue, neither IPv4 or IPv6 selected" echo "::: Exiting" @@ -286,7 +331,7 @@ use4andor6() { getStaticIPv4Settings() { # Ask if the user wants to use DHCP settings as their static IP if (whiptail --backtitle "Calibrating network interface" --title "Static IP Address" --yesno "Do you want to use your current network settings as a static address? - IP address: ${IPv4_address} + IP address: ${IPV4_ADDRESS} Gateway: ${IPv4gw}" ${r} ${c}); then # If they choose yes, let the user know that the IP address will not be available via DHCP and may cause a conflict. 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. @@ -299,16 +344,16 @@ It is also possible to use a DHCP reservation, but if you are going to do that, # Start a loop to let the user enter their information with the chance to go back and edit it if necessary until [[ ${ipSettingsCorrect} = True ]]; do # Ask for the IPv4 address - 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) + 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) if [[ $? = 0 ]]; then - echo "::: Your static IPv4 address: ${IPv4_address}" + echo "::: Your static IPv4 address: ${IPV4_ADDRESS}" # Ask for the gateway 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) if [[ $? = 0 ]]; then echo "::: Your static IPv4 gateway: ${IPv4gw}" # Give the user a chance to review their settings before moving on if (whiptail --backtitle "Calibrating network interface" --title "Static IP Address" --yesno "Are these settings correct? - IP address: ${IPv4_address} + IP address: ${IPV4_ADDRESS} Gateway: ${IPv4gw}" ${r} ${c}); then # After that's done, the loop ends and we move on ipSettingsCorrect=True @@ -335,8 +380,8 @@ It is also possible to use a DHCP reservation, but if you are going to do that, setDHCPCD() { # Append these lines to dhcpcd.conf to enable a static IP - echo "## interface ${piholeInterface} - static ip_address=${IPv4_address} + echo "## interface ${PIHOLE_INTERFACE} + static ip_address=${IPV4_ADDRESS} static routers=${IPv4gw} static domain_name_servers=${IPv4gw}" | tee -a /etc/dhcpcd.conf >/dev/null } @@ -347,45 +392,45 @@ setStaticIPv4() { local CIDR if [[ -f /etc/dhcpcd.conf ]]; then # Debian Family - if grep -q "${IPv4_address}" /etc/dhcpcd.conf; then + if grep -q "${IPV4_ADDRESS}" /etc/dhcpcd.conf; then echo "::: Static IP already configured" else setDHCPCD - ip addr replace dev "${piholeInterface}" "${IPv4_address}" + ip addr replace dev "${PIHOLE_INTERFACE}" "${IPV4_ADDRESS}" echo ":::" - echo "::: Setting IP to ${IPv4_address}. You may need to restart after the install is complete." + echo "::: Setting IP to ${IPV4_ADDRESS}. You may need to restart after the install is complete." echo ":::" fi - elif [[ -f /etc/sysconfig/network-scripts/ifcfg-${piholeInterface} ]];then + elif [[ -f /etc/sysconfig/network-scripts/ifcfg-${PIHOLE_INTERFACE} ]];then # Fedora Family - IFCFG_FILE=/etc/sysconfig/network-scripts/ifcfg-${piholeInterface} - if grep -q "${IPv4_address}" "${IFCFG_FILE}"; then + IFCFG_FILE=/etc/sysconfig/network-scripts/ifcfg-${PIHOLE_INTERFACE} + if grep -q "${IPV4_ADDRESS}" "${IFCFG_FILE}"; then echo "::: Static IP already configured" else - IPADDR=$(echo "${IPv4_address}" | cut -f1 -d/) - CIDR=$(echo "${IPv4_address}" | cut -f2 -d/) + IPADDR=$(echo "${IPV4_ADDRESS}" | cut -f1 -d/) + CIDR=$(echo "${IPV4_ADDRESS}" | cut -f2 -d/) # Backup existing interface configuration: cp "${IFCFG_FILE}" "${IFCFG_FILE}".pihole.orig # Build Interface configuration file: { echo "# Configured via Pi-Hole installer" - echo "DEVICE=$piholeInterface" + echo "DEVICE=$PIHOLE_INTERFACE" echo "BOOTPROTO=none" echo "ONBOOT=yes" echo "IPADDR=$IPADDR" echo "PREFIX=$CIDR" echo "GATEWAY=$IPv4gw" - echo "DNS1=$piholeDNS1" - echo "DNS2=$piholeDNS2" + echo "DNS1=$PIHOLE_DNS_1" + echo "DNS2=$PIHOLE_DNS_2" echo "USERCTL=no" }>> "${IFCFG_FILE}" - ip addr replace dev "${piholeInterface}" "${IPv4_address}" + ip addr replace dev "${PIHOLE_INTERFACE}" "${IPV4_ADDRESS}" if [ -x "$(command -v nmcli)" ];then # Tell NetworkManager to read our new sysconfig file nmcli con load "${IFCFG_FILE}" > /dev/null fi echo ":::" - echo "::: Setting IP to ${IPv4_address}. You may need to restart after the install is complete." + echo "::: Setting IP to ${IPV4_ADDRESS}. You may need to restart after the install is complete." echo ":::" fi else @@ -423,70 +468,70 @@ setDNS() { case ${DNSchoices} in Google) echo "::: Using Google DNS servers." - piholeDNS1="8.8.8.8" - piholeDNS2="8.8.4.4" + PIHOLE_DNS_1="8.8.8.8" + PIHOLE_DNS_2="8.8.4.4" ;; OpenDNS) echo "::: Using OpenDNS servers." - piholeDNS1="208.67.222.222" - piholeDNS2="208.67.220.220" + PIHOLE_DNS_1="208.67.222.222" + PIHOLE_DNS_2="208.67.220.220" ;; Level3) echo "::: Using Level3 servers." - piholeDNS1="4.2.2.1" - piholeDNS2="4.2.2.2" + PIHOLE_DNS_1="4.2.2.1" + PIHOLE_DNS_2="4.2.2.2" ;; Norton) echo "::: Using Norton ConnectSafe servers." - piholeDNS1="199.85.126.10" - piholeDNS2="199.85.127.10" + PIHOLE_DNS_1="199.85.126.10" + PIHOLE_DNS_2="199.85.127.10" ;; Comodo) echo "::: Using Comodo Secure servers." - piholeDNS1="8.26.56.26" - piholeDNS2="8.20.247.20" + PIHOLE_DNS_1="8.26.56.26" + PIHOLE_DNS_2="8.20.247.20" ;; Custom) until [[ ${DNSSettingsCorrect} = True ]]; do strInvalid="Invalid" - if [ ! ${piholeDNS1} ]; then - if [ ! ${piholeDNS2} ]; then + if [ ! ${PIHOLE_DNS_1} ]; then + if [ ! ${PIHOLE_DNS_2} ]; then prePopulate="" else - prePopulate=", ${piholeDNS2}" + prePopulate=", ${PIHOLE_DNS_2}" fi - elif [ ${piholeDNS1} ] && [ ! ${piholeDNS2} ]; then - prePopulate="${piholeDNS1}" - elif [ ${piholeDNS1} ] && [ ${piholeDNS2} ]; then - prePopulate="${piholeDNS1}, ${piholeDNS2}" + elif [ ${PIHOLE_DNS_1} ] && [ ! ${PIHOLE_DNS_2} ]; then + prePopulate="${PIHOLE_DNS_1}" + elif [ ${PIHOLE_DNS_1} ] && [ ${PIHOLE_DNS_2} ]; then + prePopulate="${PIHOLE_DNS_1}, ${PIHOLE_DNS_2}" fi piholeDNS=$(whiptail --backtitle "Specify Upstream DNS Provider(s)" --inputbox "Enter your desired upstream DNS provider(s), seperated by a comma.\n\nFor example '8.8.8.8, 8.8.4.4'" ${r} ${c} "${prePopulate}" 3>&1 1>&2 2>&3) if [[ $? = 0 ]]; then - piholeDNS1=$(echo "${piholeDNS}" | sed 's/[, \t]\+/,/g' | awk -F, '{print$1}') - piholeDNS2=$(echo "${piholeDNS}" | sed 's/[, \t]\+/,/g' | awk -F, '{print$2}') - if ! valid_ip "${piholeDNS1}" || [ ! "${piholeDNS1}" ]; then - piholeDNS1=${strInvalid} + PIHOLE_DNS_1=$(echo "${piholeDNS}" | sed 's/[, \t]\+/,/g' | awk -F, '{print$1}') + PIHOLE_DNS_2=$(echo "${piholeDNS}" | sed 's/[, \t]\+/,/g' | awk -F, '{print$2}') + if ! valid_ip "${PIHOLE_DNS_1}" || [ ! "${PIHOLE_DNS_1}" ]; then + PIHOLE_DNS_1=${strInvalid} fi - if ! valid_ip "${piholeDNS2}" && [ "${piholeDNS2}" ]; then - piholeDNS2=${strInvalid} + if ! valid_ip "${PIHOLE_DNS_2}" && [ "${PIHOLE_DNS_2}" ]; then + PIHOLE_DNS_2=${strInvalid} fi else echo "::: Cancel selected, exiting...." exit 1 fi - if [[ ${piholeDNS1} == "${strInvalid}" ]] || [[ ${piholeDNS2} == "${strInvalid}" ]]; then - whiptail --msgbox --backtitle "Invalid IP" --title "Invalid IP" "One or both entered IP addresses were invalid. Please try again.\n\n DNS Server 1: $piholeDNS1\n DNS Server 2: ${piholeDNS2}" ${r} ${c} - if [[ ${piholeDNS1} == "${strInvalid}" ]]; then - piholeDNS1="" + if [[ ${PIHOLE_DNS_1} == "${strInvalid}" ]] || [[ ${PIHOLE_DNS_2} == "${strInvalid}" ]]; then + 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} + if [[ ${PIHOLE_DNS_1} == "${strInvalid}" ]]; then + PIHOLE_DNS_1="" fi - if [[ ${piholeDNS2} == "${strInvalid}" ]]; then - piholeDNS2="" + if [[ ${PIHOLE_DNS_2} == "${strInvalid}" ]]; then + PIHOLE_DNS_2="" fi DNSSettingsCorrect=False else - if (whiptail --backtitle "Specify Upstream DNS Provider(s)" --title "Upstream DNS Provider(s)" --yesno "Are these settings correct?\n DNS Server 1: $piholeDNS1\n DNS Server 2: ${piholeDNS2}" ${r} ${c}); then + 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 DNSSettingsCorrect=True else # If the settings are wrong, the loop continues @@ -502,6 +547,28 @@ setDNS() { fi } +setLogging() { + local LogToggleCommand + local LogChooseOptions + local LogChoices + + LogToggleCommand=(whiptail --separate-output --radiolist "Do you want to log queries?\n (Disabling will render graphs on the Admin page useless):" ${r} ${c} 6) + LogChooseOptions=("On (Reccomended)" "" on + Off "" off) + LogChoices=$("${LogToggleCommand[@]}" "${LogChooseOptions[@]}" 2>&1 >/dev/tty) || (echo "::: Cancel selected. Exiting..." && exit 1) + case ${LogChoices} in + "On (Recommended)") + echo "::: Logging On." + QUERY_LOGGING=true + ;; + Off) + echo "::: Logging Off." + QUERY_LOGGING=false + ;; + esac +} + + version_check_dnsmasq() { # Check if /etc/dnsmasq.conf is from pihole. If so replace with an original and install new in .d directory local dnsmasq_conf="/etc/dnsmasq.conf" @@ -533,14 +600,14 @@ version_check_dnsmasq() { echo -n "::: Copying 01-pihole.conf to /etc/dnsmasq.d/01-pihole.conf..." cp ${dnsmasq_pihole_01_snippet} ${dnsmasq_pihole_01_location} echo " done." - sed -i "s/@INT@/$piholeInterface/" ${dnsmasq_pihole_01_location} - if [[ "${piholeDNS1}" != "" ]]; then - sed -i "s/@DNS1@/$piholeDNS1/" ${dnsmasq_pihole_01_location} + sed -i "s/@INT@/$PIHOLE_INTERFACE/" ${dnsmasq_pihole_01_location} + if [[ "${PIHOLE_DNS_1}" != "" ]]; then + sed -i "s/@DNS1@/$PIHOLE_DNS_1/" ${dnsmasq_pihole_01_location} else sed -i '/^server=@DNS1@/d' ${dnsmasq_pihole_01_location} fi - if [[ "${piholeDNS2}" != "" ]]; then - sed -i "s/@DNS2@/$piholeDNS2/" ${dnsmasq_pihole_01_location} + if [[ "${PIHOLE_DNS_2}" != "" ]]; then + sed -i "s/@DNS2@/$PIHOLE_DNS_2/" ${dnsmasq_pihole_01_location} else sed -i '/^server=@DNS2@/d' ${dnsmasq_pihole_01_location} fi @@ -554,16 +621,16 @@ version_check_dnsmasq() { fi #Replace IPv4 and IPv6 tokens in 01-pihole.conf for pi.hole resolution. - if [[ "${IPv4_address}" != "" ]]; then - tmp=${IPv4_address%/*} + if [[ "${IPV4_ADDRESS}" != "" ]]; then + tmp=${IPV4_ADDRESS%/*} sed -i "s/@IPv4@/$tmp/" ${dnsmasq_pihole_01_location} else sed -i '/^address=\/pi.hole\/@IPv4@/d' ${dnsmasq_pihole_01_location} sed -i '/^address=\/@HOSTNAME@\/@IPv4@/d' ${dnsmasq_pihole_01_location} fi - if [[ "${IPv6_address}" != "" ]]; then - sed -i "s/@IPv6@/$IPv6_address/" ${dnsmasq_pihole_01_location} + if [[ "${IPV6_ADDRESS}" != "" ]]; then + sed -i "s/@IPv6@/$IPV6_ADDRESS/" ${dnsmasq_pihole_01_location} else sed -i '/^address=\/pi.hole\/@IPv6@/d' ${dnsmasq_pihole_01_location} sed -i '/^address=\/@HOSTNAME@\/@IPv6@/d' ${dnsmasq_pihole_01_location} @@ -576,6 +643,14 @@ version_check_dnsmasq() { fi sed -i 's/^#conf-dir=\/etc\/dnsmasq.d$/conf-dir=\/etc\/dnsmasq.d/' ${dnsmasq_conf} + + if [[ "${QUERY_LOGGING}" == false ]] ; then + #Disable Logging + sed -i 's/^log-queries/#log-queries/' ${dnsmasq_pihole_01_location} + else + #Enable Logging + sed -i 's/^#log-queries/log-queries/' ${dnsmasq_pihole_01_location} + fi } remove_legacy_scripts() { @@ -588,23 +663,41 @@ remove_legacy_scripts() { done } +clean_existing() { + # Clean an exiting installation to prepare for upgrade/reinstall + # ${1} Directory to clean; ${2} Array of files to remove + local clean_directory="${1}" + local old_files=${2} + + for script in "${old_files[@]}"; do + rm -f "${clean_directory}${script}.sh" + done + +} + installScripts() { - # Install the scripts from /etc/.pihole to their various locations + # Install the scripts from repository to their various locations + readonly install_dir="/opt/pihole/" + echo ":::" - echo -n "::: Installing scripts to /opt/pihole..." - #clear out /opt/pihole and recreate it. This allows us to remove scripts from future installs - rm -rf /opt/pihole - install -o "${USER}" -m755 -d /opt/pihole + echo -n "::: Installing scripts from ${PI_HOLE_LOCAL_REPO}..." - cd /etc/.pihole/ + # Clear out script files from Pi-hole scripts directory. + clean_existing "${install_dir}" "${PI_HOLE_FILES}" - install -o "${USER}" -Dm755 -t /opt/pihole/ gravity.sh - install -o "${USER}" -Dm755 -t /opt/pihole/ ./advanced/Scripts/*.sh - install -o "${USER}" -Dm755 -t /opt/pihole/ ./automated\ install/uninstall.sh - install -o "${USER}" -Dm755 -t /usr/local/bin/ pihole - - install -Dm644 ./advanced/bash-completion/pihole /etc/bash_completion.d/pihole - echo " done." + # Install files from local core repository + if is_repo "${PI_HOLE_LOCAL_REPO}"; then + cd "${PI_HOLE_LOCAL_REPO}" + install -o "${USER}" -Dm755 -t /opt/pihole/ gravity.sh + install -o "${USER}" -Dm755 -t /opt/pihole/ ./advanced/Scripts/*.sh + install -o "${USER}" -Dm755 -t /opt/pihole/ ./automated\ install/uninstall.sh + install -o "${USER}" -Dm755 -t /usr/local/bin/ pihole + install -Dm644 ./advanced/bash-completion/pihole /etc/bash_completion.d/pihole + echo " done." + else + echo " *** ERROR: Local repo ${core_repo} not found, exiting." + exit 1 + fi } installConfigs() { @@ -712,42 +805,6 @@ install_dependent_packages() { done } -getGitFiles() { - # Setup git repos for directory and repository passed - # as arguments 1 and 2 - echo ":::" - echo "::: Checking for existing repository..." - if is_repo "${1}"; then - update_repo "${1}" - else - make_repo "${1}" "${2}" - fi -} - -is_repo() { - # Use git to check if directory is currently under VCS - echo -n "::: Checking $1 is a repo..." - cd "${1}" &> /dev/null || return 1 - git status &> /dev/null && echo " OK!"; return 0 || echo " not found!"; return 1 -} - -make_repo() { - # Remove the non-repod interface and clone the interface - echo -n "::: Cloning $2 into $1..." - rm -rf "${1}" - git clone -q --depth 1 "${2}" "${1}" > /dev/null & spinner $! - echo " done!" -} - -update_repo() { - # Pull the latest commits - echo -n "::: Updating repo in $1..." - cd "${1}" || exit 1 - git stash -q > /dev/null & spinner $! - git pull -q > /dev/null & spinner $! - echo " done!" -} - CreateLogFile() { # Create logfiles if necessary echo ":::" @@ -833,11 +890,12 @@ finalExports() { rm ${setupVars} fi { - echo "piholeInterface=${piholeInterface}" - echo "IPv4_address=${IPv4_address}" - echo "IPv6_address=${IPv6_address}" - echo "piholeDNS1=${piholeDNS1}" - echo "piholeDNS2=${piholeDNS2}" + echo "PIHOLE_INTERFACE=${PIHOLE_INTERFACE}" + echo "IPV4_ADDRESS=${IPV4_ADDRESS}" + echo "IPV6_ADDRESS=${IPV6_ADDRESS}" + echo "PIHOLE_DNS_1=${PIHOLE_DNS_1}" + echo "PIHOLE_DNS_2=${PIHOLE_DNS_2}" + echo "QUERY_LOGGING=${QUERY_LOGGING}" }>> "${setupVars}" } @@ -866,21 +924,36 @@ installPihole() { runGravity } -updatePihole() { +accountForRefactor() { + # At some point in the future this list can be pruned, for now we'll need it to ensure updates don't break. + # Refactoring of install script has changed the name of a couple of variables. Sort them out here. sed -i 's/IPv4addr/IPv4_address/g' ${setupVars} sed -i 's/piholeIPv6/IPv6_address/g' ${setupVars} - # Source ${setupVars} for use in the rest of the functions. - . ${setupVars} - # Install base files and web interface - installScripts - installConfigs - CreateLogFile - configureSelinux - installPiholeWeb - installCron - configureFirewall - runGravity + + # Account for renaming of global variables. + sed -i 's/piholeInterface/PIHOLE_INTERFACE/g' ${setupVars} + sed -i 's/IPv4_address/IPV4_ADDRESS/g' ${setupVars} + sed -i 's/IPv6_address/IPV6_ADDRESS/g' ${setupVars} + sed -i 's/piholeDNS1/PIHOLE_DNS_1/g' ${setupVars} + sed -i 's/piholeDNS2/PIHOLE_DNS_2/g' ${setupVars} + +} + +updatePihole() { + accountForRefactor + # Source ${setupVars} for use in the rest of the functions. + . ${setupVars} + # Install base files and web interface + installScripts + installConfigs + CreateLogFile + configureSelinux + installPiholeWeb + installCron + configureFirewall + finalExports #re-export setupVars.conf to account for any new vars added in new versions + runGravity } configureSelinux() { @@ -907,13 +980,13 @@ displayFinalMessage() { # Final completion message to user whiptail --msgbox --backtitle "Make it so." --title "Installation Complete!" "Configure your devices to use the Pi-hole as their DNS server using: -IPv4: ${IPv4_address%/*} -IPv6: ${IPv6_address} +IPv4: ${IPV4_ADDRESS%/*} +IPv6: ${IPV6_ADDRESS} If you set a new IP address, you should restart the Pi. The install log is in /etc/pihole. -View the web interface at http://pi.hole/admin or http://${IPv4_address%/*}/admin" ${r} ${c} +View the web interface at http://pi.hole/admin or http://${IPV4_ADDRESS%/*}/admin" ${r} ${c} } update_dialogs() { @@ -988,14 +1061,11 @@ main() { # Install packages used by this installation script install_dependent_packages INSTALLER_DEPS[@] - # Install packages used by the Pi-hole - install_dependent_packages PIHOLE_DEPS[@] - if [[ "${reconfigure}" == true ]]; then echo "::: --reconfigure passed to install script. Not downloading/updating local repos" else # Get Git files for Core and Admin - getGitFiles ${piholeFilesDir} ${piholeGitUrl} + getGitFiles ${PI_HOLE_LOCAL_REPO} ${piholeGitUrl} getGitFiles ${webInterfaceDir} ${webInterfaceGitUrl} fi @@ -1017,9 +1087,18 @@ main() { use4andor6 # Decide what upstream DNS Servers to use setDNS + # Let the user decide if they want query logging enabled... + setLogging + + # Install packages used by the Pi-hole + install_dependent_packages PIHOLE_DEPS[@] + # Install and log everything to a file - installPihole | tee ${tmpLog} + installPihole | tee ${tmpLog} else + # update packages used by the Pi-hole + install_dependent_packages PIHOLE_DEPS[@] + updatePihole | tee ${tmpLog} fi @@ -1041,17 +1120,19 @@ main() { echo ":::" if [[ "${useUpdateVars}" == false ]]; then echo "::: Installation Complete! Configure your devices to use the Pi-hole as their DNS server using:" - echo "::: ${IPv4_address%/*}" - echo "::: ${IPv6_address}" + echo "::: ${IPV4_ADDRESS%/*}" + echo "::: ${IPV6_ADDRESS}" echo ":::" echo "::: If you set a new IP address, you should restart the Pi." + echo "::: View the web interface at http://pi.hole/admin or http://${IPV4_ADDRESS%/*}/admin" else echo "::: Update complete!" fi echo ":::" echo "::: The install log is located at: /etc/pihole/install.log" - echo "::: View the web interface at http://pi.hole/admin or http://${IPv4_address%/*}/admin" } -main "$@" +if [[ -z "$PHTEST" ]] ; then + main "$@" +fi diff --git a/autotest b/autotest new file mode 100755 index 00000000..3747cc0b --- /dev/null +++ b/autotest @@ -0,0 +1 @@ +py.test -v -f test/ diff --git a/gravity.sh b/gravity.sh index 112b42b6..15e157f6 100755 --- a/gravity.sh +++ b/gravity.sh @@ -44,7 +44,7 @@ else fi #Remove the /* from the end of the IPv4addr. -IPv4_address=${IPv4_address%/*} +IPV4_ADDRESS=${IPV4_ADDRESS%/*} # Variables for various stages of downloading and formatting the list basename=pihole @@ -242,22 +242,22 @@ gravity_hostFormat() { # Format domain list as "192.168.x.x domain.com" echo "::: Formatting domains into a HOSTS file..." # Check vars from setupVars.conf to see if we're using IPv4, IPv6, Or both. - if [[ -n "${IPv4_address}" && -n "${IPv6_address}" ]];then + if [[ -n "${IPV4_ADDRESS}" && -n "${IPV6_ADDRESS}" ]];then # Both IPv4 and IPv6 - cat ${piholeDir}/${eventHorizon} | awk -v ipv4addr="$IPv4_address" -v ipv6addr="$IPv6_address" '{sub(/\r$/,""); print ipv4addr" "$0"\n"ipv6addr" "$0}' >> ${piholeDir}/${accretionDisc} + cat ${piholeDir}/${eventHorizon} | awk -v ipv4addr="$IPV4_ADDRESS" -v ipv6addr="$IPV6_ADDRESS" '{sub(/\r$/,""); print ipv4addr" "$0"\n"ipv6addr" "$0}' >> ${piholeDir}/${accretionDisc} - elif [[ -n "${IPv4_address}" && -z "${IPv6_address}" ]];then + elif [[ -n "${IPV4_ADDRESS}" && -z "${IPV6_ADDRESS}" ]];then # Only IPv4 - cat ${piholeDir}/${eventHorizon} | awk -v ipv4addr="$IPv4_address" '{sub(/\r$/,""); print ipv4addr" "$0}' >> ${piholeDir}/${accretionDisc} + cat ${piholeDir}/${eventHorizon} | awk -v ipv4addr="$IPV4_ADDRESS" '{sub(/\r$/,""); print ipv4addr" "$0}' >> ${piholeDir}/${accretionDisc} - elif [[ -z "${IPv4_address}" && -n "${IPv6_address}" ]];then + elif [[ -z "${IPV4_ADDRESS}" && -n "${IPV6_ADDRESS}" ]];then # Only IPv6 - cat ${piholeDir}/${eventHorizon} | awk -v ipv6addr="$IPv6_address" '{sub(/\r$/,""); print ipv6addr" "$0}' >> ${piholeDir}/${accretionDisc} + cat ${piholeDir}/${eventHorizon} | awk -v ipv6addr="$IPV6_ADDRESS" '{sub(/\r$/,""); print ipv6addr" "$0}' >> ${piholeDir}/${accretionDisc} - elif [[ -z "${IPv4_address}" && -z "${IPv6_address}" ]];then + elif [[ -z "${IPV4_ADDRESS}" && -z "${IPV6_ADDRESS}" ]];then echo "::: No IP Values found! Please run 'pihole -r' and choose reconfigure to restore values" exit 1 fi diff --git a/pihole b/pihole index 4b197d12..83046675 100755 --- a/pihole +++ b/pihole @@ -10,87 +10,87 @@ # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. +PI_HOLE_SCRIPT_DIR="/opt/pihole" # Must be root to use this tool if [[ ! $EUID -eq 0 ]];then - if [ -x "$(command -v sudo)" ];then - exec sudo bash "$0" "$@" - exit $? - else - echo "::: sudo is needed to run pihole commands. Please run this script as root or install sudo." - exit 1 - fi + if [ -x "$(command -v sudo)" ];then + exec sudo bash "$0" "$@" + exit $? + else + echo "::: sudo is needed to run pihole commands. Please run this script as root or install sudo." + exit 1 + fi fi whitelistFunc() { - /opt/pihole/list.sh "$@" - exit 0 + "${PI_HOLE_SCRIPT_DIR}"/list.sh "$@" + exit 0 } blacklistFunc() { - /opt/pihole/list.sh "$@" - exit 0 + "${PI_HOLE_SCRIPT_DIR}"/list.sh "$@" + exit 0 } debugFunc() { - /opt/pihole/piholeDebug.sh - exit 0 + "${PI_HOLE_SCRIPT_DIR}"/piholeDebug.sh + exit 0 } flushFunc() { - /opt/pihole/piholeLogFlush.sh - exit 0 + "${PI_HOLE_SCRIPT_DIR}"/piholeLogFlush.sh + exit 0 } - updatePiholeFunc() { - /opt/pihole/update.sh - exit 0 + "${PI_HOLE_SCRIPT_DIR}"/update.sh + exit 0 } reconfigurePiholeFunc() { - /etc/.pihole/automated\ install/basic-install.sh --reconfigure - exit 0; + /etc/.pihole/automated\ install/basic-install.sh --reconfigure + exit 0; } updateGravityFunc() { - /opt/pihole/gravity.sh "$@" - exit 0 + "${PI_HOLE_SCRIPT_DIR}"/gravity.sh "$@" + exit 0 } setupLCDFunction() { - /opt/pihole/setupLCD.sh - exit 0 + "${PI_HOLE_SCRIPT_DIR}"/setupLCD.sh + exit 0 } queryFunc() { - domain=$2 - for list in /etc/pihole/list.*; do - count=$(grep ${domain} $list | wc -l) - echo "::: ${list} (${count} results)" - if [[ ${count} > 0 ]]; then - grep ${domain} ${list} - fi - echo "" - done - exit 0 + domain="${2}" + for list in /etc/pihole/list.*; do + count=$(grep ${domain} $list | wc -l) + echo "::: ${list} (${count} results)" + if [[ ${count} > 0 ]]; then + grep ${domain} ${list} + fi + echo "" + done + exit 0 } chronometerFunc() { - shift - /opt/pihole/chronometer.sh "$@" - exit 0 + shift + "${PI_HOLE_SCRIPT_DIR}"/chronometer.sh "$@" + exit 0 } uninstallFunc() { - /opt/pihole/uninstall.sh - exit 0 + "${PI_HOLE_SCRIPT_DIR}"/uninstall.sh + exit 0 } versionFunc() { - shift - /opt/pihole/version.sh "$@" - exit 0 + shift + "${PI_HOLE_SCRIPT_DIR}"/version.sh "$@" + exit 0 } restartDNS() { @@ -125,15 +125,36 @@ piholeEnable() { restartDNS } +piholeLogging() { + shift + + if [[ "${1}" == "off" ]] ; then + #Disable Logging + sed -i 's/^log-queries/#log-queries/' /etc/dnsmasq.d/01-pihole.conf + sed -i 's/^QUERY_LOGGING=true/QUERY_LOGGING=false/' /etc/pihole/setupVars.conf + pihole -f + echo "::: Logging has been disabled!" + elif [[ "${1}" == "on" ]] ; then + #Enable logging + sed -i 's/^#log-queries/log-queries/' /etc/dnsmasq.d/01-pihole.conf + sed -i 's/^QUERY_LOGGING=false/QUERY_LOGGING=true/' /etc/pihole/setupVars.conf + echo "::: Logging has been enabled!" + else + echo "::: Invalid option passed, please pass 'on' or 'off'" + exit 1 + fi + restartDNS +} + piholeStatus() { - if [[ $(cat /etc/dnsmasq.d/01-pihole.conf | grep "#addn-hosts=/") ]] ; then + if [[ $(grep -i "^#addn-hosts=/" /etc/dnsmasq.d/01-pihole.conf) ]] ; then #list is commented out if [[ "${1}" == "web" ]] ; then echo 0; else echo "::: Pi-hole blocking is Disabled"; fi - elif [[ $(cat /etc/dnsmasq.d/01-pihole.conf | grep "addn-hosts=/") ]] ; then + elif [[ $(grep -i "^addn-hosts=/" /etc/dnsmasq.d/01-pihole.conf) ]] ; then #list set if [[ "${1}" == "web" ]] ; then echo 1; @@ -173,37 +194,39 @@ helpFunc() { ::: -h, help Show this help dialog ::: -v, version Show current versions ::: -q, query Query the adlists for a specific domain +::: -l, logging Enable or Disable logging (pass 'on' or 'off') ::: uninstall Uninstall Pi-Hole from your system :(! ::: status Is Pi-Hole Enabled or Disabled ::: enable Enable Pi-Hole DNS Blocking ::: disable Disable Pi-Hole DNS Blocking ::: restartdns Restart dnsmasq EOM - exit 1 + exit 1 } if [[ $# = 0 ]]; then - helpFunc + helpFunc fi # Handle redirecting to specific functions based on arguments case "${1}" in - "-w" | "whitelist" ) whitelistFunc "$@";; - "-b" | "blacklist" ) blacklistFunc "$@";; - "-d" | "debug" ) debugFunc;; - "-f" | "flush" ) flushFunc;; - "-up" | "updatePihole" ) updatePiholeFunc;; - "-r" | "reconfigure" ) reconfigurePiholeFunc;; - "-g" | "updateGravity" ) updateGravityFunc "$@";; - "-s" | "setupLCD" ) setupLCDFunction;; - "-c" | "chronometer" ) chronometerFunc "$@";; - "-h" | "help" ) helpFunc;; - "-v" | "version" ) versionFunc "$@";; - "-q" | "query" ) queryFunc "$@";; - "uninstall" ) uninstallFunc;; - "enable" ) piholeEnable 1;; - "disable" ) piholeEnable 0;; - "status" ) piholeStatus "$2";; - "restartdns" ) restartDNS;; - * ) helpFunc;; + "-w" | "whitelist" ) whitelistFunc "$@";; + "-b" | "blacklist" ) blacklistFunc "$@";; + "-d" | "debug" ) debugFunc;; + "-f" | "flush" ) flushFunc;; + "-up" | "updatePihole" ) updatePiholeFunc;; + "-r" | "reconfigure" ) reconfigurePiholeFunc;; + "-g" | "updateGravity" ) updateGravityFunc "$@";; + "-s" | "setupLCD" ) setupLCDFunction;; + "-c" | "chronometer" ) chronometerFunc "$@";; + "-h" | "help" ) helpFunc;; + "-v" | "version" ) versionFunc "$@";; + "-q" | "query" ) queryFunc "$@";; + "-l" | "logging" ) piholeLogging "$@";; + "uninstall" ) uninstallFunc;; + "enable" ) piholeEnable 1;; + "disable" ) piholeEnable 0;; + "status" ) piholeStatus "$2";; + "restartdns" ) restartDNS;; + * ) helpFunc;; esac diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..53737ca5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +docker-compose +pytest +pytest-xdist +pytest-cov +testinfra diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/centos.Dockerfile b/test/centos.Dockerfile new file mode 100644 index 00000000..9af7eb4d --- /dev/null +++ b/test/centos.Dockerfile @@ -0,0 +1,14 @@ +FROM centos:7 + +ENV GITDIR /etc/.pihole +ENV SCRIPTDIR /opt/pihole + +RUN mkdir -p $GITDIR $SCRIPTDIR /etc/pihole +ADD . $GITDIR +RUN cp $GITDIR/advanced/Scripts/*.sh $GITDIR/gravity.sh $GITDIR/pihole $GITDIR/automated\ install/*.sh $SCRIPTDIR/ +ENV PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$SCRIPTDIR + +RUN true && \ + chmod +x $SCRIPTDIR/* + +#sed '/# Start the installer/Q' /opt/pihole/basic-install.sh > /opt/pihole/stub_basic-install.sh && \ diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 00000000..407d00dc --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,46 @@ +import pytest +import testinfra + +check_output = testinfra.get_backend( + "local://" +).get_module("Command").check_output + +@pytest.fixture +def Pihole(Docker): + ''' used to contain some script stubbing, now pretty much an alias ''' + return Docker + +@pytest.fixture +def Docker(request, args, image, cmd): + ''' combine our fixtures into a docker run command and setup finalizer to cleanup ''' + assert 'docker' in check_output('id'), "Are you in the docker group?" + docker_run = "docker run {} {} {}".format(args, image, cmd) + docker_id = check_output(docker_run) + + def teardown(): + check_output("docker rm -f %s", docker_id) + request.addfinalizer(teardown) + + docker_container = testinfra.get_backend("docker://" + docker_id) + docker_container.id = docker_id + return docker_container + +@pytest.fixture +def args(request): + ''' -t became required when tput began being used ''' + return '-t -d' + +@pytest.fixture(params=['debian', 'centos']) +def tag(request): + ''' consumed by image to make the test matrix ''' + return request.param + +@pytest.fixture() +def image(request, tag): + ''' built by test_000_build_containers.py ''' + return 'pytest_pihole:{}'.format(tag) + +@pytest.fixture() +def cmd(request): + ''' default to doing nothing by tailing null, but don't exit ''' + return 'tail -f /dev/null' diff --git a/test/debian.Dockerfile b/test/debian.Dockerfile new file mode 100644 index 00000000..b80d6155 --- /dev/null +++ b/test/debian.Dockerfile @@ -0,0 +1,15 @@ +FROM debian:jessie + +ENV GITDIR /etc/.pihole +ENV SCRIPTDIR /opt/pihole + +RUN mkdir -p $GITDIR $SCRIPTDIR /etc/pihole +ADD . $GITDIR +RUN cp $GITDIR/advanced/Scripts/*.sh $GITDIR/gravity.sh $GITDIR/pihole $GITDIR/automated\ install/*.sh $SCRIPTDIR/ +ENV PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$SCRIPTDIR + + +RUN true && \ + chmod +x $SCRIPTDIR/* + +#sed '/# Start the installer/Q' /opt/pihole/basic-install.sh > /opt/pihole/stub_basic-install.sh && \ diff --git a/test/shellcheck_failing_output.txt b/test/shellcheck_failing_output.txt new file mode 100644 index 00000000..c741a8e9 --- /dev/null +++ b/test/shellcheck_failing_output.txt @@ -0,0 +1,76 @@ +============================= test session starts ============================== +platform linux2 -- Python 2.7.6, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- /usr/bin/python +cachedir: .cache +rootdir: /home/a/opensource/pi-hole, inifile: +plugins: cov-2.3.0, bdd-2.17.0, xdist-1.14, testinfra-1.4.0 +collecting ... collected 7 items + +test/test_000_build_containers.py::test_build_pihole_image[test/debian.Dockerfile-pytest_pihole:debian] PASSED +test/test_000_build_containers.py::test_build_pihole_image[test/centos.Dockerfile-pytest_pihole:centos] PASSED +test/test_automated_install.py::test_setupVars_are_sourced_to_global_scope[debian] PASSED +test/test_automated_install.py::test_setupVars_are_sourced_to_global_scope[centos] PASSED +test/test_automated_install.py::test_setupVars_saved_to_file[debian] PASSED +test/test_automated_install.py::test_setupVars_saved_to_file[centos] PASSED +test/test_shellcheck.py::test_scripts_pass_shellcheck FAILED + +=================================== FAILURES =================================== +_________________________ test_scripts_pass_shellcheck _________________________ + + def test_scripts_pass_shellcheck(): + ''' Make sure shellcheck does not find anything wrong with our shell scripts ''' + shellcheck = "find . -name 'update.sh' | while read file; do shellcheck \"$file\"; done;" + results = run_local(shellcheck) + print results.stdout +> assert '' == results.stdout +E assert '' == '\nIn ./advanced/Scripts/upda...vent glob interpretation.\n\n' +E + +E + In ./advanced/Scripts/update.sh line 24: +E + while [ "$(ps a | awk '{print $1}' | grep "${pid}")" ]; do +E + ^-- SC2143: Instead of [ -n $(foo | grep bar) ], use foo | grep -q bar . +E + +E + +E + In ./advanced/Scripts/update.sh line 57: +E + git clone -q --depth 1 "${2}" "${1}" > /dev/null & spinner $! +E + ^-- SC2086: Double quote to prevent globbing and word splitting. +E Detailed information truncated (27 more lines), use "-vv" to show + +test/test_shellcheck.py:13: AssertionError +----------------------------- Captured stdout call ----------------------------- + +In ./advanced/Scripts/update.sh line 24: + while [ "$(ps a | awk '{print $1}' | grep "${pid}")" ]; do + ^-- SC2143: Instead of [ -n $(foo | grep bar) ], use foo | grep -q bar . + + +In ./advanced/Scripts/update.sh line 57: + git clone -q --depth 1 "${2}" "${1}" > /dev/null & spinner $! + ^-- SC2086: Double quote to prevent globbing and word splitting. + + +In ./advanced/Scripts/update.sh line 65: + git stash -q > /dev/null & spinner $! + ^-- SC2086: Double quote to prevent globbing and word splitting. + + +In ./advanced/Scripts/update.sh line 66: + git pull -q > /dev/null & spinner $! + ^-- SC2086: Double quote to prevent globbing and word splitting. + + +In ./advanced/Scripts/update.sh line 107: +if [[ ${piholeVersion} == ${piholeVersionLatest} && ${webVersion} == ${webVersionLatest} ]]; then + ^-- SC2053: Quote the rhs of = in [[ ]] to prevent glob interpretation. + ^-- SC2053: Quote the rhs of = in [[ ]] to prevent glob interpretation. + + +In ./advanced/Scripts/update.sh line 112: +elif [[ ${piholeVersion} == ${piholeVersionLatest} && ${webVersion} != ${webVersionLatest} ]]; then + ^-- SC2053: Quote the rhs of = in [[ ]] to prevent glob interpretation. + + +In ./advanced/Scripts/update.sh line 120: +elif [[ ${piholeVersion} != ${piholeVersionLatest} && ${webVersion} == ${webVersionLatest} ]]; then + ^-- SC2053: Quote the rhs of = in [[ ]] to prevent glob interpretation. + + +===================== 1 failed, 6 passed in 24.01 seconds ====================== diff --git a/test/test_000_build_containers.py b/test/test_000_build_containers.py new file mode 100644 index 00000000..c617f3ae --- /dev/null +++ b/test/test_000_build_containers.py @@ -0,0 +1,18 @@ +''' This file starts with 000 to make it run first ''' +import pytest +import testinfra + +run_local = testinfra.get_backend( + "local://" +).get_module("Command").run + +@pytest.mark.parametrize("image,tag", [ + ( 'test/debian.Dockerfile', 'pytest_pihole:debian' ), + ( 'test/centos.Dockerfile', 'pytest_pihole:centos' ), +]) +def test_build_pihole_image(image, tag): + build_cmd = run_local('docker build -f {} -t {} .'.format(image, tag)) + if build_cmd.rc != 0: + print build_cmd.stdout + print build_cmd.stderr + assert build_cmd.rc == 0 diff --git a/test/test_automated_install.py b/test/test_automated_install.py new file mode 100644 index 00000000..458536eb --- /dev/null +++ b/test/test_automated_install.py @@ -0,0 +1,77 @@ +import pytest +from textwrap import dedent + +SETUPVARS = { + 'PIHOLE_INTERFACE' : 'eth99', + 'IPV4_ADDRESS' : '1.1.1.1', + 'IPV6_ADDRESS' : 'FE80::240:D0FF:FE48:4672', + 'PIHOLE_DNS_1' : '4.2.2.1', + 'PIHOLE_DNS_2' : '4.2.2.2' +} + +def test_setupVars_are_sourced_to_global_scope(Pihole): + ''' currently update_dialogs sources setupVars with a dot, + then various other functions use the variables ''' + setup_var_file = 'cat < /etc/pihole/setupVars.conf\n' + for k,v in SETUPVARS.iteritems(): + setup_var_file += "{}={}\n".format(k, v) + setup_var_file += "EOF\n" + Pihole.run(setup_var_file) + + script = dedent('''\ + #!/bin/bash -e + printSetupVars() { + # Currently debug test function only + echo "Outputting sourced variables" + echo "PIHOLE_INTERFACE=\${PIHOLE_INTERFACE}" + echo "IPV4_ADDRESS=\${IPV4_ADDRESS}" + echo "IPV6_ADDRESS=\${IPV6_ADDRESS}" + echo "PIHOLE_DNS_1=\${PIHOLE_DNS_1}" + echo "PIHOLE_DNS_2=\${PIHOLE_DNS_2}" + } + update_dialogs() { + . /etc/pihole/setupVars.conf + } + update_dialogs + printSetupVars + ''') + + output = run_script(Pihole, script).stdout + + for k,v in SETUPVARS.iteritems(): + assert "{}={}".format(k, v) in output + +def test_setupVars_saved_to_file(Pihole): + ''' confirm saved settings are written to a file for future updates to re-use ''' + set_setup_vars = '\n' # dedent works better with this and padding matching script below + for k,v in SETUPVARS.iteritems(): + set_setup_vars += " {}={}\n".format(k, v) + Pihole.run(set_setup_vars).stdout + + script = dedent('''\ + #!/bin/bash -e + echo start + TERM=xterm + PHTEST=TRUE + source /opt/pihole/basic-install.sh + {} + finalExports + cat /etc/pihole/setupVars.conf + '''.format(set_setup_vars)) + + output = run_script(Pihole, script).stdout + + for k,v in SETUPVARS.iteritems(): + assert "{}={}".format(k, v) in output + +def run_script(Pihole, script, file="/test.sh"): + _write_test_script(Pihole, script, file=file) + result = Pihole.run(file) + assert result.rc == 0 + return result + +def _write_test_script(Pihole, script, file): + ''' Running the test script blocks directly can behave differently with regard to global vars ''' + ''' this is a cheap work around to that until all functions no longer rely on global variables ''' + Pihole.run('cat < {file}\n{script}\nEOF'.format(file=file, script=script)) + Pihole.run('chmod +x {}'.format(file)) diff --git a/test/test_shellcheck.py b/test/test_shellcheck.py new file mode 100644 index 00000000..43b27164 --- /dev/null +++ b/test/test_shellcheck.py @@ -0,0 +1,13 @@ +import pytest +import testinfra + +run_local = testinfra.get_backend( + "local://" +).get_module("Command").run + +def test_scripts_pass_shellcheck(): + ''' Make sure shellcheck does not find anything wrong with our shell scripts ''' + shellcheck = "find . -name 'update.sh' | while read file; do shellcheck \"$file\"; done;" + results = run_local(shellcheck) + print results.stdout + assert '' == results.stdout