diff --git a/.codeclimate.yml b/.codeclimate.yml deleted file mode 100644 index e1cb3b50..00000000 --- a/.codeclimate.yml +++ /dev/null @@ -1,3 +0,0 @@ -engines: - shellcheck: - enabled: true diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 53f5b429..07fc4352 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -20,7 +20,7 @@ --- **[FEATURE REQUEST | QUESTION | OTHER]:** -_{replace this section with your content or delete if not a FEATURE REQUEST/QUESTION/OTHER}_ +Please [submit your feature request here](https://discourse.pi-hole.net/c/feature-requests), so it is votable by the community. It's also easier for us to track. **[BUG | ISSUE] Expected Behaviour:** @@ -35,8 +35,8 @@ _{replace this section with your content or delete if not a FEATURE REQUEST/QUES - - -**(Optional) Debug Log generated by `pihole -d`:** +**(Optional) Debug token generated by `pihole -d`:** -`http://termbin.com/` +`` _This template was created based on the work of [`udemy-dl`](https://github.com/nishad/udemy-dl/blob/master/LICENSE)._ diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7291be41..9d6310d0 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,6 @@ -**By submitting this pull request, I confirm the following (please check boxes, eg [X])Failure to fill the template will close your PR:** +**By submitting this pull request, I confirm the following (please check boxes, eg [X]) _Failure to fill the template will close your PR_:** + +***Please submit all pull requests against the `development` branch. Failure to do so will delay or deny your request*** - [] I have read and understood the [contributors guide](https://github.com/pi-hole/pi-hole/blob/master/CONTRIBUTING.md). - [] I have checked that [another pull request](https://github.com/pi-hole/pi-hole/pulls) for this purpose does not exist. 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..8de25c7c --- /dev/null +++ b/.idea/codeStyleSettings.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/.pullapprove.yml b/.pullapprove.yml index 68b9acfc..f9d10062 100644 --- a/.pullapprove.yml +++ b/.pullapprove.yml @@ -25,7 +25,7 @@ groups: - development required: 2 teams: - - gravity + - approvers master: approve_by_comment: @@ -33,6 +33,6 @@ groups: conditions: branches: - master - required: 3 + required: -1 teams: - - gravity + - admin 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/CONTRIBUTING.md b/CONTRIBUTING.md index 539f55d5..d342a8b5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,5 +33,6 @@ When requesting or submitting new features, first consider whether it might be u - Submit Pull Requests to the **development branch only**. - Before Submitting your Pull Request, merge `development` with your new branch and fix any conflicts. (Make sure you don't break anything in development!) +- Please use the [Google Style Guide for Shell](https://google.github.io/styleguide/shell.xml) for your code submission styles. - Commit Unix line endings. - (Optional fun) keep to the theme of Star Trek/black holes/gravity. diff --git a/README.md b/README.md index 7257ba4e..c982354c 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,80 @@ -[![Bountysource](https://www.bountysource.com/badge/tracker?tracker_id=3011939)](https://www.bountysource.com/trackers/3011939-pi-hole-pi-hole?utm_source=3011939&utm_medium=shield&utm_campaign=TRACKER_BADGE) [![Code Climate](https://codeclimate.com/github/pi-hole/pi-hole/badges/gpa.svg)](https://codeclimate.com/github/pi-hole/pi-hole) +

+ + + +

-# Automated Install +

+ +

-Designed For Raspberry Pi A+, B, B+, 2, 3B, and Zero (with an Ethernet/Wi-Fi adapter) -Works on most Debian and CentOS/RHEL based distributions! +## The multi-platform, network-wide ad blocker -1. Install Raspbian -2. Run the command below (downloads [this script](https://github.com/pi-hole/pi-hole/blob/master/automated%20install/basic-install.sh) in case you want to read over it first!) +Block ads for **all** your devices _without_ the need to install client-side software. The Pi-hole blocks ads the DNS-level, so all your devices are protected. -## `curl -L https://install.pi-hole.net | bash` +- Web Browsers +- Cell Phones +- Smart TV's +- Internet-connected home automation +- Anything that communicates with the Internet -### Alternative Semi-Automated install +

+ +

+ +## Your Support Still Matters + +Digital Ocean helps with our infrastructure, but our developers are all volunteers so *your donations help keep us innovating*. Sending a donation using our links below helps us offset a portion of our monthly costs. + +- ![Paypal](https://assets.pi-hole.net/static/paypal.png) [Donate via PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=3J2L3Z4DHW9UY) +- ![Bitcoin](https://assets.pi-hole.net/static/Bitcoin.png) Bitcoin Address: 1GKnevUnVaQM2pQieMyeHkpr8DXfkpfAtL + +### One-Step Automated Install +1. Install a [supported operating system](https://discourse.pi-hole.net/t/hardware-software-requirements/273/1) +2. Run the command below (it downloads [this script](https://github.com/pi-hole/pi-hole/blob/master/automated%20install/basic-install.sh) in case you want to read over it first!) + +### `curl -sSL https://install.pi-hole.net | bash` + +#### Alternative Semi-Automated Install Methods +_If you wish to read over the script before running it, run `nano basic-install.sh` to open the file in a text viewer._ + +##### Clone our repository and run the automated installer from your device. + +``` +git clone --depth 1 https://github.com/pi-hole/pi-hole.git Pi-hole +cd Pi-hole/automated_installer/ +bash basic-install.sh +``` + +##### Or ```bash wget -O basic-install.sh https://install.pi-hole.net -chmod +x basic-install.sh -./basic-install.sh +bash basic-install.sh ``` -If you wish to read over the script before running it, then after the `wget` command, do `nano basic-install.sh` to open a text viewer - Once installed, [configure your router to have **DHCP clients use the Pi as their DNS server**](http://pi-hole.net/faq/can-i-set-the-pi-hole-to-be-the-dns-server-at-my-router-so-i-dont-have-to-change-settings-for-my-devices/) and then any device that connects to your network will have ads blocked without any further configuration. Alternatively, you can manually set each device to [use the Raspberry Pi as its DNS server](http://pi-hole.net/faq/how-do-i-use-the-pi-hole-as-my-dns-server/). -## How To Install Pi-hole +## Installing the Pi-hole (Click to Watch!) +

+ +

-[![60-second install tutorial](http://i.imgur.com/5TEc3a6.png)](https://www.youtube.com/watch?v=TzFLJqUeirA) - -## How It Works +## Would you like to know more? **Watch the 60-second video below to get a quick overview** - -[![Pi-hole exlplained](http://i.imgur.com/pG1m937.png)](https://youtu.be/9Eti3xibiho) - -## Pi-hole Is Free, But Powered By Your Donations - -Send a one-time donation or sign up for Optimal.com's service using our link below to provide us with a small portion of the monthly fee. - -- ![Paypal](http://i.imgur.com/3muNfxu.png) : [Donate](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=3J2L3Z4DHW9UY) -- ![Flattr](http://i.imgur.com/ZFceFRu.png) : [Donate](https://flattr.com/submit/auto?user_id=jacobsalmela&url=https://github.com/pi-hole/pi-hole) -- ![Bitcoin](http://i.imgur.com/FIlmOMG.png) : 1GKnevUnVaQM2pQieMyeHkpr8DXfkpfAtL +

+ +

## Get Help Or Connect With Us On The Web -- [@The_Pi_Hole](https://twitter.com/The_Pi_Hole) -- [/r/pihole](https://www.reddit.com/r/pihole/) -- [Pi-hole YouTube channel](https://www.youtube.com/channel/UCT5kq9w0wSjogzJb81C9U0w) -- [Wiki](https://github.com/pi-hole/pi-hole/wiki/Customization) -- [FAQs](https://pi-hole.net/help/) +- [Users Forum](https://discourse.pi-hole.net/) +- [FAQs](https://discourse.pi-hole.net/c/faqs) +- [Wiki](https://github.com/pi-hole/pi-hole/wiki) +- ![Twitter](https://assets.pi-hole.net/static/twitter.png) [Tweet @The_Pi_Hole](https://twitter.com/The_Pi_Hole) +- ![Reddit](https://assets.pi-hole.net/static/reddit.png) [Reddit /r/pihole](https://www.reddit.com/r/pihole/) +- ![YouTube](https://assets.pi-hole.net/static/youtube.png) [Pi-hole channel](https://www.youtube.com/channel/UCT5kq9w0wSjogzJb81C9U0w) - [![Join the chat at https://gitter.im/pi-hole/pi-hole](https://badges.gitter.im/pi-hole/pi-hole.svg)](https://gitter.im/pi-hole/pi-hole?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## Technical Details @@ -55,25 +83,34 @@ The Pi-hole is an **advertising-aware DNS/Web server**. If an ad domain is queri ### Gravity -The [gravity.sh](https://github.com/pi-hole/pi-hole/blob/master/gravity.sh) does most of the magic. The script pulls in ad domains from many sources and compiles them into a single list of [over 1.6 million entries](http://jacobsalmela.com/block-millions-ads-network-wide-with-a-raspberry-pi-hole-2-0) (if you decide to use the [mahakala list](https://github.com/pi-hole/pi-hole/commit/963eacfe0537a7abddf30441c754c67ca1e40965)). +The [gravity.sh](https://github.com/pi-hole/pi-hole/blob/master/gravity.sh) does most of the magic. The script pulls in ad domains from many sources and compiles them into a single list of [over 1.6 million entries](http://jacobsalmela.com/block-millions-ads-network-wide-with-a-raspberry-pi-hole-2-0) (if you decide to use the [mahakala list](https://github.com/pi-hole/pi-hole/commit/963eacfe0537a7abddf30441c754c67ca1e40965)). This script is controlled by the `pihole` command. Please run `pihole -h` to see what commands can be run via `pihole`. -## Web Interface -The [Web interface](https://github.com/jacobsalmela/AdminLTE#pi-hole-admin-dashboard) will be installed automatically so you can view stats and change settings. You can find it at: + +#### Other Operating Systems + +The automated install is only for a clean install of a Debian family or Fedora based system, such as the Raspberry Pi. However, this script will work for most UNIX-like systems, some with some slight **modifications** that we can help you work through. If you can install `dnsmasq` and a Webserver, it should work OK. If there are other platforms you'd like supported, let us know. + +### Web Interface + +The [Web interface](https://github.com/pi-hole/AdminLTE#pi-hole-admin-dashboard) will be installed automatically so you can view stats and change settings. You can find it at: `http://192.168.1.x/admin/index.php` or `http://pi.hole/admin` -![Pi-hole Advanced Stats Dashboard](http://i.imgur.com/gTq2GbS.png) +![Pi-hole Advanced Stats Dashboard](https://assets.pi-hole.net/static/dashboard.png) ### Whitelist and blacklist -Domains can be whitelisted and blacklisted using two pre-installed scripts. See [the wiki page](https://github.com/pi-hole/pi-hole/wiki/Whitelisting-and-Blacklisting) for more details ![Whitelist editor in the Web interface](http://i.imgur.com/ogu2ewg.png) +Domains can be whitelisted and blacklisted using either the web interface or the command line. See [the wiki page](https://github.com/pi-hole/pi-hole/wiki/Whitelisting-and-Blacklisting) for more details +

+ +

## API A basic read-only API can be accessed at `/admin/api.php`. It returns the following JSON: -```json +``` json { "domains_being_blocked": "136708", "dns_queries_today": "18108", @@ -103,6 +140,8 @@ You can view [real-time stats](http://pi-hole.net/faq/install-the-real-time-lcd- - [Pi-hole on/off button](http://thetimmy.silvernight.org/pages/endisbutton/) - [Minibian Pi-hole](http://munkjensen.net/wiki/index.php/See_my_Pi-Hole#Minibian_Pi-hole) - [Windows Tray Stat Application](https://github.com/goldbattle/copernicus) +- [Let your blink1 device blink when Pi-hole filters ads](https://gist.github.com/elpatron68/ec0b4c582e5abf604885ac1e068d233f) +- [Pi-Hole Prometheus exporter](https://github.com/nlamirault/pihole_exporter) : a [Prometheus](https://prometheus.io/) exporter for Pi-Hole ## Coverage @@ -119,7 +158,4 @@ You can view [real-time stats](http://pi-hole.net/faq/install-the-real-time-lcd- - [Foolish Tech Show](https://youtu.be/bYyena0I9yc?t=2m4s) - [Pi-hole on Ubuntu](http://www.boyter.org/2015/12/pi-hole-ubuntu-14-04/) - [Catchpoint: iOS 9 Ad Blocking](http://blog.catchpoint.com/2015/09/14/ad-blocking-apple/) - -## Other Operating Systems - -This script will work for other UNIX-like systems with some slight **modifications**. As long as you can install `dnsmasq` and a Webserver, it should work OK. The automated install is only for a clean install of a Debian based system, such as the Raspberry Pi. +- [Build an Ad-Blocker for less than 10$ with Orange-Pi](http://www.devacron.com/orangepi-zero-as-an-ad-block-server-with-pi-hole/) diff --git a/adlists.default b/adlists.default index 20b48008..92422db7 100644 --- a/adlists.default +++ b/adlists.default @@ -1,4 +1,4 @@ -## Pi-hole ad-list default sources. Updated 22/05/2016 ######################### +## Pi-hole ad-list default sources. Updated 29/10/2016 ######################### # # # To make changes to this file: # # 1. run `cp /etc/pihole/adlists.default /etc/pihole/adlists.list` # @@ -28,7 +28,7 @@ https://hosts-file.net/ad_servers.txt #http://adblock.mahakala.is/ # ADZHOSTS list. Has been known to block legitimate domains -#http://optimate.dl.sourceforge.net/project/adzhosts/HOSTS.txt +#http://pilotfiber.dl.sourceforge.net/project/adzhosts/HOSTS.txt # Windows 10 telemetry list #https://raw.githubusercontent.com/crazy-max/WindowsSpyBlocker/master/data/hosts/win10/spy.txt diff --git a/advanced/01-pihole.conf b/advanced/01-pihole.conf index f4707ef4..0ddf1caa 100644 --- a/advanced/01-pihole.conf +++ b/advanced/01-pihole.conf @@ -20,13 +20,8 @@ # OR IN /etc/dnsmasq.conf # ############################################################################### -address=/pi.hole/@IPv4@ -address=/pi.hole/@IPv6@ - -address=/@HOSTNAME@/@IPv4@ -address=/@HOSTNAME@/@IPv6@ - addn-hosts=/etc/pihole/gravity.list +addn-hosts=/etc/pihole/local.list domain-needed diff --git a/advanced/Scripts/chronometer.sh b/advanced/Scripts/chronometer.sh index 2c305d53..c4b3d865 100755 --- a/advanced/Scripts/chronometer.sh +++ b/advanced/Scripts/chronometer.sh @@ -17,15 +17,15 @@ gravity="/etc/pihole/gravity.list" today=$(date "+%b %e") +. /etc/pihole/setupVars.conf + CalcBlockedDomains() { - CheckIPv6 if [ -e "${gravity}" ]; then - #Are we IPV6 or IPV4? - if [[ -n ${piholeIPv6} ]]; then - #We are IPV6 + # if BOTH IPV4 and IPV6 are in use, then we need to divide total domains by 2. + if [[ -n "${IPV4_ADDRESS}" && -n "${IPV6_ADDRESS}" ]]; then blockedDomainsTotal=$(wc -l /etc/pihole/gravity.list | awk '{print $1/2}') else - #We are IPV4 + # only one is set. blockedDomainsTotal=$(wc -l /etc/pihole/gravity.list | awk '{print $1}') fi else @@ -61,14 +61,6 @@ CalcPercentBlockedToday() { fi } -CheckIPv6() { - piholeIPv6file="/etc/pihole/.useIPv6" - if [[ -f ${piholeIPv6file} ]];then - # If the file exists, then the user previously chose to use IPv6 in the automated installer - piholeIPv6=$(ip -6 route get 2001:4860:4860::8888 | awk -F " " '{ for(i=1;i<=NF;i++) if ($i == "src") print $(i+1) }') - fi -} - outputJSON() { CalcQueriesToday CalcblockedToday @@ -88,7 +80,7 @@ normalChrono() { echo "| _/ |___| ' \/ _ \ / -_)" echo "|_| |_| |_||_\___/_\___|" echo "" - echo " $(ifconfig eth0 | awk '/inet addr/ {print $2}' | cut -d':' -f2)" + echo " ${IPV4_ADDRESS}" echo "" uptime | cut -d' ' -f11- #uptime -p #Doesn't work on all versions of uptime @@ -111,8 +103,6 @@ normalChrono() { CalcBlockedDomains echo "Blocking: ${blockedDomainsTotal}" - #below commented line does not add up to todaysQueryCount - #echo "Queries: $todaysQueryCountV4 / $todaysQueryCountV6" echo "Queries: ${queriesToday}" #same total calculation as dashboard echo "Pi-holed: ${blockedToday} (${percentBlockedToday}%)" @@ -131,7 +121,7 @@ displayHelp() { ::: -j, --json output stats as JSON formatted string ::: -h, --help display this help text EOM - exit 1 + exit 0 } if [[ $# = 0 ]]; then diff --git a/advanced/Scripts/list.sh b/advanced/Scripts/list.sh old mode 100644 new mode 100755 index 24f6d055..bb3a8a43 --- a/advanced/Scripts/list.sh +++ b/advanced/Scripts/list.sh @@ -47,12 +47,21 @@ helpFunc() { ::: -h, --help Show this help dialog ::: -l, --list Display your ${word}listed domains EOM - exit 1 + exit 0 +} + +EscapeRegexp() { + # This way we may safely insert an arbitrary + # string in our regular expressions + echo $* | sed "s/[]\\.|$(){}?+*^]/\\\\&/g" | sed "s/\\//\\\\\//g" } HandleOther(){ - #check validity of domain - validDomain=$(echo "$1" | perl -ne'print if /\b((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b/') + # First, convert everything to lowercase + domain=$(sed -e "y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/" <<< "$1") + + #check validity of domain + validDomain=$(perl -ne "print if /\b((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b/" <<< "$domain") if [ -z "${validDomain}" ]; then echo "::: $1 is not a valid argument or domain name" else @@ -77,12 +86,12 @@ PoplistFile() { } AddDomain() { - list="$2" + domain=$(EscapeRegexp "$1") bool=true #Is the domain in the list we want to add it to? - grep -Ex -q "$1" ${list} > /dev/null 2>&1 || bool=false + grep -Ex -q "${domain}" ${list} > /dev/null 2>&1 || bool=false if [[ "${bool}" == false ]]; then #domain not found in the whitelist file, add it! @@ -101,14 +110,16 @@ AddDomain() { RemoveDomain() { list="$2" + domain=$(EscapeRegexp "$1") bool=true - #Is it in the other list? Logic follows that if its whitelisted it should not be blacklisted and vice versa - grep -Ex -q "$1" ${list} > /dev/null 2>&1 || bool=false + #Is it in the list? Logic follows that if its whitelisted it should not be blacklisted and vice versa + grep -Ex -q "${domain}" ${list} > /dev/null 2>&1 || bool=false if [[ "${bool}" == true ]]; then # Remove it from the other one echo "::: Removing $1 from $list..." - echo "$1" | sed 's/\./\\./g' | xargs -I {} perl -i -ne'print unless /'{}'(?!.)/;' ${list} + # /I flag: search case-insensitive + sed -i "/${domain}/Id" ${list} reload=true else if [[ "${verbose}" == true ]]; then diff --git a/advanced/Scripts/piholeDebug.sh b/advanced/Scripts/piholeDebug.sh index 6768a8ea..d0e60177 100755 --- a/advanced/Scripts/piholeDebug.sh +++ b/advanced/Scripts/piholeDebug.sh @@ -27,7 +27,7 @@ PIHOLELOG="/var/log/pihole.log" WHITELISTMATCHES="/tmp/whitelistmatches.list" IPV6_READY=false - +TIMEOUT=60 # Header info and introduction cat << EOM ::: Beginning Pi-hole debug at $(date)! @@ -316,6 +316,16 @@ debugLighttpd() { echo ":::" } +countdown() { + tuvix=${TIMEOUT} + printf "::: Logging will automatically teminate in ${TIMEOUT} seconds\n" + while [ $tuvix -ge 1 ] + do + printf ":::\t${tuvix} seconds left. \r" + sleep 5 + tuvix=$(( tuvix - 5 )) + done +} ### END FUNCTIONS ### # Gather version of required packages / repositories @@ -356,10 +366,9 @@ dumpPiHoleLog() { echo -e "::: Try loading a site that you are having trouble with now from a client web browser.. \n:::\t(Press CTRL+C to finish logging.)" header_write "pihole.log" if [ -e "${PIHOLELOG}" ]; then - while true; do - tail -f "${PIHOLELOG}" >> ${DEBUG_LOG} - log_write "" - done + # Dummy process to use for flagging down tail to terminate + countdown & + tail -n0 -f --pid=$! "${PIHOLELOG}" >> ${DEBUG_LOG} else log_write "No pihole.log file found!" printf ":::\tNo pihole.log file found!\n" @@ -384,10 +393,13 @@ finalWork() { # Check if tricorder.pi-hole.net is reachable and provide token. if [ -n "${tricorder}" ]; then echo "::: Your debug token is : ${tricorder}" - echo "::: Please contact the Pi-hole team with your token to being assistance." + echo "::: Please contact the Pi-hole team with your token for assistance." echo "::: Thank you." + else + echo "::: There was an error uploading your debug log." + echo "::: Please try again or contact the Pi-hole team for assistance." fi - echo "::: Debug log can be found at : /var/log/pihole_debug.log" + echo "::: A local copy of the Debug log can be found at : /var/log/pihole_debug.log" } trap finalWork EXIT 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..42bd96ce 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 } @@ -84,7 +86,7 @@ helpFunc() { ::: -h, --help Show this help dialog ::: EOM - exit 1 + exit 0 } if [[ $# = 0 ]]; then diff --git a/advanced/Scripts/webpage.sh b/advanced/Scripts/webpage.sh new file mode 100755 index 00000000..07706346 --- /dev/null +++ b/advanced/Scripts/webpage.sh @@ -0,0 +1,242 @@ +#!/usr/bin/env bash +# Pi-hole: A black hole for Internet advertisements +# Network-wide ad blocking via your Raspberry Pi +# http://pi-hole.net +# Web interface settings +# +# 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 +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. + +args=("$@") + +helpFunc() { + cat << EOM +::: Set admin options for the web interface of pihole +::: +::: Usage: pihole -a [options] +::: +::: Options: +::: -p, password Set web interface password, an empty input will remove any previously set password +::: -c, celsius Set Celsius temperature unit +::: -f, fahrenheit Set Fahrenheit temperature unit +::: -k, kelvin Set Kelvin temperature unit +::: -h, --help Show this help dialog +EOM + exit 0 +} + +SetTemperatureUnit(){ + + # Remove setting from file (create backup setupVars.conf.bak) + sed -i.bak '/TEMPERATUREUNIT/d' /etc/pihole/setupVars.conf + # Save setting to file + echo "TEMPERATUREUNIT=${unit}" >> /etc/pihole/setupVars.conf + +} + +SetWebPassword(){ + + if [ "${SUDO_USER}" == "www-data" ]; then + echo "Security measure: user www-data is not allowed to change webUI password!" + echo "Exiting" + exit 1 + fi + + if [ "${SUDO_USER}" == "lighttpd" ]; then + echo "Security measure: user lighttpd is not allowed to change webUI password!" + echo "Exiting" + exit 1 + fi + + # Remove password from file (create backup setupVars.conf.bak) + sed -i.bak '/WEBPASSWORD/d' /etc/pihole/setupVars.conf + # Set password only if there is one to be set + if (( ${#args[2]} > 0 )) ; then + # Compute password hash twice to avoid rainbow table vulnerability + hash=$(echo -n ${args[2]} | sha256sum | sed 's/\s.*$//') + hash=$(echo -n ${hash} | sha256sum | sed 's/\s.*$//') + # Save hash to file + echo "WEBPASSWORD=${hash}" >> /etc/pihole/setupVars.conf + echo "New password set" + else + echo "Password removed" + fi + +} + +SetDNSServers(){ + + # Remove setting from file (create backup setupVars.conf.bak) + sed -i.bak '/PIHOLE_DNS_1/d;/PIHOLE_DNS_2/d;/DNS_FQDN_REQUIRED/d;' /etc/pihole/setupVars.conf + # Save setting to file + echo "PIHOLE_DNS_1=${args[2]}" >> /etc/pihole/setupVars.conf + if [[ "${args[3]}" != "none" ]]; then + echo "PIHOLE_DNS_2=${args[3]}" >> /etc/pihole/setupVars.conf + else + echo "PIHOLE_DNS_2=" >> /etc/pihole/setupVars.conf + fi + + # Replace within actual dnsmasq config file + sed -i '/server=/d;' /etc/dnsmasq.d/01-pihole.conf + echo "server=${args[2]}" >> /etc/dnsmasq.d/01-pihole.conf + if [[ "${args[3]}" != "none" ]]; then + echo "server=${args[3]}" >> /etc/dnsmasq.d/01-pihole.conf + fi + + # Remove domain-needed entry + sed -i '/domain-needed/d;' /etc/dnsmasq.d/01-pihole.conf + + # Readd it if required + if [[ "${args[4]}" == "domain-needed" ]]; then + echo "domain-needed" >> /etc/dnsmasq.d/01-pihole.conf + echo "DNS_FQDN_REQUIRED=true" >> /etc/pihole/setupVars.conf + else + # Leave it deleted if not wanted + echo "DNS_FQDN_REQUIRED=false" >> /etc/pihole/setupVars.conf + fi + + # Remove bogus-priv entry + sed -i '/bogus-priv/d;' /etc/dnsmasq.d/01-pihole.conf + + # Readd it if required + if [[ "${args[5]}" == "bogus-priv" ]]; then + echo "bogus-priv" >> /etc/dnsmasq.d/01-pihole.conf + echo "DNS_BOGUS_PRIV=true" >> /etc/pihole/setupVars.conf + else + # Leave it deleted if not wanted + echo "DNS_BOGUS_PRIV=false" >> /etc/pihole/setupVars.conf + fi + + # Restart dnsmasq to load new configuration + RestartDNS + +} + +SetExcludeDomains(){ + + # Remove setting from file (create backup setupVars.conf.bak) + sed -i.bak '/API_EXCLUDE_DOMAINS/d;' /etc/pihole/setupVars.conf + # Save setting to file + echo "API_EXCLUDE_DOMAINS=${args[2]}" >> /etc/pihole/setupVars.conf +} + +SetExcludeClients(){ + + # Remove setting from file (create backup setupVars.conf.bak) + sed -i.bak '/API_EXCLUDE_CLIENTS/d;' /etc/pihole/setupVars.conf + # Save setting to file + echo "API_EXCLUDE_CLIENTS=${args[2]}" >> /etc/pihole/setupVars.conf +} + +Reboot(){ + + nohup bash -c "sleep 5; reboot" &> /dev/null /dev/null + else + service dnsmasq restart &> /dev/null + fi + +} + +SetQueryLogOptions(){ + + # Remove setting from file (create backup setupVars.conf.bak) + sed -i.bak '/API_QUERY_LOG_SHOW/d;' /etc/pihole/setupVars.conf + # Save setting to file + echo "API_QUERY_LOG_SHOW=${args[2]}" >> /etc/pihole/setupVars.conf +} + +EnableDHCP(){ + + # Remove setting from file (create backup setupVars.conf.bak) + sed -i.bak '/DHCP_/d;' /etc/pihole/setupVars.conf + echo "DHCP_ACTIVE=true" >> /etc/pihole/setupVars.conf + echo "DHCP_START=${args[2]}" >> /etc/pihole/setupVars.conf + echo "DHCP_END=${args[3]}" >> /etc/pihole/setupVars.conf + echo "DHCP_ROUTER=${args[4]}" >> /etc/pihole/setupVars.conf + + # Remove setting from file + sed -i '/dhcp-/d;/quiet-dhcp/d;' /etc/dnsmasq.d/01-pihole.conf + # Save setting to file + echo "dhcp-range=${args[2]},${args[3]},infinite" >> /etc/dnsmasq.d/01-pihole.conf + echo "dhcp-option=option:router,${args[4]}" >> /etc/dnsmasq.d/01-pihole.conf + # Changes the behaviour from strict RFC compliance so that DHCP requests on unknown leases from unknown hosts are not ignored. This allows new hosts to get a lease without a tedious timeout under all circumstances. It also allows dnsmasq to rebuild its lease database without each client needing to reacquire a lease, if the database is lost. + echo "dhcp-authoritative" >> /etc/dnsmasq.d/01-pihole.conf + # Use the specified file to store DHCP lease information + echo "dhcp-leasefile=/etc/pihole/dhcp.leases" >> /etc/dnsmasq.d/01-pihole.conf + # Suppress logging of the routine operation of these protocols. Errors and problems will still be logged, though. + echo "quiet-dhcp" >> /etc/dnsmasq.d/01-pihole.conf + echo "quiet-dhcp6" >> /etc/dnsmasq.d/01-pihole.conf + + RestartDNS +} + +DisableDHCP(){ + + # Remove setting from file (create backup setupVars.conf.bak) + sed -i.bak '/DHCP_ACTIVE/d;' /etc/pihole/setupVars.conf + echo "DHCP_ACTIVE=false" >> /etc/pihole/setupVars.conf + + # Remove setting from file + sed -i '/dhcp-/d;/quiet-dhcp/d;' /etc/dnsmasq.d/01-pihole.conf + + RestartDNS +} + +SetWebUILayout(){ + + # Remove setting from file (create backup setupVars.conf.bak) + sed -i.bak '/WEBUIBOXEDLAYOUT/d;' /etc/pihole/setupVars.conf + echo "WEBUIBOXEDLAYOUT=${args[2]}" >> /etc/pihole/setupVars.conf + +} + +SetDNSDomainName(){ + + # Remove setting from file (create backup setupVars.conf.bak) + sed -i.bak '/PIHOLE_DOMAIN/d;' /etc/pihole/setupVars.conf + # Save setting to file + echo "PIHOLE_DOMAIN=${args[2]}" >> /etc/pihole/setupVars.conf + + # Replace within actual dnsmasq config file + sed -i '/domain=/d;' /etc/dnsmasq.d/01-pihole.conf + echo "domain=${args[2]}" >> /etc/dnsmasq.d/01-pihole.conf + + # Restart dnsmasq to load new configuration + RestartDNS + +} + +case "${args[1]}" in + "-p" | "password" ) SetWebPassword;; + "-c" | "celsius" ) unit="C"; SetTemperatureUnit;; + "-f" | "fahrenheit" ) unit="F"; SetTemperatureUnit;; + "-k" | "kelvin" ) unit="K"; SetTemperatureUnit;; + "setdns" ) SetDNSServers;; + "setexcludedomains" ) SetExcludeDomains;; + "setexcludeclients" ) SetExcludeClients;; + "reboot" ) Reboot;; + "restartdns" ) RestartDNS;; + "setquerylog" ) SetQueryLogOptions;; + "enabledhcp" ) EnableDHCP;; + "disabledhcp" ) DisableDHCP;; + "layout" ) SetWebUILayout;; + "-h" | "--help" ) helpFunc;; + "domainname" ) SetDNSDomainName;; + * ) helpFunc;; +esac + +shift + +if [[ $# = 0 ]]; then + helpFunc +fi + diff --git a/advanced/bash-completion/pihole b/advanced/bash-completion/pihole index dd3f050d..05baa820 100644 --- a/advanced/bash-completion/pihole +++ b/advanced/bash-completion/pihole @@ -3,7 +3,7 @@ _pihole() { COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" - opts="blacklist chronometer debug flush help query reconfigure setupLCD uninstall updateGravity updatePihole version whitelist" + opts="admin blacklist chronometer debug disable enable flush help logging query reconfigure restartdns setupLCD status tail uninstall updateGravity updatePihole version whitelist" COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 diff --git a/advanced/pihole.cron b/advanced/pihole.cron index 77b7c1ca..8311acfb 100644 --- a/advanced/pihole.cron +++ b/advanced/pihole.cron @@ -8,7 +8,7 @@ # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# +# # This file is under source-control of the Pi-hole installation and update # scripts, any changes made to this file will be overwritten when the softare # is updated or re-installed. Please make any changes to the appropriate crontab @@ -16,11 +16,11 @@ # Pi-hole: Update the ad sources once a week on Sunday at 01:59 # Download any updates from the adlists -59 1 * * 7 root /usr/local/bin/pihole updateGravity +59 1 * * 7 root PATH="$PATH:/usr/local/bin/" pihole updateGravity # Pi-hole: Update Pi-hole! Uncomment to enable auto update -#30 2 * * 7 root /usr/local/bin/pihole updatePihole +#30 2 * * 7 root PATH="$PATH:/usr/local/bin/" pihole updatePihole # Pi-hole: Flush the log daily at 00:00 so it doesn't get out of control # Stats will be viewable in the Web interface thanks to the cron job above -00 00 * * * root /usr/local/bin/pihole flush +00 00 * * * root PATH="$PATH:/usr/local/bin/" pihole flush diff --git a/automated install/basic-install.sh b/automated install/basic-install.sh index c299e0ea..3dacdfff 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,1001 +54,1072 @@ 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 - #Decide if php should be `php5` or just `php` (Fixes issues with Ubuntu 16.04 LTS) - phpVer="php5" - apt-get install --dry-run php5 > /dev/null 2>&1 || phpVer="php" - ############################################# - 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" - INSTALLER_DEPS=( apt-utils whiptail git dhcpcd5) - PIHOLE_DEPS=( dnsutils bc dnsmasq lighttpd ${phpVer}-common ${phpVer}-cgi curl unzip wget sudo netcat cron iproute2 ) - 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_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 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" + # Prefer the php metapackage if it's there, fall back on the php5 pacakges + ${PKG_MANAGER} install --dry-run php > /dev/null 2>&1 && phpVer="php" || phpVer="php5" + # ######################################### + INSTALLER_DEPS=(apt-utils debconf dhcpcd5 git whiptail) + PIHOLE_DEPS=(bc cron curl dnsmasq dnsutils ${IPROUTE_PKG} iputils-ping lighttpd lsof netcat ${phpVer}-common ${phpVer}-cgi sudo unzip wget) + 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_INSTALL="${PKG_MANAGER} install -y" + PKG_COUNT="${PKG_MANAGER} check-update | egrep '(.i686|.x86|.noarch|.arm|.src)' | wc -l" + INSTALLER_DEPS=(git iproute net-tools newt procps-ng) + PIHOLE_DEPS=(bc bind-utils cronie curl dnsmasq epel-release findutils lighttpd lighttpd-fastcgi nmap-ncat php php-common php-cli sudo unzip wget) + + 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" +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 + 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 + git pull -q &> /dev/null + 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}') - IPv4gw=$(ip route get 8.8.8.8 | awk '{print $3}') + # 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}') + IPv4gw=$(ip route get 8.8.8.8 | awk '{print $3}') } get_available_interfaces() { - # Get available interfaces. Consider only getting UP interfaces in the future, and leaving DOWN interfaces out of list. - availableInterfaces=$(ip -o link | awk '{print $2}' | grep -v "lo" | cut -d':' -f1 | cut -d'@' -f1) + # Get available interfaces. Consider only getting UP interfaces in the future, and leaving DOWN interfaces out of list. + availableInterfaces=$(ip -o link | awk '{print $2}' | grep -v "lo" | 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} + # 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} + # 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. + # 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. - echo "::: Verifying free disk space..." - local required_free_kilobytes=51200 - local existing_free_kilobytes=$(df -Pk | grep -m1 '\/$' | awk '{print $4}') + # 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. + echo "::: Verifying free disk space..." + 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 "::: Unknown free disk space!" - echo "::: We were unable to determine available free disk space on this system." - echo "::: You may override this check and force the installation, however, it is not recommended" - echo "::: To do so, pass the argument '--i_do_not_follow_recommendations' to the install script" - echo "::: eg. curl -L https://install.pi-hole.net | bash /dev/stdin --i_do_not_follow_recommendations" - exit 1 - # - Insufficient free disk space - elif [[ ${existing_free_kilobytes} -lt ${required_free_kilobytes} ]]; then - echo "::: Insufficient Disk Space!" - echo "::: Your system appears to be low on disk space. pi-hole recommends a minimum of $required_free_kilobytes KiloBytes." - echo "::: You only have ${existing_free_kilobytes} KiloBytes free." - echo "::: If this is a new install you may need to expand your disk." - echo "::: Try running 'sudo raspi-config', and choose the 'expand file system option'" - echo "::: After rebooting, run this installation again. (curl -L https://install.pi-hole.net | bash)" - - echo "Insufficient free space, exiting..." - exit 1 - - fi + # - Unknown free disk space , not a integer + if ! [[ "${existing_free_kilobytes}" =~ ^([0-9])+$ ]]; then + echo "::: Unknown free disk space!" + echo "::: We were unable to determine available free disk space on this system." + echo "::: You may override this check and force the installation, however, it is not recommended" + echo "::: To do so, pass the argument '--i_do_not_follow_recommendations' to the install script" + echo "::: eg. curl -L https://install.pi-hole.net | bash /dev/stdin --i_do_not_follow_recommendations" + exit 1 + # - Insufficient free disk space + elif [[ ${existing_free_kilobytes} -lt ${required_free_kilobytes} ]]; then + echo "::: Insufficient Disk Space!" + echo "::: Your system appears to be low on disk space. pi-hole recommends a minimum of $required_free_kilobytes KiloBytes." + echo "::: You only have ${existing_free_kilobytes} KiloBytes free." + echo "::: If this is a new install you may need to expand your disk." + echo "::: Try running 'sudo raspi-config', and choose the 'expand file system option'" + echo "::: After rebooting, run this installation again. (curl -L https://install.pi-hole.net | bash)" + echo "Insufficient free space, exiting..." + exit 1 + fi } chooseInterface() { - # Turn the available interfaces into an array so it can be used with a whiptail dialog - local interfacesArray=() - # Number of available interfaces - local interfaceCount - # Whiptail variable storage - local chooseInterfaceCmd - # Temporary Whiptail options storage - local chooseInterfaceOptions - # Loop sentinel variable - local firstLoop=1 + # Turn the available interfaces into an array so it can be used with a whiptail dialog + local interfacesArray=() + # Number of available interfaces + local interfaceCount + # Whiptail variable storage + local chooseInterfaceCmd + # Temporary Whiptail options storage + local chooseInterfaceOptions + # Loop sentinel variable + local firstLoop=1 - while read -r line; do - mode="OFF" - if [[ ${firstLoop} -eq 1 ]]; then - firstLoop=0 - mode="ON" - fi - interfacesArray+=("${line}" "available" "${mode}") - done <<< "${availableInterfaces}" + while read -r line; do + mode="OFF" + if [[ ${firstLoop} -eq 1 ]]; then + firstLoop=0 + mode="ON" + fi + interfacesArray+=("${line}" "available" "${mode}") + done <<< "${availableInterfaces}" - # Find out how many interfaces are available to choose from - interfaceCount=$(echo "${availableInterfaces}" | wc -l) - chooseInterfaceCmd=(whiptail --separate-output --radiolist "Choose An Interface (press space to select)" ${r} ${c} ${interfaceCount}) - chooseInterfaceOptions=$("${chooseInterfaceCmd[@]}" "${interfacesArray[@]}" 2>&1 >/dev/tty) - if [[ $? = 0 ]]; then - for desiredInterface in ${chooseInterfaceOptions}; do - piholeInterface=${desiredInterface} - echo "::: Using interface: $piholeInterface" - done - else - echo "::: Cancel selected, exiting...." - exit 1 - fi + # Find out how many interfaces are available to choose from + interfaceCount=$(echo "${availableInterfaces}" | wc -l) + chooseInterfaceCmd=(whiptail --separate-output --radiolist "Choose An Interface (press space to select)" ${r} ${c} ${interfaceCount}) + chooseInterfaceOptions=$("${chooseInterfaceCmd[@]}" "${interfacesArray[@]}" 2>&1 >/dev/tty) + if [[ $? = 0 ]]; then + for desiredInterface in ${chooseInterfaceOptions}; do + PIHOLE_INTERFACE=${desiredInterface} + echo "::: Using interface: $PIHOLE_INTERFACE" + done + else + echo "::: Cancel selected, exiting...." + exit 1 + fi } 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} + # 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} } use4andor6() { - local useIPv4 - local useIPv6 - # Let use select IPv4 and/or IPv6 - cmd=(whiptail --separate-output --checklist "Select Protocols (press space to select)" ${r} ${c} 2) - options=(IPv4 "Block ads over IPv4" on - IPv6 "Block ads over IPv6" off) - choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) - if [[ $? = 0 ]];then - for choice in ${choices} - do - case ${choice} in - IPv4 ) useIPv4=true;; - IPv6 ) useIPv6=true;; - esac - done - if [[ ${useIPv4} ]]; then - find_IPv4_information - getStaticIPv4Settings - setStaticIPv4 - fi - if [[ ${useIPv6} ]]; then - useIPv6dialog - fi - echo "::: IPv4 address: ${IPv4_address}" - echo "::: IPv6 address: ${IPv6_address}" - if [ ! ${useIPv4} ] && [ ! ${useIPv6} ]; then - echo "::: Cannot continue, neither IPv4 or IPv6 selected" - echo "::: Exiting" - exit 1 - fi - else - echo "::: Cancel selected. Exiting..." - exit 1 - fi + local useIPv4 + local useIPv6 + # Let use select IPv4 and/or IPv6 + cmd=(whiptail --separate-output --checklist "Select Protocols (press space to select)" ${r} ${c} 2) + options=(IPv4 "Block ads over IPv4" on + IPv6 "Block ads over IPv6" on) + choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) + if [[ $? = 0 ]];then + for choice in ${choices} + do + case ${choice} in + IPv4 ) useIPv4=true;; + IPv6 ) useIPv6=true;; + esac + done + if [[ ${useIPv4} ]]; then + find_IPv4_information + getStaticIPv4Settings + setStaticIPv4 + fi + if [[ ${useIPv6} ]]; then + useIPv6dialog + fi + echo "::: IPv4 address: ${IPV4_ADDRESS}" + echo "::: IPv6 address: ${IPV6_ADDRESS}" + if [ ! ${useIPv4} ] && [ ! ${useIPv6} ]; then + echo "::: Cannot continue, neither IPv4 or IPv6 selected" + echo "::: Exiting" + exit 1 + fi + else + echo "::: Cancel selected. Exiting..." + exit 1 + fi } 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} - 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. + # 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} + 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. If you are worried, either manually set the address, or modify the DHCP reservation pool so it does not include the IP you want. 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} - # Nothing else to do since the variables are already set above - else - # Otherwise, we need to ask the user to input their desired settings. - # Start by getting the IPv4 address (pre-filling it with info gathered from DHCP) - # 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) - if [[ $? = 0 ]]; then - 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} - Gateway: ${IPv4gw}" ${r} ${c}); then - # After that's done, the loop ends and we move on - ipSettingsCorrect=True - else - # If the settings are wrong, the loop continues - ipSettingsCorrect=False - fi - else - # Cancelling gateway settings window - ipSettingsCorrect=False - echo "::: Cancel selected. Exiting..." - exit 1 - fi - else - # Cancelling IPv4 settings window - ipSettingsCorrect=False - echo "::: Cancel selected. Exiting..." - exit 1 - fi - done - # End the if statement for DHCP vs. static - fi + # Nothing else to do since the variables are already set above + else + # Otherwise, we need to ask the user to input their desired settings. + # Start by getting the IPv4 address (pre-filling it with info gathered from DHCP) + # 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) + if [[ $? = 0 ]]; then + 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} + Gateway: ${IPv4gw}" ${r} ${c}); then + # After that's done, the loop ends and we move on + ipSettingsCorrect=True + else + # If the settings are wrong, the loop continues + ipSettingsCorrect=False + fi + else + # Cancelling gateway settings window + ipSettingsCorrect=False + echo "::: Cancel selected. Exiting..." + exit 1 + fi + else + # Cancelling IPv4 settings window + ipSettingsCorrect=False + echo "::: Cancel selected. Exiting..." + exit 1 + fi + done + # End the if statement for DHCP vs. static + fi } setDHCPCD() { - # Append these lines to dhcpcd.conf to enable a static IP - echo "## interface ${piholeInterface} - static ip_address=${IPv4_address} - static routers=${IPv4gw} - static domain_name_servers=${IPv4gw}" | tee -a /etc/dhcpcd.conf >/dev/null + # Append these lines to dhcpcd.conf to enable a static IP + echo "## interface ${PIHOLE_INTERFACE} + static ip_address=${IPV4_ADDRESS} + static routers=${IPv4gw} + static domain_name_servers=${IPv4gw}" | tee -a /etc/dhcpcd.conf >/dev/null } setStaticIPv4() { - local IFCFG_FILE - local IPADDR - local CIDR - if [[ -f /etc/dhcpcd.conf ]]; then - # Debian Family - if grep -q "${IPv4_address}" /etc/dhcpcd.conf; then - echo "::: Static IP already configured" - else - setDHCPCD - ip addr replace dev "${piholeInterface}" "${IPv4_address}" - echo ":::" - 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 - # Fedora Family - IFCFG_FILE=/etc/sysconfig/network-scripts/ifcfg-${piholeInterface} - 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/) - # 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 "BOOTPROTO=none" - echo "ONBOOT=yes" - echo "IPADDR=$IPADDR" - echo "PREFIX=$CIDR" - echo "GATEWAY=$IPv4gw" - echo "DNS1=$piholeDNS1" - echo "DNS2=$piholeDNS2" - echo "USERCTL=no" - }>> "${IFCFG_FILE}" - ip addr replace dev "${piholeInterface}" "${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 ":::" - fi - else - echo "::: Warning: Unable to locate configuration file to set static IPv4 address!" - exit 1 - fi + local IFCFG_FILE + local IPADDR + local CIDR + if [[ -f /etc/dhcpcd.conf ]]; then + # Debian Family + if grep -q "${IPV4_ADDRESS}" /etc/dhcpcd.conf; then + echo "::: Static IP already configured" + else + setDHCPCD + 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 ":::" + fi + elif [[ -f /etc/sysconfig/network-scripts/ifcfg-${PIHOLE_INTERFACE} ]];then + # Fedora Family + 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/) + # Backup existing interface configuration: + cp "${IFCFG_FILE}" "${IFCFG_FILE}".pihole.orig + # Build Interface configuration file: + { + echo "# Configured via Pi-Hole installer" + echo "DEVICE=$PIHOLE_INTERFACE" + echo "BOOTPROTO=none" + echo "ONBOOT=yes" + echo "IPADDR=$IPADDR" + echo "PREFIX=$CIDR" + echo "GATEWAY=$IPv4gw" + echo "DNS1=$PIHOLE_DNS_1" + echo "DNS2=$PIHOLE_DNS_2" + echo "USERCTL=no" + }> "${IFCFG_FILE}" + 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 ":::" + fi + else + echo "::: Warning: Unable to locate configuration file to set static IPv4 address!" + exit 1 + fi } valid_ip() { - local ip=${1} - local stat=1 + local ip=${1} + local stat=1 - if [[ ${ip} =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then - OIFS=$IFS - IFS='.' - ip=(${ip}) - IFS=${OIFS} - [[ ${ip[0]} -le 255 && ${ip[1]} -le 255 \ - && ${ip[2]} -le 255 && ${ip[3]} -le 255 ]] - stat=$? - fi - return ${stat} + if [[ ${ip} =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then + OIFS=$IFS + IFS='.' + ip=(${ip}) + IFS=${OIFS} + [[ ${ip[0]} -le 255 && ${ip[1]} -le 255 \ + && ${ip[2]} -le 255 && ${ip[3]} -le 255 ]] + stat=$? + fi + return ${stat} } setDNS() { - DNSChooseCmd=(whiptail --separate-output --radiolist "Select Upstream DNS Provider. To use your own, select Custom." ${r} ${c} 6) - DNSChooseOptions=(Google "" on - OpenDNS "" off - Level3 "" off - Norton "" off - Comodo "" off - Custom "" off) - DNSchoices=$("${DNSChooseCmd[@]}" "${DNSChooseOptions[@]}" 2>&1 >/dev/tty) - if [[ $? = 0 ]];then - case ${DNSchoices} in - Google) - echo "::: Using Google DNS servers." - piholeDNS1="8.8.8.8" - piholeDNS2="8.8.4.4" - ;; - OpenDNS) - echo "::: Using OpenDNS servers." - piholeDNS1="208.67.222.222" - piholeDNS2="208.67.220.220" - ;; - Level3) - echo "::: Using Level3 servers." - piholeDNS1="4.2.2.1" - piholeDNS2="4.2.2.2" - ;; - Norton) - echo "::: Using Norton ConnectSafe servers." - piholeDNS1="199.85.126.10" - piholeDNS2="199.85.127.10" - ;; - Comodo) - echo "::: Using Comodo Secure servers." - piholeDNS1="8.26.56.26" - piholeDNS2="8.20.247.20" - ;; - Custom) - until [[ ${DNSSettingsCorrect} = True ]]; do - strInvalid="Invalid" - if [ ! ${piholeDNS1} ]; then - if [ ! ${piholeDNS2} ]; then - prePopulate="" - else - prePopulate=", ${piholeDNS2}" - fi - elif [ ${piholeDNS1} ] && [ ! ${piholeDNS2} ]; then - prePopulate="${piholeDNS1}" - elif [ ${piholeDNS1} ] && [ ${piholeDNS2} ]; then - prePopulate="${piholeDNS1}, ${piholeDNS2}" - fi + DNSChooseCmd=(whiptail --separate-output --radiolist "Select Upstream DNS Provider. To use your own, select Custom." ${r} ${c} 6) + DNSChooseOptions=(Google "" on + OpenDNS "" off + Level3 "" off + Norton "" off + Comodo "" off + Custom "" off) + DNSchoices=$("${DNSChooseCmd[@]}" "${DNSChooseOptions[@]}" 2>&1 >/dev/tty) + if [[ $? = 0 ]];then + case ${DNSchoices} in + Google) + echo "::: Using Google DNS servers." + PIHOLE_DNS_1="8.8.8.8" + PIHOLE_DNS_2="8.8.4.4" + ;; + OpenDNS) + echo "::: Using OpenDNS servers." + PIHOLE_DNS_1="208.67.222.222" + PIHOLE_DNS_2="208.67.220.220" + ;; + Level3) + echo "::: Using Level3 servers." + PIHOLE_DNS_1="4.2.2.1" + PIHOLE_DNS_2="4.2.2.2" + ;; + Norton) + echo "::: Using Norton ConnectSafe servers." + PIHOLE_DNS_1="199.85.126.10" + PIHOLE_DNS_2="199.85.127.10" + ;; + Comodo) + echo "::: Using Comodo Secure servers." + PIHOLE_DNS_1="8.26.56.26" + PIHOLE_DNS_2="8.20.247.20" + ;; + Custom) + until [[ ${DNSSettingsCorrect} = True ]]; do + strInvalid="Invalid" + if [ ! ${PIHOLE_DNS_1} ]; then + if [ ! ${PIHOLE_DNS_2} ]; then + prePopulate="" + else + prePopulate=", ${PIHOLE_DNS_2}" + fi + 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) + 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} - fi - if ! valid_ip "${piholeDNS2}" && [ "${piholeDNS2}" ]; then - piholeDNS2=${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="" - fi - if [[ ${piholeDNS2} == "${strInvalid}" ]]; then - piholeDNS2="" - 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 - DNSSettingsCorrect=True - else - # If the settings are wrong, the loop continues - DNSSettingsCorrect=False - fi - fi - done - ;; - esac - else - echo "::: Cancel selected. Exiting..." - exit 1 - fi + if [[ $? = 0 ]]; then + 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 "${PIHOLE_DNS_2}" && [ "${PIHOLE_DNS_2}" ]; then + PIHOLE_DNS_2=${strInvalid} + fi + else + echo "::: Cancel selected, exiting...." + exit 1 + fi + 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 [[ ${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: $PIHOLE_DNS_1\n DNS Server 2: ${PIHOLE_DNS_2}" ${r} ${c}); then + DNSSettingsCorrect=True + else + # If the settings are wrong, the loop continues + DNSSettingsCorrect=False + fi + fi + done + ;; + esac + else + echo "::: Cancel selected. Exiting..." + exit 1 + 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" - local dnsmasq_conf_orig="/etc/dnsmasq.conf.orig" - local dnsmasq_pihole_id_string="addn-hosts=/etc/pihole/gravity.list" - local dnsmasq_original_config="/etc/.pihole/advanced/dnsmasq.conf.original" - local dnsmasq_pihole_01_snippet="/etc/.pihole/advanced/01-pihole.conf" - local dnsmasq_pihole_01_location="/etc/dnsmasq.d/01-pihole.conf" + # 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" + local dnsmasq_conf_orig="/etc/dnsmasq.conf.orig" + local dnsmasq_pihole_id_string="addn-hosts=/etc/pihole/gravity.list" + local dnsmasq_original_config="/etc/.pihole/advanced/dnsmasq.conf.original" + local dnsmasq_pihole_01_snippet="/etc/.pihole/advanced/01-pihole.conf" + local dnsmasq_pihole_01_location="/etc/dnsmasq.d/01-pihole.conf" - if [ -f ${dnsmasq_conf} ]; then - echo -n "::: Existing dnsmasq.conf found..." - if grep -q ${dnsmasq_pihole_id_string} ${dnsmasq_conf}; then - echo " it is from a previous pi-hole install." - echo -n "::: Backing up dnsmasq.conf to dnsmasq.conf.orig..." - mv -f ${dnsmasq_conf} ${dnsmasq_conf_orig} - echo " done." - echo -n "::: Restoring default dnsmasq.conf..." - cp ${dnsmasq_original_config} ${dnsmasq_conf} - echo " done." - else - echo " it is not a pi-hole file, leaving alone!" - fi - else - echo -n "::: No dnsmasq.conf found.. restoring default dnsmasq.conf..." - cp ${dnsmasq_original_config} ${dnsmasq_conf} - echo " done." - fi + if [ -f ${dnsmasq_conf} ]; then + echo -n "::: Existing dnsmasq.conf found..." + if grep -q ${dnsmasq_pihole_id_string} ${dnsmasq_conf}; then + echo " it is from a previous pi-hole install." + echo -n "::: Backing up dnsmasq.conf to dnsmasq.conf.orig..." + mv -f ${dnsmasq_conf} ${dnsmasq_conf_orig} + echo " done." + echo -n "::: Restoring default dnsmasq.conf..." + cp ${dnsmasq_original_config} ${dnsmasq_conf} + echo " done." + else + echo " it is not a pi-hole file, leaving alone!" + fi + else + echo -n "::: No dnsmasq.conf found.. restoring default dnsmasq.conf..." + cp ${dnsmasq_original_config} ${dnsmasq_conf} + echo " done." + fi - 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} - else - sed -i '/^server=@DNS1@/d' ${dnsmasq_pihole_01_location} - fi - if [[ "${piholeDNS2}" != "" ]]; then - sed -i "s/@DNS2@/$piholeDNS2/" ${dnsmasq_pihole_01_location} - else - sed -i '/^server=@DNS2@/d' ${dnsmasq_pihole_01_location} - fi + 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@/$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 [[ "${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 - #sed -i "s/@HOSTNAME@/$hostname/" ${dnsmasq_pihole_01_location} + sed -i 's/^#conf-dir=\/etc\/dnsmasq.d$/conf-dir=\/etc\/dnsmasq.d/' ${dnsmasq_conf} - if [[ -f /etc/hostname ]]; then - hostname=$( /dev/null & spinner $! || true - else - service "${1}" stop &> /dev/null & spinner $! || true - fi - echo " done." + # Stop service passed in as argument. + # Can softfail, as process may not be installed when this is called + echo ":::" + echo -n "::: Stopping ${1} service..." + if [ -x "$(command -v systemctl)" ]; then + systemctl stop "${1}" &> /dev/null || true + else + service "${1}" stop &> /dev/null || true + fi + echo " done." } start_service() { - # Start/Restart service passed in as argument - # This should not fail, it's an error if it does - echo ":::" - echo -n "::: Starting ${1} service..." - if [ -x "$(command -v systemctl)" ]; then - systemctl restart "${1}" &> /dev/null & spinner $! - else - service "${1}" restart &> /dev/null & spinner $! - fi - echo " done." + # Start/Restart service passed in as argument + # This should not fail, it's an error if it does + echo ":::" + echo -n "::: Starting ${1} service..." + if [ -x "$(command -v systemctl)" ]; then + systemctl restart "${1}" &> /dev/null + else + service "${1}" restart &> /dev/null + fi + echo " done." } enable_service() { - # Enable service so that it will start with next reboot - echo ":::" - echo -n "::: Enabling ${1} service to start on reboot..." - if [ -x "$(command -v systemctl)" ]; then - systemctl enable "${1}" &> /dev/null & spinner $! - else - update-rc.d "${1}" defaults &> /dev/null & spinner $! - fi - echo " done." + # Enable service so that it will start with next reboot + echo ":::" + echo -n "::: Enabling ${1} service to start on reboot..." + if [ -x "$(command -v systemctl)" ]; then + systemctl enable "${1}" &> /dev/null + else + update-rc.d "${1}" defaults &> /dev/null + fi + echo " done." } update_pacakge_cache() { - #Running apt-get update/upgrade with minimal output can cause some issues with - #requiring user input (e.g password for phpmyadmin see #218) + #Running apt-get update/upgrade with minimal output can cause some issues with + #requiring user input (e.g password for phpmyadmin see #218) - #Check to see if apt-get update has already been run today - #it needs to have been run at least once on new installs! - timestamp=$(stat -c %Y ${PKG_CACHE}) - timestampAsDate=$(date -d @"${timestamp}" "+%b %e") - today=$(date "+%b %e") + #Check to see if apt-get update has already been run today + #it needs to have been run at least once on new installs! + timestamp=$(stat -c %Y ${PKG_CACHE}) + timestampAsDate=$(date -d @"${timestamp}" "+%b %e") + today=$(date "+%b %e") - if [ ! "${today}" == "${timestampAsDate}" ]; then - #update package lists - echo ":::" - echo -n "::: ${PKG_MANAGER} update has not been run today. Running now..." - ${UPDATE_PKG_CACHE} &> /dev/null & spinner $! - echo " done!" - fi + if [ ! "${today}" == "${timestampAsDate}" ]; then + #update package lists + echo ":::" + echo -n "::: ${PKG_MANAGER} update has not been run today. Running now..." + ${UPDATE_PKG_CACHE} &> /dev/null + echo " done!" + fi } notify_package_updates_available() { # Let user know if they have outdated packages on their system and # advise them to run a package update at soonest possible. - echo ":::" - echo -n "::: Checking ${PKG_MANAGER} for upgraded packages...." - updatesToInstall=$(eval "${PKG_COUNT}") - echo " done!" - echo ":::" - if [[ ${updatesToInstall} -eq "0" ]]; then - echo "::: Your system is up to date! Continuing with Pi-hole installation..." - else - echo "::: There are ${updatesToInstall} updates available for your system!" - echo "::: We recommend you run '${PKG_UPDATE}' after installing Pi-Hole! " - echo ":::" - fi + echo ":::" + echo -n "::: Checking ${PKG_MANAGER} for upgraded packages...." + updatesToInstall=$(eval "${PKG_COUNT}") + echo " done!" + echo ":::" + if [[ ${updatesToInstall} -eq "0" ]]; then + echo "::: Your system is up to date! Continuing with Pi-hole installation..." + else + echo "::: There are ${updatesToInstall} updates available for your system!" + echo "::: We recommend you update your OS after installing Pi-Hole! " + echo ":::" + fi } install_dependent_packages() { - # Install packages passed in via argument array - # No spinner - conflicts with set -e - declare -a argArray1=("${!1}") + # Install packages passed in via argument array + # No spinner - conflicts with set -e + declare -a argArray1=("${!1}") - for i in "${argArray1[@]}"; do - echo -n "::: Checking for $i..." - package_check_install "${i}" &> /dev/null - echo " installed!" - 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!" + if command -v debconf-apt-progress &> /dev/null; then + debconf-apt-progress -- ${PKG_INSTALL} "${argArray1[@]}" + else + for i in "${argArray1[@]}"; do + echo -n "::: Checking for $i..." + package_check_install "${i}" &> /dev/null + echo " installed!" + done + fi } CreateLogFile() { - # Create logfiles if necessary - echo ":::" - echo -n "::: Creating log file and changing owner to dnsmasq..." - if [ ! -f /var/log/pihole.log ]; then - touch /var/log/pihole.log - chmod 644 /var/log/pihole.log - chown "${DNSMASQ_USER}":root /var/log/pihole.log - echo " done!" - else - echo " already exists!" - fi + # Create logfiles if necessary + echo ":::" + echo -n "::: Creating log file and changing owner to dnsmasq..." + if [ ! -f /var/log/pihole.log ]; then + touch /var/log/pihole.log + chmod 644 /var/log/pihole.log + chown "${DNSMASQ_USER}":root /var/log/pihole.log + echo " done!" + else + echo " already exists!" + fi } installPiholeWeb() { - # Install the web interface - echo ":::" - echo -n "::: Installing pihole custom index page..." - if [ -d "/var/www/html/pihole" ]; then - echo " Existing page detected, not overwriting" - else - mkdir /var/www/html/pihole - if [ -f /var/www/html/index.lighttpd.html ]; then - mv /var/www/html/index.lighttpd.html /var/www/html/index.lighttpd.orig - else - printf "\n:::\tNo default index.lighttpd.html file found... not backing up" - fi - cp /etc/.pihole/advanced/index.* /var/www/html/pihole/. - echo " done!" - fi - # Install Sudoer file - echo -n "::: Installing sudoer file..." - mkdir -p /etc/sudoers.d/ - cp /etc/.pihole/advanced/pihole.sudo /etc/sudoers.d/pihole - chmod 0440 /etc/sudoers.d/pihole - echo " done!" + # Install the web interface + echo ":::" + echo "::: Installing pihole custom index page..." + if [ -d "/var/www/html/pihole" ]; then + if [ -f "/var/www/html/pihole/index.html" ]; then + echo "::: Existing index.html detected, not overwriting" + else + echo -n "::: index.html missing, replacing... " + cp /etc/.pihole/advanced/index.html /var/www/html/pihole/ + echo " done!" + fi + + if [ -f "/var/www/html/pihole/index.js" ]; then + echo "::: Existing index.js detected, not overwriting" + else + echo -n "::: index.js missing, replacing... " + cp /etc/.pihole/advanced/index.js /var/www/html/pihole/ + echo " done!" + fi + + else + mkdir /var/www/html/pihole + if [ -f /var/www/html/index.lighttpd.html ]; then + mv /var/www/html/index.lighttpd.html /var/www/html/index.lighttpd.orig + else + printf "\n:::\tNo default index.lighttpd.html file found... not backing up" + fi + cp /etc/.pihole/advanced/index.* /var/www/html/pihole/. + echo " done!" + fi + # Install Sudoer file + echo ":::" + echo -n "::: Installing sudoer file..." + mkdir -p /etc/sudoers.d/ + cp /etc/.pihole/advanced/pihole.sudo /etc/sudoers.d/pihole + chmod 0440 /etc/sudoers.d/pihole + echo " done!" } installCron() { - # Install the cron job - echo ":::" - echo -n "::: Installing latest Cron script..." - cp /etc/.pihole/advanced/pihole.cron /etc/cron.d/pihole - echo " done!" + # Install the cron job + echo ":::" + echo -n "::: Installing latest Cron script..." + cp /etc/.pihole/advanced/pihole.cron /etc/cron.d/pihole + echo " done!" } runGravity() { - # Run gravity.sh to build blacklists - echo ":::" - echo "::: Preparing to run gravity.sh to refresh hosts..." - if ls /etc/pihole/list* 1> /dev/null 2>&1; then - echo "::: Cleaning up previous install (preserving whitelist/blacklist)" - rm /etc/pihole/list.* - fi - echo "::: Running gravity.sh" - /opt/pihole/gravity.sh + # Run gravity.sh to build blacklists + echo ":::" + echo "::: Preparing to run gravity.sh to refresh hosts..." + if ls /etc/pihole/list* 1> /dev/null 2>&1; then + echo "::: Cleaning up previous install (preserving whitelist/blacklist)" + rm /etc/pihole/list.* + fi + echo "::: Running gravity.sh" + /opt/pihole/gravity.sh } create_pihole_user() { - # Check if user pihole exists and create if not - echo "::: Checking if user 'pihole' exists..." - id -u pihole &> /dev/null && echo "::: User 'pihole' already exists" || (echo "::: User 'pihole' doesn't exist. Creating..." && useradd -r -s /usr/sbin/nologin pihole) + # Check if user pihole exists and create if not + echo "::: Checking if user 'pihole' exists..." + id -u pihole &> /dev/null && echo "::: User 'pihole' already exists" || (echo "::: User 'pihole' doesn't exist. Creating..." && useradd -r -s /usr/sbin/nologin pihole) } configureFirewall() { - # Allow HTTP and DNS traffic - if [ -x "$(command -v firewall-cmd)" ]; then - firewall-cmd --state &> /dev/null && ( echo "::: Configuring firewalld for httpd and dnsmasq.." && firewall-cmd --permanent --add-port=80/tcp && firewall-cmd --permanent --add-port=53/tcp \ - && firewall-cmd --permanent --add-port=53/udp && firewall-cmd --reload) || echo "::: FirewallD not enabled" - elif [ -x "$(command -v iptables)" ]; then - echo "::: Configuring iptables for httpd and dnsmasq.." - iptables -A INPUT -p tcp -m tcp --dport 80 -j ACCEPT - iptables -A INPUT -p tcp -m tcp --dport 53 -j ACCEPT - iptables -A INPUT -p udp -m udp --dport 53 -j ACCEPT - else - echo "::: No firewall detected.. skipping firewall configuration." - fi + # Allow HTTP and DNS traffic + if [ -x "$(command -v firewall-cmd)" ]; then + firewall-cmd --state &> /dev/null && ( echo "::: Configuring firewalld for httpd and dnsmasq.." && firewall-cmd --permanent --add-port=80/tcp && firewall-cmd --permanent --add-port=53/tcp \ + && firewall-cmd --permanent --add-port=53/udp && firewall-cmd --reload) || echo "::: FirewallD not enabled" + elif [ -x "$(command -v iptables)" ]; then + echo "::: Configuring iptables for httpd and dnsmasq.." + iptables -A INPUT -p tcp -m tcp --dport 80 -j ACCEPT + iptables -A INPUT -p tcp -m tcp --dport 53 -j ACCEPT + iptables -A INPUT -p udp -m udp --dport 53 -j ACCEPT + else + echo "::: No firewall detected.. skipping firewall configuration." + fi } finalExports() { - #If it already exists, lets overwrite it with the new values. - if [[ -f ${setupVars} ]]; then - rm ${setupVars} - fi + # Update variables in setupVars.conf file + if [ -e "${setupVars}" ]; then + sed -i.update.bak '/PIHOLE_INTERFACE/d;/IPV4_ADDRESS/d;/IPV6_ADDRESS/d;/PIHOLE_DNS_1/d;/PIHOLE_DNS_2/d;/QUERY_LOGGING/d;' "${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}" } installPihole() { - # Install base files and web interface - create_pihole_user - if [ ! -d "/var/www/html" ]; then - mkdir -p /var/www/html - fi - chown ${LIGHTTPD_USER}:${LIGHTTPD_GROUP} /var/www/html - chmod 775 /var/www/html - usermod -a -G ${LIGHTTPD_GROUP} pihole - if [ -x "$(command -v lighty-enable-mod)" ]; then - lighty-enable-mod fastcgi fastcgi-php > /dev/null || true - else - printf "\n:::\tWarning: 'lighty-enable-mod' utility not found. Please ensure fastcgi is enabled if you experience issues.\n" - fi - installScripts - installConfigs - CreateLogFile - configureSelinux - installPiholeWeb - installCron - configureFirewall - finalExports - runGravity + # Install base files and web interface + create_pihole_user + if [ ! -d "/var/www/html" ]; then + mkdir -p /var/www/html + fi + chown ${LIGHTTPD_USER}:${LIGHTTPD_GROUP} /var/www/html + chmod 775 /var/www/html + usermod -a -G ${LIGHTTPD_GROUP} pihole + if [ -x "$(command -v lighty-enable-mod)" ]; then + lighty-enable-mod fastcgi fastcgi-php > /dev/null || true + else + printf "\n:::\tWarning: 'lighty-enable-mod' utility not found. Please ensure fastcgi is enabled if you experience issues.\n" + fi + installScripts + installConfigs + CreateLogFile + configureSelinux + installPiholeWeb + installCron + configureFirewall + finalExports + runGravity +} + +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/piholeInterface/PIHOLE_INTERFACE/g' ${setupVars} + sed -i 's/IPv4_address/IPV4_ADDRESS/g' ${setupVars} + sed -i 's/IPv4addr/IPV4_ADDRESS/g' ${setupVars} + sed -i 's/IPv6_address/IPV6_ADDRESS/g' ${setupVars} + sed -i 's/piholeIPv6/IPV6_ADDRESS/g' ${setupVars} + sed -i 's/piholeDNS1/PIHOLE_DNS_1/g' ${setupVars} + sed -i 's/piholeDNS2/PIHOLE_DNS_2/g' ${setupVars} + } updatePihole() { - # 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 + 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() { - if [ -x "$(command -v getenforce)" ]; then - printf "\n::: SELinux Detected\n" - printf ":::\tChecking for SELinux policy development packages..." - package_check_install "selinux-policy-devel" > /dev/null - echo " installed!" - printf ":::\tEnabling httpd server side includes (SSI).. " - setsebool -P httpd_ssi_exec on &> /dev/null && echo "Success" || echo "SELinux not enabled" - printf "\n:::\tCompiling Pi-Hole SELinux policy..\n" - if ! [ -x "$(command -v systemctl)" ]; then - sed -i.bak '/systemd/d' /etc/.pihole/advanced/selinux/pihole.te - fi - checkmodule -M -m -o /etc/pihole/pihole.mod /etc/.pihole/advanced/selinux/pihole.te - semodule_package -o /etc/pihole/pihole.pp -m /etc/pihole/pihole.mod - semodule -i /etc/pihole/pihole.pp - rm -f /etc/pihole/pihole.mod - semodule -l | grep pihole &> /dev/null && echo "::: Installed Pi-Hole SELinux policy" || echo "::: Warning: Pi-Hole SELinux policy did not install." - fi + if [ -x "$(command -v getenforce)" ]; then + printf "\n::: SELinux Detected\n" + printf ":::\tChecking for SELinux policy development packages..." + package_check_install "selinux-policy-devel" > /dev/null + echo " installed!" + printf ":::\tEnabling httpd server side includes (SSI).. " + setsebool -P httpd_ssi_exec on &> /dev/null && echo "Success" || echo "SELinux not enabled" + printf "\n:::\tCompiling Pi-Hole SELinux policy..\n" + if ! [ -x "$(command -v systemctl)" ]; then + sed -i.bak '/systemd/d' /etc/.pihole/advanced/selinux/pihole.te + fi + checkmodule -M -m -o /etc/pihole/pihole.mod /etc/.pihole/advanced/selinux/pihole.te + semodule_package -o /etc/pihole/pihole.pp -m /etc/pihole/pihole.mod + semodule -i /etc/pihole/pihole.pp + rm -f /etc/pihole/pihole.mod + semodule -l | grep pihole &> /dev/null && echo "::: Installed Pi-Hole SELinux policy" || echo "::: Warning: Pi-Hole SELinux policy did not install." + fi } 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: + if (( ${#1} > 0 )) ; then + # 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 +The currently set password is ${1}" ${r} ${c} + else + 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} + +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} + fi } update_dialogs() { - # reconfigure - if [ "${reconfigure}" = true ]; then - opt1a="Repair" - opt1b="This will retain existing settings" - strAdd="You will remain on the same version" - else - opt1a="Update" - opt1b="This will retain existing settings." - strAdd="You will be updated to the latest version." - fi - opt2a="Reconfigure" - opt2b="This will allow you to enter new settings" + # reconfigure + if [ "${reconfigure}" = true ]; then + opt1a="Repair" + opt1b="This will retain existing settings" + strAdd="You will remain on the same version" + else + opt1a="Update" + opt1b="This will retain existing settings." + strAdd="You will be updated to the latest version." + fi + opt2a="Reconfigure" + opt2b="This will allow you to enter new settings" - 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 \ - "${opt1a}" "${opt1b}" \ - "${opt2a}" "${opt2b}" 3>&2 2>&1 1>&3) - - if [[ $? = 0 ]];then - case ${UpdateCmd} in - ${opt1a}) - echo "::: ${opt1a} option selected." - useUpdateVars=true - ;; - ${opt2a}) - echo "::: ${opt2a} option selected" - useUpdateVars=false - ;; - esac - else - echo "::: Cancel selected. Exiting..." - exit 1 - fi + 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 \ + "${opt1a}" "${opt1b}" \ + "${opt2a}" "${opt2b}" 3>&2 2>&1 1>&3) + if [[ $? = 0 ]];then + case ${UpdateCmd} in + ${opt1a}) + echo "::: ${opt1a} option selected." + useUpdateVars=true + ;; + ${opt2a}) + echo "::: ${opt2a} option selected" + useUpdateVars=false + ;; + esac + else + echo "::: Cancel selected. Exiting..." + exit 1 + fi } main() { # Check arguments for the undocumented flags - for var in "$@"; do - case "$var" in - "--reconfigure" ) reconfigure=true;; - "--i_do_not_follow_recommendations" ) skipSpaceCheck=false;; - "--unattended" ) runUnattended=true;; - esac - done + for var in "$@"; do + case "$var" in + "--reconfigure" ) reconfigure=true;; + "--i_do_not_follow_recommendations" ) skipSpaceCheck=false;; + "--unattended" ) runUnattended=true;; + esac + done - if [[ -f ${setupVars} ]]; then - if [[ "${runUnattended}" == true ]]; then - echo "::: --unattended passed to install script, no whiptail dialogs will be displayed" - useUpdateVars=true - else - update_dialogs - fi - fi + if [[ -f ${setupVars} ]]; then + if [[ "${runUnattended}" == true ]]; then + echo "::: --unattended passed to install script, no whiptail dialogs will be displayed" + useUpdateVars=true + else + update_dialogs + fi + fi - # Start the installer - # Verify there is enough disk space for the install - if [[ "${skipSpaceCheck}" == true ]]; then - echo "::: --i_do_not_follow_recommendations passed to script, skipping free disk space verification!" - else - verifyFreeDiskSpace - fi + # Start the installer + # Verify there is enough disk space for the install + if [[ "${skipSpaceCheck}" == true ]]; then + echo "::: --i_do_not_follow_recommendations passed to script, skipping free disk space verification!" + else + verifyFreeDiskSpace + fi - # Update package cache - update_pacakge_cache + # Update package cache + update_pacakge_cache - # Notify user of package availability - notify_package_updates_available + # Notify user of package availability + notify_package_updates_available - # Install packages used by this installation script - install_dependent_packages INSTALLER_DEPS[@] + # 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 ${PI_HOLE_LOCAL_REPO} ${piholeGitUrl} + getGitFiles ${webInterfaceDir} ${webInterfaceGitUrl} + fi - 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 ${webInterfaceDir} ${webInterfaceGitUrl} - fi + if [[ ${useUpdateVars} == false ]]; then + # Display welcome dialogs + welcomeDialogs + # Create directory for Pi-hole storage + mkdir -p /etc/pihole/ + # Stop resolver and webserver while installing proceses + stop_service dnsmasq + stop_service lighttpd + # Determine available interfaces + get_available_interfaces + # Find interfaces and let the user choose one + chooseInterface + # Let the user decide if they want to block ads over IPv4 and/or IPv6 + use4andor6 + # Decide what upstream DNS Servers to use + setDNS + # Let the user decide if they want query logging enabled... + setLogging - if [[ ${useUpdateVars} == false ]]; then - # Display welcome dialogs - welcomeDialogs - # Create directory for Pi-hole storage - mkdir -p /etc/pihole/ - # Remove legacy scripts from previous storage location - remove_legacy_scripts - # Stop resolver and webserver while installing proceses - stop_service dnsmasq - stop_service lighttpd - # Determine available interfaces - get_available_interfaces - # Find interfaces and let the user choose one - chooseInterface - # Let the user decide if they want to block ads over IPv4 and/or IPv6 - use4andor6 - # Decide what upstream DNS Servers to use - setDNS - # Install and log everything to a file - installPihole | tee ${tmpLog} - else - updatePihole | tee ${tmpLog} - fi + # Install packages used by the Pi-hole + install_dependent_packages PIHOLE_DEPS[@] - # Move the log file into /etc/pihole for storage - mv ${tmpLog} ${instalLogLoc} + # Install and log everything to a file + installPihole | tee ${tmpLog} + else + # update packages used by the Pi-hole + install_dependent_packages PIHOLE_DEPS[@] - if [[ "${useUpdateVars}" == false ]]; then - displayFinalMessage - fi + updatePihole | tee ${tmpLog} + fi - echo "::: Restarting services..." - # Start services - start_service dnsmasq - enable_service dnsmasq - start_service lighttpd - enable_service lighttpd - echo "::: done." + # Move the log file into /etc/pihole for storage + mv ${tmpLog} ${instalLogLoc} - 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 ":::" - echo "::: If you set a new IP address, you should restart the Pi." - else - echo "::: Update complete!" - fi + # Add password to web UI if there is none + pw="" + if [[ $(grep 'WEBPASSWORD' -c /etc/pihole/setupVars.conf) == 0 ]] ; then + pw=$(tr -dc _A-Z-a-z-0-9 < /dev/urandom | head -c 8) + pihole -a -p ${pw} + 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" + if [[ "${useUpdateVars}" == false ]]; then + displayFinalMessage ${pw} + fi + + echo "::: Restarting services..." + # Start services + start_service dnsmasq + enable_service dnsmasq + start_service lighttpd + enable_service lighttpd + echo "::: done." + + 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 ":::" + 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 + + if (( ${#pw} > 0 )) ; then + echo ":::" + echo "::: Note: As security measure a password has been installed for your web interface" + echo "::: The currently set password is" + echo "::: ${pw}" + echo ":::" + echo "::: You can always change it using" + echo "::: pihole -a -p new_password" + fi + + echo ":::" + echo "::: The install log is located at: /etc/pihole/install.log" } -main "$@" +if [[ "${PH_TEST}" != true ]] ; then + main "$@" +fi diff --git a/automated install/uninstall.sh b/automated install/uninstall.sh index abba88a5..2bc4dd25 100755 --- a/automated install/uninstall.sh +++ b/automated install/uninstall.sh @@ -82,7 +82,7 @@ removeAndPurge() { read -rp "::: Do you wish to remove ${i} from your system? [y/n]: " yn case ${yn} in [Yy]* ) printf ":::\tRemoving %s..." "${i}"; ${SUDO} ${PKG_REMOVE} "${i}" &> /dev/null & spinner $!; printf "done!\n"; break;; - [Nn]* ) printf ":::\tSkipping %s" "${i}\n"; break;; + [Nn]* ) printf ":::\tSkipping %s\n" "${i}"; break;; * ) printf "::: You must answer yes or no!\n";; esac done @@ -136,7 +136,7 @@ removeNoPurge() { fi echo "::: Removing config files and scripts..." - package_check ${i} > /dev/null + package_check lighttpd > /dev/null if [ $? -eq 1 ]; then ${SUDO} rm -rf /etc/lighttpd/ &> /dev/null else @@ -154,6 +154,12 @@ removeNoPurge() { ${SUDO} rm /usr/local/bin/pihole &> /dev/null ${SUDO} rm /etc/bash_completion.d/pihole &> /dev/null ${SUDO} rm /etc/sudoers.d/pihole &> /dev/null + + # If the pihole user exists, then remove + if id "pihole" >/dev/null 2>&1; then + echo "::: Removing pihole user..." + ${SUDO} userdel -r pihole + fi echo ":::" printf "::: Finished removing PiHole from your system. Sorry to see you go!\n" 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..58d0eef9 100755 --- a/gravity.sh +++ b/gravity.sh @@ -23,7 +23,7 @@ helpFunc() { ::: -f, --force Force lists to be downloaded, even if they don't need updating. ::: -h, --help Show this help dialog EOM - exit 1 + exit 0 } @@ -44,12 +44,14 @@ else fi #Remove the /* from the end of the IPv4addr. -IPv4_address=${IPv4_address%/*} +IPV4_ADDRESS=${IPV4_ADDRESS%/*} +IPV6_ADDRESS=${IPV6_ADDRESS} # Variables for various stages of downloading and formatting the list basename=pihole piholeDir=/etc/${basename} adList=${piholeDir}/gravity.list +localList=${piholeDir}/local.list justDomainsExtension=domains matterAndLight=${basename}.0.matterandlight.txt supernova=${basename}.1.supernova.txt @@ -189,7 +191,7 @@ gravity_Blacklist() { if [[ -f "${blacklistFile}" ]]; then numBlacklisted=$(wc -l < "${blacklistFile}") plural=; [[ "$numBlacklisted" != "1" ]] && plural=s - echo -n "::: BlackListing $numBlacklisted domain${plural}..." + echo -n "::: Blacklisting $numBlacklisted domain${plural}..." cat ${blacklistFile} >> ${piholeDir}/${eventHorizon} echo " done!" else @@ -240,30 +242,42 @@ gravity_unique() { 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 + echo -n "::: Formatting domains into a HOSTS file..." - # 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} + if [[ -f /etc/hostname ]]; then + hostname=$( ${localList} + # 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} - # Only IPv4 - cat ${piholeDir}/${eventHorizon} | awk -v ipv4addr="$IPv4_address" '{sub(/\r$/,""); print ipv4addr" "$0}' >> ${piholeDir}/${accretionDisc} + elif [[ -n "${IPV4_ADDRESS}" && -z "${IPV6_ADDRESS}" ]];then - elif [[ -z "${IPv4_address}" && -n "${IPv6_address}" ]];then + echo -e "${IPV4_ADDRESS} ${hostname}\n${IPV4_ADDRESS} pi.hole" > ${localList} + # Only IPv4 + cat ${piholeDir}/${eventHorizon} | awk -v ipv4addr="$IPV4_ADDRESS" '{sub(/\r$/,""); print ipv4addr" "$0}' >> ${piholeDir}/${accretionDisc} - # Only IPv6 - cat ${piholeDir}/${eventHorizon} | awk -v ipv6addr="$IPv6_address" '{sub(/\r$/,""); print ipv6addr" "$0}' >> ${piholeDir}/${accretionDisc} + elif [[ -z "${IPV4_ADDRESS}" && -n "${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 + echo -e "${IPV6_ADDRESS} ${hostname}\n${IPV6_ADDRESS} pi.hole" > ${localList} + # Only IPv6 + cat ${piholeDir}/${eventHorizon} | awk -v ipv6addr="$IPV6_ADDRESS" '{sub(/\r$/,""); print ipv6addr" "$0}' >> ${piholeDir}/${accretionDisc} + + 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 # Copy the file over as /etc/pihole/gravity.list so dnsmasq can use it cp ${piholeDir}/${accretionDisc} ${adList} + echo " done!" } # blackbody - remove any remnant files from script processes @@ -287,8 +301,14 @@ gravity_advanced() { echo -n "::: Formatting list of domains to remove comments...." #awk '($1 !~ /^#/) { if (NF>1) {print $2} else {print $1}}' ${piholeDir}/${matterAndLight} | sed -nr -e 's/\.{2,}/./g' -e '/\./p' > ${piholeDir}/${supernova} #Above line does not correctly grab domains where comment is on the same line (e.g 'addomain.com #comment') - #Add additional awk command to read all lines up to a '#', and then continue as we were - cat ${piholeDir}/${matterAndLight} | awk -F'#' '{print $1}' | awk '($1 !~ /^#/) { if (NF>1) {print $2} else {print $1}}' | sed -nr -e 's/\.{2,}/./g' -e '/\./p' > ${piholeDir}/${supernova} + #Awk -F splits on given IFS, we grab the right hand side (chops trailing #coments and /'s to grab the domain only. + #Last awk command takes non-commented lines and if they have 2 fields, take the left field (the domain) and leave + #+ the right (IP address), otherwise grab the single field. + cat ${piholeDir}/${matterAndLight} | \ + awk -F '#' '{print $1}' | \ + awk -F '/' '{print $1}' | \ + awk '($1 !~ /^#/) { if (NF>1) {print $2} else {print $1}}' | \ + sed -nr -e 's/\.{2,}/./g' -e '/\./p' > ${piholeDir}/${supernova} echo " done!" numberOf=$(wc -l < ${piholeDir}/${supernova}) @@ -306,7 +326,7 @@ gravity_reload() { # Reload hosts file echo ":::" - echo "::: Refresh lists in dnsmasq..." + echo -n "::: Refresh lists in dnsmasq..." #ensure /etc/dnsmasq.d/01-pihole.conf is pointing at the correct list! #First escape forward slashes in the path: @@ -314,7 +334,8 @@ gravity_reload() { #Now replace the line in dnsmasq file # sed -i "s/^addn-hosts.*/addn-hosts=$adList/" /etc/dnsmasq.d/01-pihole.conf - pihole restartdns + pihole restartdns + echo " done!" } for var in "$@"; do diff --git a/pihole b/pihole index 4b197d12..5509df47 100755 --- a/pihole +++ b/pihole @@ -10,87 +10,107 @@ # 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 +webpageFunc() { + /opt/pihole/webpage.sh "$@" + exit 0 +} + 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 +} + +scanList(){ + domain="${1}" + list="${2}" + method="${3}" + if [[ ${method} == "-exact" ]] ; then + grep -E "(^|\s)${domain}($|\s)" "${list}" + else + grep "${domain}" "${list}" + fi } 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}" + method="${3}" + lists=( /etc/pihole/list.* /etc/pihole/blacklist.txt) + for list in ${lists[@]}; do + result=$(scanList ${domain} ${list} ${method}) + # Remove empty lines before couting number of results + count=$(sed '/^\s*$/d' <<< "$result" | wc -l) + echo "::: ${list} (${count} results)" + if [[ ${count} > 0 ]]; then + echo "${result}" + 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() { @@ -115,8 +135,26 @@ restartDNS() { piholeEnable() { if [[ "${1}" == "0" ]] ; then #Disable Pihole - sed -i 's/^addn-hosts/#addn-hosts/' /etc/dnsmasq.d/01-pihole.conf + sed -i 's/^addn-hosts=\/etc\/pihole\/gravity.list/#addn-hosts=\/etc\/pihole\/gravity.list/' /etc/dnsmasq.d/01-pihole.conf echo "::: Blocking has been disabled!" + if [[ $# > 1 ]] ; then + if [[ ${2} == *"s"* ]] ; then + tt=${2%"s"} + echo "::: Blocking will be re-enabled in ${tt} seconds" + nohup bash -c "sleep ${tt}; pihole enable" /dev/null & + elif [[ ${2} == *"m"* ]] ; then + tt=${2%"m"} + echo "::: Blocking will be re-enabled in ${tt} minutes" + tt=$((${tt}*60)) + nohup bash -c "sleep ${tt}; pihole enable" /dev/null & + else + echo "::: Unknown format for delayed reactivation of the blocking!" + echo "::: Example:" + echo "::: pihole disable 5s - will disable blocking for 5 seconds" + echo "::: pihole disable 7m - will disable blocking for 7 minutes" + echo "::: Blocking will not automatically be re-enabled!" + fi + fi else #Enable pihole echo "::: Blocking has been enabled!" @@ -125,15 +163,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; @@ -153,57 +212,72 @@ piholeStatus() { fi } +tailFunc() { + echo "Press Ctrl-C to exit" + tail -F /var/log/pihole.log + exit 0 +} helpFunc() { cat << EOM ::: Control all PiHole specific functions! ::: ::: Usage: pihole [options] -::: Add -h after -w (whitelist), -b (blacklist), or -c (chronometer) for more information on usage +::: Add -h after -w (whitelist), -b (blacklist), -c (chronometer), or -a (admin) for more information on usage ::: ::: Options: ::: -w, whitelist Whitelist domains ::: -b, blacklist Blacklist domains ::: -d, debug Start a debugging session if having trouble ::: -f, flush Flush the pihole.log file +::: -t, tail Output the last lines of the pihole.log file. Lines are appended as the file grows ::: -up, updatePihole Update Pi-hole +::: -r, reconfigure Reconfigure or Repair Pi-hole ::: -g, updateGravity Update the list of ad-serving domains ::: -s, setupLCD Automatically configures the Pi to use the 2.8 LCD screen to display stats on it ::: -c, chronometer Calculates stats and displays to an LCD ::: -h, help Show this help dialog ::: -v, version Show current versions ::: -q, query Query the adlists for a specific domain +::: Use pihole -q domain -exact if you want to see exact matches only +::: -l, logging Enable or Disable logging (pass 'on' or 'off') +::: -a, admin Admin webpage options ::: 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 +::: Blocking can also be disabled only temporarily, e.g., +::: pihole disable 5m - will disable blocking for 5 minutes ::: restartdns Restart dnsmasq EOM - exit 1 + exit 0 } 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 $2;; + "status" ) piholeStatus "$2";; + "restartdns" ) restartDNS;; + "-a" | "admin" ) webpageFunc "$@";; + "-t" | "tail" ) tailFunc;; + * ) 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..00543b67 --- /dev/null +++ b/test/centos.Dockerfile @@ -0,0 +1,16 @@ +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/* + +ENV PH_TEST true + +#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..5960cc24 --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,61 @@ +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. + Also provides bash as the default run function shell ''' + def run_bash(self, command, *args, **kwargs): + cmd = self.get_command(command, *args) + if self.user is not None: + out = self.run_local( + "docker exec -u %s %s /bin/bash -c %s", + self.user, self.name, cmd) + else: + out = self.run_local( + "docker exec %s /bin/bash -c %s", self.name, cmd) + out.command = self.encode(cmd) + return out + + funcType = type(Docker.run) + Docker.run = funcType(run_bash, Docker, testinfra.backend.docker.DockerBackend) + 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..931c0ba7 --- /dev/null +++ b/test/debian.Dockerfile @@ -0,0 +1,16 @@ +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/* + +ENV PH_TEST true + +#sed '/# Start the installer/Q' /opt/pihole/basic-install.sh > /opt/pihole/stub_basic-install.sh && \ 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..ee3beeee --- /dev/null +++ b/test/test_automated_install.py @@ -0,0 +1,102 @@ +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. + This confirms the sourced variables are in scope between functions ''' + 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('''\ + set -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('''\ + set -e + echo start + TERM=xterm + 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 test_configureFirewall_firewalld_no_errors(Pihole): + ''' confirms firewalld rules are applied when appopriate ''' + mock_command('firewall-cmd', '0', Pihole) + configureFirewall = Pihole.run(''' + source /opt/pihole/basic-install.sh + configureFirewall + ''') + expected_stdout = '::: Configuring firewalld for httpd and dnsmasq.' + assert expected_stdout in configureFirewall.stdout + firewall_calls = Pihole.run('cat /var/log/firewall-cmd').stdout + assert 'firewall-cmd --state' in firewall_calls + assert 'firewall-cmd --permanent --add-port=80/tcp' in firewall_calls + assert 'firewall-cmd --permanent --add-port=53/tcp' in firewall_calls + assert 'firewall-cmd --permanent --add-port=53/udp' in firewall_calls + assert 'firewall-cmd --reload' in firewall_calls + + +# Helper functions +def mock_command(script, result, container): + ''' Allows for setup of commands we don't really want to have to run for real in unit tests ''' + ''' TODO: support array of results that enable the results to change over multiple executions of a command ''' + full_script_path = '/usr/local/bin/{}'.format(script) + mock_script = dedent('''\ + #!/bin/bash -e + echo "\$0 \$@" >> /var/log/{script} + exit {retcode} + '''.format(script=script, retcode=result)) + container.run(''' + cat < {script}\n{content}\nEOF + chmod +x {script} + '''.format(script=full_script_path, content=mock_script)) + +def run_script(Pihole, script): + result = Pihole.run(script) + assert result.rc == 0 + return result diff --git a/test/test_shellcheck.py b/test/test_shellcheck.py new file mode 100644 index 00000000..fa342027 --- /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 . -type f \( -name 'update.sh' -o -name 'piholeDebug.sh' \) | while read file; do shellcheck \"$file\"; done;" + results = run_local(shellcheck) + print results.stdout + assert '' == results.stdout