Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/pi-hole/pi-hole.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJacob Salmela <jacob.salmela@pi-hole.net>2017-12-07 06:37:10 +0300
committerGitHub <noreply@github.com>2017-12-07 06:37:10 +0300
commit5ba413569ea8a4220c7e3bd2fa8c28b33b9e8492 (patch)
tree9cfb8552b9e040b9784b8b086a295da1e398075f
parentb0eceddcec2054273d7e1add34d1f806b0c81693 (diff)
parentb3e969f000167e5ce975fbd1465f2d4f3e184aec (diff)
Merge pull request #1776 from pi-hole/release/3.2v3.2
Pi-hole core v3.2
-rw-r--r--.github/ISSUE_TEMPLATE.md36
-rw-r--r--.github/PULL_REQUEST_TEMPLATE.md37
-rw-r--r--.gitignore1
-rw-r--r--README.md301
-rw-r--r--advanced/01-pihole.conf4
-rw-r--r--advanced/Scripts/COL_TABLE49
-rwxr-xr-xadvanced/Scripts/chronometer.sh494
-rwxr-xr-xadvanced/Scripts/list.sh160
-rw-r--r--advanced/Scripts/piholeCheckout.sh287
-rwxr-xr-xadvanced/Scripts/piholeDebug.sh1468
-rwxr-xr-xadvanced/Scripts/piholeLogFlush.sh7
-rwxr-xr-xadvanced/Scripts/update.sh116
-rwxr-xr-xadvanced/Scripts/updatecheck.sh59
-rwxr-xr-xadvanced/Scripts/webpage.sh103
-rw-r--r--advanced/blockingpage.css505
-rw-r--r--advanced/index.js1
-rw-r--r--advanced/index.php507
-rw-r--r--advanced/lighttpd.conf.debian36
-rw-r--r--advanced/lighttpd.conf.fedora35
-rw-r--r--advanced/pihole.cron7
-rwxr-xr-xautomated install/basic-install.sh1534
-rwxr-xr-xautomated install/uninstall.sh180
-rwxr-xr-xgravity.sh966
-rwxr-xr-xpihole448
-rw-r--r--test/test_automated_install.py158
25 files changed, 5029 insertions, 2470 deletions
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 23e67795..4a9c585a 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -1,33 +1,37 @@
-**In raising this issue, I confirm the following (please check boxes, eg [X]) Failure to fill the template will close your issue:**
+**In raising this issue, I confirm the following:** `{please fill the checkboxes, e.g: [X]}`
- [] I have read and understood the [contributors guide](https://github.com/pi-hole/pi-hole/blob/master/CONTRIBUTING.md).
-- [] The issue I am reporting can be *replicated*
+- [] The issue I am reporting can be *replicated*.
- [] The issue I am reporting isn't a duplicate (see [FAQs](https://github.com/pi-hole/pi-hole/wiki/FAQs), [closed issues](https://github.com/pi-hole/pi-hole/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), and [open issues](https://github.com/pi-hole/pi-hole/issues)).
-**How familiar are you with the codebase?:**
+**How familiar are you with the the source code relevant to this issue?:**
-_{replace this text with a number from 1 to 10, with 1 being not familiar, and 10 being very familiar}_
+`{Replace this with a number from 1 to 10. 1 being not familiar, and 10 being very familiar}`
---
-**[BUG REPORT | OTHER]:**
+**Expected behaviour:**
-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.
+`{A detailed description of what you expect to see}`
-**[BUG | ISSUE] Expected Behaviour:**
+**Actual behaviour:**
+`{A detailed description and/or screenshots of what you do see}`
-**[BUG | ISSUE] Actual Behaviour:**
+**Steps to reproduce:**
+`{Detailed steps of how we can reproduce this}`
-**[BUG | ISSUE] Steps to reproduce:**
+**Debug token provided by [uploading `pihole -d` log](https://discourse.pi-hole.net/t/the-pihole-command-with-examples/738#debug):**
--
--
--
--
+`{Alphanumeric token}`
-**(Optional) Debug token generated by `pihole -d`:**
+**Troubleshooting undertaken, and/or other relevant information:**
-`<token>`
+`{Steps of what you have done to fix this}`
-_This template was created based on the work of [`udemy-dl`](https://github.com/nishad/udemy-dl/blob/master/LICENSE)._
+> * `{Please delete this quoted section when opening your issue}`
+> * You must follow the template instructions. Failure to do so will result in your issue being closed.
+> * Please [submit any feature requests here](https://discourse.pi-hole.net/c/feature-requests), so it is votable and trackable by the community.
+> * Please respect that Pi-hole is developed by volunteers, who can only reply in their spare time.
+> * Detail helps us understand and resolve an issue quicker, but please ensure it's relevant.
+> * _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 424bbc78..96ce4ba5 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,19 +1,32 @@
-**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 fill any appropriate checkboxes, e.g: [X]}`
-***Please submit all pull requests against the `development` branch. Failure to do so will delay or deny your request***
+`{Please ensure that your pull request is for the 'development' branch!}`
-- [] 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.
-- [] I have considered, and confirmed that this submission will be valuable to others.
-- [] I accept that this submission may not be used, and the pull request closed at the will of the maintainer.
-- [] I give this submission freely, and claim no ownership to its content.
+- [] I have read and understood the [contributors guide](https://github.com/pi-hole/pi-hole/blob/master/CONTRIBUTING.md), as well as this entire template.
+- [] I have made only one major change in my proposed changes.
+- [] I have commented my proposed changes within the code.
+- [] I have tested my proposed changes, and have included unit tests where possible.
+- [] I am willing to help maintain this change if there are issues with it later.
+- [] I give this submission freely and claim no ownership.
+- [] It is compatible with the [EUPL 1.2 license](https://opensource.org/licenses/EUPL-1.1)
+- [] I have squashed any insignificant commits. ([`git rebase`](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html))
+- [] I have Signed Off all commits. ([`git commit --signoff`](https://git-scm.com/docs/git-commit#git-commit---signoff))
-**How familiar are you with the codebase?:**
+---
-_{replace this text with a number from 1 to 10, with 1 being not familiar, and 10 being very familiar}_
+**What does this PR aim to accomplish?:**
----
-_{replace this line with your pull request content}_
+`{A detailed description, screenshots (if necessary), as well as links to any relevant GitHub issues}`
+
+**How does this PR accomplish the above?:**
+
+`{A detailed description (such as a changelog) and screenshots (if necessary) of the implemented fix}`
+
+**What documentation changes (if any) are needed to support this PR?:**
+`{A detailed list of any necessary changes}`
-_This template was created based on the work of [`udemy-dl`](https://github.com/nishad/udemy-dl/blob/master/LICENSE)._
+> * `{Please delete this quoted section when opening your pull request}`
+> * You must follow the template instructions. Failure to do so will result in your issue being closed.
+> * Please respect that Pi-hole is developed by volunteers, who can only reply in their spare time.
+> * Detail helps us understand an issue quicker, but please ensure it's relevant.
diff --git a/.gitignore b/.gitignore
index 91014dcd..0e0d4b99 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,3 @@
*.swp
__pycache__
.cache
-.pullapprove.yml
diff --git a/README.md b/README.md
index 6f8813fa..f6d15f43 100644
--- a/README.md
+++ b/README.md
@@ -1,176 +1,217 @@
<p align="center">
-<a href=https://www.bountysource.com/trackers/3011939-pi-hole-pi-hole?utm_source=3011939&utm_medium=shield&utm_campaign=TRACKER_BADGE><img src="https://www.bountysource.com/badge/tracker?tracker_id=3011939"></a>
-<a href="https://www.codacy.com/app/Pi-hole/pi-hole?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=pi-hole/pi-hole&amp;utm_campaign=Badge_Grade"><img src="https://api.codacy.com/project/badge/Grade/c558a0f8d7124c99b02b84f0f5564238"/></a>
-<a href=https://travis-ci.org/pi-hole/pi-hole><img src="https://travis-ci.org/pi-hole/pi-hole.svg?branch=development"></a>
+<a href="https://pi-hole.net"><img src="https://pi-hole.github.io/graphics/Vortex/Vortex_with_text.png" width="150" height="255" alt="Pi-hole"></a><br/>
+<b>Network-wide ad blocking via your own Linux hardware</b><br/>
</p>
-<p align="center">
-<a href=https://discourse.pi-hole.net><img src="https://assets.pi-hole.net/static/Vortex_with_text_and_TM.png" width=210></a>
-</p>
-
-## The multi-platform, network-wide ad blocker
+The Pi-hole is a [DNS sinkhole](https://en.wikipedia.org/wiki/DNS_Sinkhole) that protects your devices from unwanted content, without installing any client-side software.
-Block ads for **all** your devices _without_ the need to install client-side software. The Pi-hole™ blocks ads at the DNS-level, so all your devices are protected.
+- **Easy-to-install**: our versatile installer walks you through the process, and [takes less than ten minutes](https://www.youtube.com/watch?v=vKWjx1AQYgs)
+- **Resolute**: content is blocked in _non-browser locations_, such as ad-laden mobile apps and smart TVs
+- **Responsive**: seamlessly speeds up the feel of everyday browsing by caching DNS queries
+- **Lightweight**: runs smoothly with [minimal hardware and software requirements](https://discourse.pi-hole.net/t/hardware-software-requirements/273)
+- **Robust**: a command line interface that is quality assured for interoperability
+- **Insightful**: a beautiful responsive Web Interface dashboard to view and control your Pi-hole
+- **Versatile**: can optionally function as a [DHCP server](https://discourse.pi-hole.net/t/how-do-i-use-pi-holes-built-in-dhcp-server-and-why-would-i-want-to/3026), ensuring *all* your devices are protected automatically
+- **Scalable**: [capable of handling hundreds of millions of queries](https://pi-hole.net/2017/05/24/how-much-traffic-can-pi-hole-handle/) when installed on server-grade hardware
+- **Modern**: blocks ads over both IPv4 and IPv6
+- **Free**: open source software which helps ensure _you_ are the sole person in control of your privacy
-- Web Browsers
-- Cell Phones
-- Smart TV's
-- Internet-connected home automation
-- Anything that communicates with the Internet
-
-<p align="center">
-<a href=http://www.digitalocean.com/?refcode=344d234950e1><img src="https://assets.pi-hole.net/static/DOHostingSlug.png"></a>
-</p>
+-----
+<a href="https://www.codacy.com/app/Pi-hole/pi-hole?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=pi-hole/pi-hole&amp;utm_campaign=Badge_Grade"><img src="https://api.codacy.com/project/badge/Grade/c558a0f8d7124c99b02b84f0f5564238" alt="Codacy Grade"/></a>
+<a href="https://travis-ci.org/pi-hole/pi-hole"><img src="https://travis-ci.org/pi-hole/pi-hole.svg?branch=development" alt="Travis Build Status"/></a>
+<a href="https://www.bountysource.com/trackers/3011939-pi-hole-pi-hole?utm_source=3011939&utm_medium=shield&utm_campaign=TRACKER_BADGE"><img src="https://www.bountysource.com/badge/tracker?tracker_id=3011939" alt="BountySource"/></a>
-## Your Support Still Matters
+## One-Step Automated Install
+Those who want to get started quickly and conveniently, may install Pi-hole using the following command:
-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.
+#### `curl -sSL https://install.pi-hole.net | bash`
-- ![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.
+## Alternative Install Methods
+[Piping to `bash` is controversial](https://pi-hole.net/2016/07/25/curling-and-piping-to-bash), as it prevents you from [reading code that is about to run](https://github.com/pi-hole/pi-hole/blob/master/automated%20install/basic-install.sh) on your system. Therefore, we provide these alternative installation methods which allow code review before installation:
+### Method 1: Clone our repository and run
```
git clone --depth 1 https://github.com/pi-hole/pi-hole.git Pi-hole
-cd Pi-hole/automated\ install/
-bash basic-install.sh
+cd "Pi-hole/automated install/"
+sudo bash basic-install.sh
```
-##### Or
-
-```bash
+### Method 2: Manually download the installer and run
+```
wget -O basic-install.sh https://install.pi-hole.net
-bash basic-install.sh
+sudo bash basic-install.sh
```
-Once installed, [configure your router to have **DHCP clients use the Pi as their DNS server**](https://discourse.pi-hole.net/t/how-do-i-configure-my-devices-to-use-pi-hole-as-their-dns-server/245) 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 Pi-hole™ as their DNS server.
+## Post-install: Make your network take advantage of Pi-hole
-## What is Pi-hole™ and how do I install it?
-<p align="center">
-<a href=https://www.youtube.com/watch?v=vKWjx1AQYgs><img src="https://assets.pi-hole.net/static/video-explainer.png"></a>
-</p>
+Once the installer has been run, you will need to [configure your router to have **DHCP clients use Pi-hole as their DNS server**](https://discourse.pi-hole.net/t/how-do-i-configure-my-devices-to-use-pi-hole-as-their-dns-server/245) which ensures that all devices connecting to your network will have content blocked without any further intervention.
+If your router does not support setting the DNS server, you can [use Pi-hole's built in DHCP server](https://discourse.pi-hole.net/t/how-do-i-use-pi-holes-built-in-dhcp-server-and-why-would-i-want-to/3026); just be sure to disable DHCP on your router first (if it has that feature available).
-## Get Help Or Connect With Us On The Web
+As a last resort, you can always manually set each device to use Pi-hole as their DNS server.
-- [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
+## Pi-hole is free, but powered by your support
+There are many reoccurring costs involved with maintaining free, open source, and privacy respecting software; expenses which [our volunteer developers](https://github.com/orgs/pi-hole/people) pitch in to cover out-of-pocket. This is just one example of how strongly we feel about our software, as well as the importance of keeping it maintained.
-The Pi-hole™ is an **advertising-aware DNS/Web server**. If an ad domain is queried, a small Web page or GIF is delivered in place of the advertisement.
+Make no mistake: **your support is absolutely vital to help keep us innovating!**
-### Gravity
+### Donations
+Sending a donation using our links below is **extremely helpful** in offsetting a portion of our monthly expenses:
-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`.
+&nbsp;<img src="https://pi-hole.github.io/graphics/Badges/paypal-badge-black.svg" width="24" height="24" alt="PP"/> <a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=3J2L3Z4DHW9UY">Donate via PayPal</a><br/>
+&nbsp;<img src="https://pi-hole.github.io/graphics/Badges/bitcoin-badge-black.svg" width="24" height="24" alt="BTC"/> Bitcoin Address: <code>1GKnevUnVaQM2pQieMyeHkpr8DXfkpfAtL</code>
+### Alternative support
+If you'd rather not donate (_which is okay!_), there are other ways you can help support us:
+- [Digital Ocean](http://www.digitalocean.com/?refcode=344d234950e1) affiliate link
+- [Vultr](http://www.vultr.com/?ref=7190426) affiliate link
+- [UNIXstickers.com](http://unixstickers.refr.cc/jacobs) affiliate link
+- [Pi-hole Swag Store](https://pi-hole.net/shop/)
+- Spreading the word about our software, and how you have benefited from it
-#### Other Operating Systems
+### Contributing via GitHub
+We welcome _everyone_ to contribute to issue reports, suggest new features, and create pull requests.
-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 web server, it should work OK. If there are other platforms you'd like supported, let us know.
+If you have something to add - anything from a typo through to a whole new feature, we're happy to check it out! Just make sure to fill out our template when submitting your request; the questions that it asks will help the volunteers quickly understand what you're aiming to achieve.
-### Web Interface
+You'll find that the [install script](https://github.com/pi-hole/pi-hole/blob/master/automated%20install/basic-install.sh) and the [debug script](https://github.com/pi-hole/pi-hole/blob/master/advanced/Scripts/piholeDebug.sh) have an abundance of comments, which will help you better understand how Pi-hole works. They're also a valuable resource to those who want to learn how to write scripts or code a program! We encourage anyone who likes to tinker to read through it, and submit a pull request for us to review.
-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:
+### Presentations about Pi-hole
+Word-of-mouth continues to help our project grow immensely, and so we are helping make this easier for people.
-`http://192.168.1.x/admin/index.php` or `http://pi.hole/admin`
+If you are going to be presenting Pi-hole at a conference, meetup or even a school project, [get in touch with us](https://pi-hole.net/2017/05/17/giving-a-presentation-on-pi-hole-contact-us-first-for-some-goodies-and-support/) so we can hook you up with free swag to hand out to your audience!
-![Pi-hole Advanced Stats Dashboard](https://assets.pi-hole.net/static/dashboard212.png)
+-----
-### Whitelist and blacklist
+## Getting in touch with us
+While we are primarily reachable on our <a href="https://discourse.pi-hole.net/">Discourse User Forum</a>, we can also be found on a variety of social media outlets. **Please be sure to check the FAQ's** before starting a new discussion, as we do not have the spare time to reply to every request for assistance.
-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
-<p align="center">
-<a href=https://github.com/pi-hole/pi-hole/wiki/Whitelisting-and-Blacklisting><img src="https://assets.pi-hole.net/static/whitelist212.png"></a>
-</p>
+<ul>
+ <li><a href="https://discourse.pi-hole.net/c/faqs">Frequently Asked Questions</a></li>
+ <li><a href="https://github.com/pi-hole/pi-hole/wiki">Pi-hole Wiki</a></li>
+ <li><a href="https://discourse.pi-hole.net/c/feature-requests?order=votes">Feature Requests</a></li>
+</ul>
+<br/>
+<ul>
+ <li><a href="https://discourse.pi-hole.net/">Discourse User Forum</a></li>
+ <li><a href="https://www.reddit.com/r/pihole/">Reddit</a></li>
+ <li><a href="https://gitter.im/pi-hole/pi-hole">Gitter</a> (Real-time chat)</li>
+ <li><a href="https://twitter.com/The_Pi_Hole">Twitter</a></li>
+ <li><a href="https://www.youtube.com/channel/UCT5kq9w0wSjogzJb81C9U0w">YouTube</a></li>
+ <li><a href="https://www.facebook.com/ThePiHole/">Facebook</a></li>
+</ul>
-### Settings
+-----
-The settings page lets you control and configure your Pi-hole™. You can do things like:
+## Breakdown of Features
+### The Command Line Interface
+The `pihole` command has all the functionality necessary to be able to fully administer the Pi-hole, without the need of the Web Interface. It's fast, user-friendly, and auditable by anyone with understanding of `bash`.
-- enable Pi-hole's built-in DHCP server
-- exclude domains from the graphs
-- configure upstream DNS servers
-- and more!
+<a href="https://pi-hole.github.io/graphics/Screenshots/blacklist-cli.gif"><img src="https://pi-hole.github.io/graphics/Screenshots/blacklist-cli.gif" alt="Pi-hole Blacklist Demo"/></a>
-![Settings page](https://assets.pi-hole.net/static/settings212.png)
+Some notable features include:
+* [Whitelisting, Blacklisting and Wildcards](https://github.com/pi-hole/pi-hole/wiki/Core-Function-Breakdown#whitelisting-blacklisting-and-wildcards)
+* [Debugging utility](https://github.com/pi-hole/pi-hole/wiki/Core-Function-Breakdown#debugger)
+* [Viewing the live log file](https://github.com/pi-hole/pi-hole/wiki/Core-Function-Breakdown#tail)
+* [Real-time Statistics via `ssh`](https://github.com/pi-hole/pi-hole/wiki/Core-Function-Breakdown#chronometer) or [your TFT LCD screen](http://www.amazon.com/exec/obidos/ASIN/B00ID39LM4/pihole09-20)
+* [Updating Ad Lists](https://github.com/pi-hole/pi-hole/wiki/Core-Function-Breakdown#gravity)
+* [Querying Ad Lists for blocked domains](https://github.com/pi-hole/pi-hole/wiki/Core-Function-Breakdown#query)
+* [Enabling and Disabling Pi-hole](https://github.com/pi-hole/pi-hole/wiki/Core-Function-Breakdown#enable--disable)
+* ... and *many* more!
-#### Built-in DHCP Server
+You can read our [Core Feature Breakdown](https://github.com/pi-hole/pi-hole/wiki/Core-Function-Breakdown), as well as read up on [example usage](https://discourse.pi-hole.net/t/the-pihole-command-with-examples/738) for more information.
-Pi-hole™ ships with a built-in DHCP server. This allows you to let your network devices use Pi-hole™ as their DNS server if your router does not let you adjust the DHCP options.
-<p align="center">
-<a href=hhttps://discourse.pi-hole.net/t/how-do-i-configure-my-devices-to-use-pi-hole-as-their-dns-server/245><img src="https://assets.pi-hole.net/static/piholedhcpserver.png"></a>
-</p>
+### The Web Interface Dashboard
+This [optional dashboard](https://github.com/pi-hole/AdminLTE) allows you to view stats, change settings, and configure your Pi-hole. It's the power of the Command Line Interface, with none of the learning curve!
-## API
+<a href="https://pi-hole.github.io/graphics/Screenshots/dashboard.png"><img src="https://pi-hole.github.io/graphics/Screenshots/dashboard.png" width="888" height="522" alt="Pi-hole Dashboard"/></a>
-A basic read-only API can be accessed at `/admin/api.php`. It returns the following JSON:
+Some notable features include:
+* Mobile friendly interface
+* Password protection
+* Detailed graphs and doughnut charts
+* Top lists of domains and clients
+* A filterable and sortable query log
+* Long Term Statistics to view data over user defined time ranges
+* The ability to easily manage and configure Pi-hole features
+* ... and all the main features of the Command Line Interface!
-``` json
-{
- "domains_being_blocked": "136708",
- "dns_queries_today": "18108",
- "ads_blocked_today": "14648",
- "ads_percentage_today": "80.89"
-}
-```
+There are several ways to [access the dashboard](https://discourse.pi-hole.net/t/how-do-i-access-pi-holes-dashboard-admin-interface/3168):
-The same output can be achieved on the CLI by running `chronometer.sh -j`
-
-## Real-time Statistics
-
-You can view [real-time stats](https://discourse.pi-hole.net/t/how-do-i-view-my-pi-holes-stats-over-ssh-or-on-an-lcd-using-chronometer/240) via `ssh` or on an [2.8" LCD screen](http://amzn.to/1P0q1Fj). This is accomplished via [`chronometer.sh`](https://github.com/pi-hole/pi-hole/blob/master/advanced/Scripts/chronometer.sh). ![Pi-hole LCD](http://i.imgur.com/nBEqycp.jpg)
-
-## Pi-hole™ Projects
-
-- [An ad blocking Magic Mirror](https://zonksec.com/blog/magic-mirror-dns-filtering/#dnssoftware)
-- [Pi-hole stats in your Mac's menu bar](https://getbitbar.com/plugins/Network/pi-hole.1m.py)
-- [Get LED alerts for each blocked ad](http://thetimmy.silvernight.org/pages/endisbutton/)
-- [Pi-hole on Ubuntu 14.04 on VirtualBox](http://hbalagtas.blogspot.com/2016/02/adblocking-with-pi-hole-and-ubuntu-1404.html)
-- [Docker Pi-hole container (x86 and ARM)](https://hub.docker.com/r/diginc/pi-hole/)
-- [Splunk: Pi-hole Visualiser](https://splunkbase.splunk.com/app/3023/)
-- [Pi-hole Chrome extension](https://chrome.google.com/webstore/detail/pi-hole-list-editor/hlnoeoejkllgkjbnnnhfolapllcnaglh) ([open source](https://github.com/packtloss/pihole-extension))
-- [Go Bananas for CHiP-hole ad blocking](https://www.hackster.io/jacobsalmela/chip-hole-network-wide-ad-blocker-98e037)
-- [Sky-Hole](http://dlaa.me/blog/post/skyhole)
-- [Pi-hole in the Cloud!](http://blog.codybunch.com/2015/07/28/Pi-Hole-in-the-cloud/)
-- [unRaid-hole](https://github.com/spants/unraidtemplates/blob/master/Spants/unRaid-hole.xml#L13)--[Repo and more info](http://lime-technology.com/forum/index.php?PHPSESSID=c0eae3e5ef7e521f7866034a3336489d&topic=38486.0)
-- [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
-- [Pi-hole Droid - open source Android client](https://github.com/friimaind/pi-hole-droid)
-- [Windows DNS Swapper](https://github.com/roots84/DNS-Swapper), see [#1400](https://github.com/pi-hole/pi-hole/issues/1400)
+1. `http://<IP_ADDPRESS_OF_YOUR_PI_HOLE>/admin/`
+2. `http:/pi.hole/admin/` (when using Pi-hole as your DNS server)
+3. `http://pi.hole/` (when using Pi-hole as your DNS server)
-## Coverage
+## The Faster-Than-Light Engine
+The [FTL Engine](https://github.com/pi-hole/FTL) is a lightweight, purpose-built daemon used to provide statistics needed for the Web Interface, and its API can be easily integrated into your own projects. As the name implies, FTL does this all *very quickly*!
-- [Adafruit livestream install](https://www.youtube.com/watch?v=eg4u2j1HYlI)
-- [TekThing: 5 fun, easy projects for a Raspberry Pi](https://youtu.be/QwrKlyC2kdM?t=1m42s)
-- [Pi-hole on Adafruit's blog](https://blog.adafruit.com/2016/03/04/pi-hole-is-a-black-hole-for-internet-ads-piday-raspberrypi-raspberry_pi/)
-- [The Defrag Show - MSDN/Channel 9](https://channel9.msdn.com/Shows/The-Defrag-Show/Defrag-Endoscope-USB-Camera-The-Final-HoloLens-Vote-Adblock-Pi-and-more?WT.mc_id=dlvr_twitter_ch9#time=20m39s)
-- [MacObserver Podcast 585](http://www.macobserver.com/tmo/podcast/macgeekgab-585)
-- [Medium: Block All Ads For $53](https://medium.com/@robleathern/block-ads-on-all-home-devices-for-53-18-a5f1ec139693#.gj1xpgr5d)
-- [MakeUseOf: Adblock Everywhere, The Pi-hole Way](http://www.makeuseof.com/tag/adblock-everywhere-raspberry-pi-hole-way/)
-- [Lifehacker: Turn Your Pi Into An Ad Blocker With A Single Command](http://lifehacker.com/turn-a-raspberry-pi-into-an-ad-blocker-with-a-single-co-1686093533)!
-- [Pi-hole on TekThing](https://youtu.be/8Co59HU2gY0?t=2m)
-- [Pi-hole on Security Now! Podcast](http://www.youtube.com/watch?v=p7-osq_y8i8&t=100m26s)
-- [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/)
-- [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/)
+Some of the statistics you can integrate include:
+* Total number of domains being blocked
+* Total number of DNS queries today
+* Total number of ads blocked today
+* Percentage of ads blocked
+* Unique domains
+* Queries forwarded (to your chosen upstream DNS server)
+* Queries cached
+* Unique clients
+
+The API can be accessed via [`telnet`](https://github.com/pi-hole/FTL), the Web (`admin/api.php`) and Command Line (`pihole -c -j`). You can out find [more details over here](https://discourse.pi-hole.net/t/pi-hole-api/1863).
+
+-----
+
+## The Origin Of Pi-hole
+Pi-hole being a **advertising-aware DNS/Web server**, makes use of the following technologies:
+
+* [`dnsmasq`](http://www.thekelleys.org.uk/dnsmasq/doc.html) - a lightweight DNS and DHCP server
+* [`curl`](https://curl.haxx.se) - A command line tool for transferring data with URL syntax
+* [`lighttpd`](https://www.lighttpd.net) - webserver designed and optimized for high performance
+* [`php`](https://secure.php.net) - a popular general-purpose web scripting language
+* [AdminLTE Dashboard](https://github.com/almasaeed2010/AdminLTE) - premium admin control panel based on Bootstrap 3.x
+
+While quite outdated at this point, [this original blog post about Pi-hole](https://jacobsalmela.com/2015/06/16/block-millions-ads-network-wide-with-a-raspberry-pi-hole-2-0/) goes into **great detail** about how Pi-hole was originally setup and how it works. Syntactically, it's no longer accurate, but the same basic principles and logic still apply to Pi-hole's current state.
+
+-----
+
+## Pi-hole Projects
+- [The Big Blocklist Collection](https://wally3k.github.io)
+- [Docker Pi-hole container (x86 and ARM)](https://hub.docker.com/r/diginc/pi-hole/)
+- [Pi-Hole in the cloud](http://blog.codybunch.com/2015/07/28/Pi-Hole-in-the-cloud/)
+- [Pie in the Sky-Hole [A Pi-Hole in the cloud for ad-blocking via DNS]](https://dlaa.me/blog/post/skyhole)
+- [Pi-hole Enable/Disable Button](http://thetimmy.silvernight.org/pages/endisbutton/)
+- [Minibian Pi-hole](https://munkjensen.net/wiki/index.php/See_my_Pi-Hole#Minibian_Pi-hole)
+- [CHiP-hole: Network-wide Ad-blocker](https://www.hackster.io/jacobsalmela/chip-hole-network-wide-ad-blocker-98e037)
+- [Chrome Extension: Pi-Hole List Editor](https://chrome.google.com/webstore/detail/pi-hole-list-editor/hlnoeoejkllgkjbnnnhfolapllcnaglh) ([Source Code](https://github.com/packtloss/pihole-extension))
+- [Splunk: Pi-hole Visualiser](https://splunkbase.splunk.com/app/3023/)
+- [Adblocking with P-hole and Ubuntu 14.04 on VirtualBox](https://hbalagtas.blogspot.com.au/2016/02/adblocking-with-pi-hole-and-ubuntu-1404.html)
+- [Pi-hole stats in your Mac's menu bar](https://getbitbar.com/plugins/Network/pi-hole.1m.py)
+- [Pi-hole unRAID Template](https://forums.lime-technology.com/topic/36810-support-spants-nodered-mqtt-dashing-couchdb/)
+- [Copernicus: Windows Tray Application](https://github.com/goldbattle/copernicus)
+- [Let your blink1 device blink when Pi-hole filters ads](https://gist.github.com/elpatron68/ec0b4c582e5abf604885ac1e068d233f)
+- [Pi-hole metrics](https://github.com/nlamirault/pihole_exporter) exporter for [Prometheus](https://prometheus.io/)
+- [Magic Mirror with DNS Filtering](https://zonksec.com/blog/magic-mirror-dns-filtering/#dnssoftware)
+- [Pi-hole Droid: Android client](https://github.com/friimaind/pi-hole-droid)
+
+-----
+
+## Coverage
+- [Lifehacker: Turn A Raspberry Pi Into An Ad Blocker With A Single Command](https://www.lifehacker.com.au/2015/02/turn-a-raspberry-pi-into-an-ad-blocker-with-a-single-command/)
+- [MakeUseOf: Adblock Everywhere: The Raspberry Pi-Hole Way](http://www.makeuseof.com/tag/adblock-everywhere-raspberry-pi-hole-way/)
+- [Catchpoint: Ad-Blocking on Apple iOS9: Valuing the End User Experience](http://blog.catchpoint.com/2015/09/14/ad-blocking-apple/)
+- [Security Now Netcast: Pi-hole](https://www.youtube.com/watch?v=p7-osq_y8i8&t=100m26s)
+- [TekThing: Raspberry Pi-Hole Makes Ads Disappear!](https://youtu.be/8Co59HU2gY0?t=2m)
+- [Foolish Tech Show](https://youtu.be/bYyena0I9yc?t=2m4s)
+- [Block Ads on All Home Devices for $53.18](https://medium.com/@robleathern/block-ads-on-all-home-devices-for-53-18-a5f1ec139693#.gj1xpgr5d)
+- [Pi-Hole for Ubuntu 14.04](http://www.boyter.org/2015/12/pi-hole-ubuntu-14-04/)
+- [MacObserver Podcast 585](https://www.macobserver.com/tmo/podcast/macgeekgab-585)
+- [The Defrag Show: Endoscope USB Camera, The Final [HoloLens] Vote, Adblock Pi and more](https://channel9.msdn.com/Shows/The-Defrag-Show/Defrag-Endoscope-USB-Camera-The-Final-HoloLens-Vote-Adblock-Pi-and-more?WT.mc_id=dlvr_twitter_ch9#time=20m39s)
+- [Adafruit: Pi-hole is a black hole for internet ads](https://blog.adafruit.com/2016/03/04/pi-hole-is-a-black-hole-for-internet-ads-piday-raspberrypi-raspberry_pi/)
+- [Digital Trends: 5 Fun, Easy Projects You Can Try With a $35 Raspberry Pi](https://youtu.be/QwrKlyC2kdM?t=1m42s)
+- [Adafruit: Raspberry Pi Quick Look at Pi Hole ad blocking server with Tony D](https://www.youtube.com/watch?v=eg4u2j1HYlI)
+- [Devacron: OrangePi Zero as an Ad-Block server with Pi-Hole](http://www.devacron.com/orangepi-zero-as-an-ad-block-server-with-pi-hole/)
+- [Linux Pro: The Hole Truth](http://www.linuxpromagazine.com/Issues/2017/200/The-sysadmin-s-daily-grind-Pi-hole)
+- [CryptoAUSTRALIA: How We Tried 5 Privacy Focused Raspberry Pi Projects](https://blog.cryptoaustralia.org.au/2017/10/05/5-privacy-focused-raspberry-pi-projects/)
+- [CryptoAUSTRALIA: Pi-hole Workshop](https://blog.cryptoaustralia.org.au/2017/11/02/pi-hole-network-wide-ad-blocker/)
+- [Know How 355: Killing ads with a Raspberry Pi-Hole!](https://www.twit.tv/shows/know-how/episodes/355)
diff --git a/advanced/01-pihole.conf b/advanced/01-pihole.conf
index 79735c15..f7b78ab0 100644
--- a/advanced/01-pihole.conf
+++ b/advanced/01-pihole.conf
@@ -21,8 +21,8 @@
###############################################################################
addn-hosts=/etc/pihole/gravity.list
-addn-hosts=/etc/pihole/local.list
addn-hosts=/etc/pihole/black.list
+addn-hosts=/etc/pihole/local.list
domain-needed
@@ -42,6 +42,6 @@ cache-size=10000
log-queries
log-facility=/var/log/pihole.log
-local-ttl=300
+local-ttl=2
log-async
diff --git a/advanced/Scripts/COL_TABLE b/advanced/Scripts/COL_TABLE
new file mode 100644
index 00000000..57aab4dd
--- /dev/null
+++ b/advanced/Scripts/COL_TABLE
@@ -0,0 +1,49 @@
+# Determine if terminal is capable of showing colours
+if [[ -t 1 ]] && [[ $(tput colors) -ge 8 ]]; then
+ # Bold and underline may not show up on all clients
+ # If something MUST be emphasised, use both
+ COL_BOLD=''
+ COL_ULINE=''
+
+ COL_NC=''
+ COL_GRAY=''
+ COL_RED=''
+ COL_GREEN=''
+ COL_YELLOW=''
+ COL_BLUE=''
+ COL_PURPLE=''
+ COL_CYAN=''
+else
+ # Provide empty variables for `set -u`
+ COL_BOLD=""
+ COL_ULINE=""
+
+ COL_NC=""
+ COL_GRAY=""
+ COL_RED=""
+ COL_GREEN=""
+ COL_YELLOW=""
+ COL_BLUE=""
+ COL_PURPLE=""
+ COL_CYAN=""
+fi
+
+# Deprecated variables
+COL_WHITE="${COL_BOLD}"
+COL_BLACK="${COL_NC}"
+COL_LIGHT_BLUE="${COL_BLUE}"
+COL_LIGHT_GREEN="${COL_GREEN}"
+COL_LIGHT_CYAN="${COL_CYAN}"
+COL_LIGHT_RED="${COL_RED}"
+COL_URG_RED="${COL_RED}${COL_BOLD}${COL_ULINE}"
+COL_LIGHT_PURPLE="${COL_PURPLE}"
+COL_BROWN="${COL_YELLOW}"
+COL_LIGHT_GRAY="${COL_GRAY}"
+COL_DARK_GRAY="${COL_GRAY}"
+
+TICK="[${COL_GREEN}✓${COL_NC}]"
+CROSS="[${COL_RED}✗${COL_NC}]"
+INFO="[i]"
+QST="[?]"
+DONE="${COL_GREEN} done!${COL_NC}"
+OVER="\\r"
diff --git a/advanced/Scripts/chronometer.sh b/advanced/Scripts/chronometer.sh
index d9b7d05b..8599e995 100755
--- a/advanced/Scripts/chronometer.sh
+++ b/advanced/Scripts/chronometer.sh
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
+# shellcheck disable=SC1090,SC1091
# Pi-hole: A black hole for Internet advertisements
# (c) 2017 Pi-hole, LLC (https://pi-hole.net)
# Network-wide ad blocking via your own hardware.
@@ -7,6 +8,7 @@
#
# This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license.
+LC_NUMERIC=C
# Retrieve stats from FTL engine
pihole-FTL() {
@@ -32,43 +34,74 @@ pihole-FTL() {
exec 3<&-
fi
else
- echo -e "${COL_LIGHT_RED}FTL offline${COL_NC}"
+ echo "0"
fi
}
-# Print spaces to align right-side content
+# Print spaces to align right-side additional text
printFunc() {
- txt_len="${#2}"
-
- # Reduce string length when using colour code
- [ "${2:0:1}" == "" ] && txt_len=$((txt_len-7))
-
- if [[ "$3" == "last" ]]; then
- # Prevent final line from printing trailing newline
- scr_size=( $(stty size 2>/dev/null || echo 24 80) )
- scr_width="${scr_size[1]}"
-
- title_len="${#1}"
- spc_num=$(( (scr_width - title_len) - txt_len ))
- [[ "$spc_num" -lt 0 ]] && spc_num="0"
- spc=$(printf "%${spc_num}s")
-
- printf "%s%s$spc" "$1" "$2"
+ local text_last
+
+ title="$1"
+ title_len="${#title}"
+
+ text_main="$2"
+ text_main_nocol="$text_main"
+ if [[ "${text_main:0:1}" == "" ]]; then
+ text_main_nocol=$(sed 's/\[[0-9;]\{1,5\}m//g' <<< "$text_main")
+ fi
+ text_main_len="${#text_main_nocol}"
+
+ text_addn="$3"
+ if [[ "$text_addn" == "last" ]]; then
+ text_addn=""
+ text_last="true"
+ fi
+
+ # If there is additional text, define max length of text_main
+ if [[ -n "$text_addn" ]]; then
+ case "$scr_cols" in
+ [0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-4]) text_main_max_len="9";;
+ 4[5-9]) text_main_max_len="14";;
+ *) text_main_max_len="19";;
+ esac
+ fi
+
+ [[ -z "$text_addn" ]] && text_main_max_len="$(( scr_cols - title_len ))"
+
+ # Remove excess characters from main text
+ if [[ "$text_main_len" -gt "$text_main_max_len" ]]; then
+ # Trim text without colours
+ text_main_trim="${text_main_nocol:0:$text_main_max_len}"
+ # Replace with trimmed text
+ text_main="${text_main/$text_main_nocol/$text_main_trim}"
+ fi
+
+ # Determine amount of spaces for each line
+ if [[ -n "$text_last" ]]; then
+ # Move cursor to end of screen
+ spc_num=$(( scr_cols - ( title_len + text_main_len ) ))
else
- # Determine number of spaces for padding
- spc_num=$(( 20 - txt_len ))
- [[ "$spc_num" -lt 0 ]] && spc_num="0"
- spc=$(printf "%${spc_num}s")
+ spc_num=$(( text_main_max_len - text_main_len ))
+ fi
+
+ [[ "$spc_num" -le 0 ]] && spc_num="0"
+ spc=$(printf "%${spc_num}s")
+ #spc="${spc// /.}" # Debug: Visualise spaces
- # Print string (Max 20 characters, prevents overflow)
- printf "%s%s$spc" "$1" "${2:0:20}"
+ printf "%s%s$spc" "$title" "$text_main"
+
+ if [[ -n "$text_addn" ]]; then
+ printf "%s(%s)%s\\n" "$COL_NC$COL_DARK_GRAY" "$text_addn" "$COL_NC"
+ else
+ # Do not print trailing newline on final line
+ [[ -z "$text_last" ]] && printf "%s\\n" "$COL_NC"
fi
}
# Perform on first Chrono run (not for JSON formatted string)
get_init_stats() {
- LC_NUMERIC=C
- calcFunc(){ awk "BEGIN {print $*}"; }
+ calcFunc(){ awk "BEGIN {print $*}" 2> /dev/null; }
# Convert bytes to human-readable format
hrBytes() {
@@ -90,33 +123,53 @@ get_init_stats() {
# Convert seconds to human-readable format
hrSecs() {
- day=$(( $1/60/60/24 )); hrs=$(( $1/3600%24 )); mins=$(( ($1%3600)/60 )); secs=$(( $1%60 ))
+ day=$(( $1/60/60/24 )); hrs=$(( $1/3600%24 ))
+ mins=$(( ($1%3600)/60 )); secs=$(( $1%60 ))
[[ "$day" -ge "2" ]] && plu="s"
[[ "$day" -ge "1" ]] && days="$day day${plu}, " || days=""
- printf "%s%02d:%02d:%02d\n" "$days" "$hrs" "$mins" "$secs"
- }
+ printf "%s%02d:%02d:%02d\\n" "$days" "$hrs" "$mins" "$secs"
+ }
# Set Colour Codes
coltable="/opt/pihole/COL_TABLE"
if [[ -f "${coltable}" ]]; then
source ${coltable}
else
- COL_NC=''
- COL_DARK_GRAY=''
- COL_LIGHT_GREEN=''
- COL_LIGHT_BLUE=''
- COL_LIGHT_RED=''
- COL_YELLOW=''
- COL_LIGHT_RED=''
- COL_URG_RED=''
+ COL_NC=""
+ COL_DARK_GRAY=""
+ COL_LIGHT_GREEN=""
+ COL_LIGHT_BLUE=""
+ COL_LIGHT_RED=""
+ COL_YELLOW=""
+ COL_LIGHT_RED=""
+ COL_URG_RED=""
fi
- # Get RPi model number, or OS distro info
+ # Get RPi throttle state (RPi 3B only) & model number, or OS distro info
if command -v vcgencmd &> /dev/null; then
- sys_rev=$(awk '/Revision/ {print $3}' < /proc/cpuinfo)
- case "$sys_rev" in
+ local sys_throttle_raw
+ local sys_rev_raw
+
+ sys_throttle_raw=$(vgt=$(sudo vcgencmd get_throttled); echo "${vgt##*x}")
+
+ # Active Throttle Notice: http://bit.ly/2gnunOo
+ if [[ "$sys_throttle_raw" != "0" ]]; then
+ case "$sys_throttle_raw" in
+ *0001) thr_type="${COL_YELLOW}Under Voltage";;
+ *0002) thr_type="${COL_LIGHT_BLUE}Arm Freq Cap";;
+ *0003) thr_type="${COL_YELLOW}UV${COL_DARK_GRAY},${COL_NC} ${COL_LIGHT_BLUE}AFC";;
+ *0004) thr_type="${COL_LIGHT_RED}Throttled";;
+ *0005) thr_type="${COL_YELLOW}UV${COL_DARK_GRAY},${COL_NC} ${COL_LIGHT_RED}TT";;
+ *0006) thr_type="${COL_LIGHT_BLUE}AFC${COL_DARK_GRAY},${COL_NC} ${COL_LIGHT_RED}TT";;
+ *0007) thr_type="${COL_YELLOW}UV${COL_DARK_GRAY},${COL_NC} ${COL_LIGHT_BLUE}AFC${COL_DARK_GRAY},${COL_NC} ${COL_LIGHT_RED}TT";;
+ esac
+ [[ -n "$thr_type" ]] && sys_throttle="$thr_type${COL_DARK_GRAY}"
+ fi
+
+ sys_rev_raw=$(awk '/Revision/ {print $3}' < /proc/cpuinfo)
+ case "$sys_rev_raw" in
000[2-6]) sys_model=" 1, Model B";; # 256MB
- 000[7-9]) sys_model=" 1, Model A" ;; # 256MB
+ 000[7-9]) sys_model=" 1, Model A";; # 256MB
000d|000e|000f) sys_model=" 1, Model B";; # 512MB
0010|0013) sys_model=" 1, Model B+";; # 512MB
0012|0015) sys_model=" 1, Model A+";; # 256MB
@@ -126,7 +179,7 @@ get_init_stats() {
90009[2-3]|920093) sys_model=" Zero";; # 512MB
9000c1) sys_model=" Zero W";; # 512MB
a02082|a[2-3]2082) sys_model=" 3, Model B";; # 1GB
- *) sys_model="" ;;
+ *) sys_model="";;
esac
sys_type="Raspberry Pi$sys_model"
else
@@ -137,7 +190,6 @@ get_init_stats() {
# Get core count
sys_cores=$(grep -c "^processor" /proc/cpuinfo)
- [[ "$sys_cores" -ne 1 ]] && sys_cores_plu="cores" || sys_cores_plu="core"
# Test existence of clock speed file for ARM CPU
if [[ -f "/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq" ]]; then
@@ -168,80 +220,97 @@ get_sys_stats() {
# Update every 12 refreshes (Def: every 60s)
count=$((count+1))
if [[ "$count" == "1" ]] || (( "$count" % 12 == 0 )); then
+ # Do not source setupVars if file does not exist
[[ -n "$setupVars" ]] && source "$setupVars"
-
-
- ph_ver_raw=($(pihole -v -c 2> /dev/null | sed -n 's/^.* v/v/p'))
+
+ mapfile -t ph_ver_raw < <(pihole -v -c 2> /dev/null | sed -n 's/^.* v/v/p')
if [[ -n "${ph_ver_raw[0]}" ]]; then
ph_core_ver="${ph_ver_raw[0]}"
ph_lte_ver="${ph_ver_raw[1]}"
ph_ftl_ver="${ph_ver_raw[2]}"
else
- ph_core_ver="${COL_LIGHT_RED}API unavailable${COL_NC}"
+ ph_core_ver="-1"
fi
-
+
sys_name=$(hostname)
-
+
[[ -n "$TEMPERATUREUNIT" ]] && temp_unit="$TEMPERATUREUNIT" || temp_unit="c"
-
+
# Get storage stats for partition mounted on /
- disk_raw=($(df -B1 / 2> /dev/null | awk 'END{ print $3,$2,$5 }'))
+ read -r -a disk_raw <<< "$(df -B1 / 2> /dev/null | awk 'END{ print $3,$2,$5 }')"
disk_used="${disk_raw[0]}"
disk_total="${disk_raw[1]}"
disk_perc="${disk_raw[2]}"
-
+
net_gateway=$(route -n | awk '$4 == "UG" {print $2;exit}')
-
+
# Get DHCP stats, if feature is enabled
if [[ "$DHCP_ACTIVE" == "true" ]]; then
- ph_dhcp_eip="${DHCP_END##*.}"
ph_dhcp_max=$(( ${DHCP_END##*.} - ${DHCP_START##*.} + 1 ))
fi
-
- # Get alt DNS server, or print total count of alt DNS servers
- if [[ -z "${PIHOLE_DNS_3}" ]]; then
- ph_alts="${PIHOLE_DNS_2}"
- else
- dns_count="0"
- [[ -n "${PIHOLE_DNS_2}" ]] && dns_count=$((dns_count+1))
- [[ -n "${PIHOLE_DNS_3}" ]] && dns_count=$((dns_count+1))
- [[ -n "${PIHOLE_DNS_4}" ]] && dns_count=$((dns_count+1))
- [[ -n "${PIHOLE_DNS_5}" ]] && dns_count=$((dns_count+1))
- [[ -n "${PIHOLE_DNS_6}" ]] && dns_count=$((dns_count+1))
- [[ -n "${PIHOLE_DNS_7}" ]] && dns_count=$((dns_count+1))
- [[ -n "${PIHOLE_DNS_8}" ]] && dns_count=$((dns_count+1))
- [[ -n "${PIHOLE_DNS_9}" ]] && dns_count="$dns_count+"
- ph_alts="${dns_count} others"
- fi
+
+ # Get DNS server count
+ dns_count="0"
+ [[ -n "${PIHOLE_DNS_1}" ]] && dns_count=$((dns_count+1))
+ [[ -n "${PIHOLE_DNS_2}" ]] && dns_count=$((dns_count+1))
+ [[ -n "${PIHOLE_DNS_3}" ]] && dns_count=$((dns_count+1))
+ [[ -n "${PIHOLE_DNS_4}" ]] && dns_count=$((dns_count+1))
+ [[ -n "${PIHOLE_DNS_5}" ]] && dns_count=$((dns_count+1))
+ [[ -n "${PIHOLE_DNS_6}" ]] && dns_count=$((dns_count+1))
+ [[ -n "${PIHOLE_DNS_7}" ]] && dns_count=$((dns_count+1))
+ [[ -n "${PIHOLE_DNS_8}" ]] && dns_count=$((dns_count+1))
+ [[ -n "${PIHOLE_DNS_9}" ]] && dns_count="$dns_count+"
fi
-
+
+ # Get screen size
+ read -r -a scr_size <<< "$(stty size 2>/dev/null || echo 24 80)"
+ scr_lines="${scr_size[0]}"
+ scr_cols="${scr_size[1]}"
+
+ # Determine Chronometer size behaviour
+ if [[ "$scr_cols" -ge 58 ]]; then
+ chrono_width="large"
+ elif [[ "$scr_cols" -gt 40 ]]; then
+ chrono_width="medium"
+ else
+ chrono_width="small"
+ fi
+
+ # Determine max length of divider string
+ scr_line_len=$(( scr_cols - 2 ))
+ [[ "$scr_line_len" -ge 58 ]] && scr_line_len="58"
+ scr_line_str=$(printf "%${scr_line_len}s")
+ scr_line_str="${scr_line_str// /—}"
+
sys_uptime=$(hrSecs "$(cut -d. -f1 /proc/uptime)")
sys_loadavg=$(cut -d " " -f1,2,3 /proc/loadavg)
-
- # Get CPU usage, only counting processes over 1% CPU as active
+
+ # Get CPU usage, only counting processes over 1% as active
+ # shellcheck disable=SC2009
cpu_raw=$(ps -eo pcpu,rss --no-headers | grep -E -v " 0")
cpu_tasks=$(wc -l <<< "$cpu_raw")
cpu_taskact=$(sed -r "/(^ 0.)/d" <<< "$cpu_raw" | wc -l)
cpu_perc=$(awk '{sum+=$1} END {printf "%.0f\n", sum/'"$sys_cores"'}' <<< "$cpu_raw")
-
+
# Get CPU clock speed
if [[ -n "$scaling_freq_file" ]]; then
cpu_mhz=$(( $(< /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq) / 1000 ))
else
- cpu_mhz=$(lscpu | awk -F "[ .]+" '/MHz/ {print $4;exit}')
+ cpu_mhz=$(lscpu | awk -F ":" '/MHz/ {print $2;exit}')
+ cpu_mhz=$(printf "%.0f" "${cpu_mhz//[[:space:]]/}")
fi
-
- # Determine correct string format for CPU clock speed
+
+ # Determine whether to display CPU clock speed as MHz or GHz
if [[ -n "$cpu_mhz" ]]; then
- [[ "$cpu_mhz" -le "999" ]] && cpu_freq="$cpu_mhz MHz" || cpu_freq="$(calcFunc "$cpu_mhz"/1000) Ghz"
- [[ -n "$cpu_freq" ]] && cpu_freq_str=" @ $cpu_freq" || cpu_freq_str=""
+ [[ "$cpu_mhz" -le "999" ]] && cpu_freq="$cpu_mhz MHz" || cpu_freq="$(printf "%.1f" $(calcFunc "$cpu_mhz"/1000)) GHz"
+ [[ "${cpu_freq}" == *".0"* ]] && cpu_freq="${cpu_freq/.0/}"
fi
-
+
# Determine colour for temperature
if [[ -n "$temp_file" ]]; then
if [[ "$temp_unit" == "C" ]]; then
- cpu_temp=$(printf "%'.0fc\n" "$(calcFunc "$(< $temp_file) / 1000")")
-
+ cpu_temp=$(printf "%.0fc\\n" "$(calcFunc "$(< $temp_file) / 1000")")
+
case "${cpu_temp::-1}" in
-*|[0-9]|[1-3][0-9]) cpu_col="$COL_LIGHT_BLUE";;
4[0-9]) cpu_col="";;
@@ -249,13 +318,13 @@ get_sys_stats() {
6[0-9]) cpu_col="$COL_LIGHT_RED";;
*) cpu_col="$COL_URG_RED";;
esac
-
+
# $COL_NC$COL_DARK_GRAY is needed for $COL_URG_RED
- cpu_temp_str=", $cpu_col$cpu_temp$COL_NC$COL_DARK_GRAY"
-
+ cpu_temp_str=" @ $cpu_col$cpu_temp$COL_NC$COL_DARK_GRAY"
+
elif [[ "$temp_unit" == "F" ]]; then
- cpu_temp=$(printf "%'.0ff\n" "$(calcFunc "($(< $temp_file) / 1000) * 9 / 5 + 32")")
-
+ cpu_temp=$(printf "%.0ff\\n" "$(calcFunc "($(< $temp_file) / 1000) * 9 / 5 + 32")")
+
case "${cpu_temp::-1}" in
-*|[0-9]|[0-9][0-9]) cpu_col="$COL_LIGHT_BLUE";;
1[0-1][0-9]) cpu_col="";;
@@ -263,132 +332,201 @@ get_sys_stats() {
1[4-5][0-9]) cpu_col="$COL_LIGHT_RED";;
*) cpu_col="$COL_URG_RED";;
esac
-
- cpu_temp_str=", $cpu_col$cpu_temp$COL_NC$COL_DARK_GRAY"
-
+
+ cpu_temp_str=" @ $cpu_col$cpu_temp$COL_NC$COL_DARK_GRAY"
+
else
- cpu_temp_str=$(printf ", %'.0fk\n" "$(calcFunc "($(< $temp_file) / 1000) + 273.15")")
+ cpu_temp_str=$(printf " @ %.0fk\\n" "$(calcFunc "($(< $temp_file) / 1000) + 273.15")")
fi
else
cpu_temp_str=""
fi
-
- ram_raw=($(awk '/MemTotal:/{total=$2} /MemFree:/{free=$2} /Buffers:/{buffers=$2} /^Cached:/{cached=$2} END {printf "%.0f %.0f %.0f", (total-free-buffers-cached)*100/total, (total-free-buffers-cached)*1024, total*1024}' /proc/meminfo))
+
+ read -r -a ram_raw <<< "$(awk '/MemTotal:/{total=$2} /MemFree:/{free=$2} /Buffers:/{buffers=$2} /^Cached:/{cached=$2} END {printf "%.0f %.0f %.0f", (total-free-buffers-cached)*100/total, (total-free-buffers-cached)*1024, total*1024}' /proc/meminfo)"
ram_perc="${ram_raw[0]}"
ram_used="${ram_raw[1]}"
ram_total="${ram_raw[2]}"
-
+
if [[ "$(pihole status web 2> /dev/null)" == "1" ]]; then
ph_status="${COL_LIGHT_GREEN}Active"
else
- ph_status="${COL_LIGHT_RED}Inactive"
+ ph_status="${COL_LIGHT_RED}Offline"
fi
-
+
if [[ "$DHCP_ACTIVE" == "true" ]]; then
- ph_dhcp_num=$(wc -l 2> /dev/null < "/etc/pihole/dhcp.leases")
+ local ph_dhcp_range
+
+ ph_dhcp_range=$(seq -s "|" -f "${DHCP_START%.*}.%g" "${DHCP_START##*.}" "${DHCP_END##*.}")
+
+ # Count dynamic leases from available range, and not static leases
+ ph_dhcp_num=$(grep -cE "$ph_dhcp_range" "/etc/pihole/dhcp.leases")
+ ph_dhcp_percent=$(( ph_dhcp_num * 100 / ph_dhcp_max ))
fi
}
get_ftl_stats() {
local stats_raw
-
- stats_raw=($(pihole-FTL "stats"))
- domains_being_blocked_raw="${stats_raw[1]}"
- dns_queries_today_raw="${stats_raw[3]}"
- ads_blocked_today_raw="${stats_raw[5]}"
- ads_percentage_today_raw="${stats_raw[7]}"
+
+ mapfile -t stats_raw < <(pihole-FTL "stats")
+ domains_being_blocked_raw="${stats_raw[0]#* }"
+ dns_queries_today_raw="${stats_raw[1]#* }"
+ ads_blocked_today_raw="${stats_raw[2]#* }"
+ ads_percentage_today_raw="${stats_raw[3]#* }"
+ queries_forwarded_raw="${stats_raw[5]#* }"
+ queries_cached_raw="${stats_raw[6]#* }"
# Only retrieve these stats when not called from jsonFunc
if [[ -z "$1" ]]; then
- local recent_blocked_raw
local top_ad_raw
local top_domain_raw
local top_client_raw
-
- domains_being_blocked=$(printf "%'.0f\n" "${domains_being_blocked_raw}")
- dns_queries_today=$(printf "%'.0f\n" "${dns_queries_today_raw}")
- ads_blocked_today=$(printf "%'.0f\n" "${ads_blocked_today_raw}")
- ads_percentage_today=$(printf "%'.0f\n" "${ads_percentage_today_raw}")
-
- recent_blocked_raw=$(pihole-FTL recentBlocked)
- top_ad_raw=($(pihole-FTL "top-ads (1)"))
- top_domain_raw=($(pihole-FTL "top-domains (1)"))
- top_client_raw=($(pihole-FTL "top-clients (1)"))
-
- # Limit strings to 40 characters to prevent overflow
- recent_blocked="${recent_blocked_raw:0:40}"
- top_ad="${top_ad_raw[2]:0:40}"
- top_domain="${top_domain_raw[2]:0:40}"
- [[ "${top_client_raw[3]}" ]] && top_client="${top_client_raw[3]:0:40}" || top_client="${top_client_raw[2]:0:40}"
+
+ domains_being_blocked=$(printf "%.0f\\n" "${domains_being_blocked_raw}" 2> /dev/null)
+ dns_queries_today=$(printf "%.0f\\n" "${dns_queries_today_raw}")
+ ads_blocked_today=$(printf "%.0f\\n" "${ads_blocked_today_raw}")
+ ads_percentage_today=$(printf "%'.0f\\n" "${ads_percentage_today_raw}")
+ queries_cached_percentage=$(printf "%.0f\\n" "$(calcFunc "$queries_cached_raw * 100 / ( $queries_forwarded_raw + $queries_cached_raw )")")
+ recent_blocked=$(pihole-FTL recentBlocked)
+ read -r -a top_ad_raw <<< "$(pihole-FTL "top-ads (1)")"
+ read -r -a top_domain_raw <<< "$(pihole-FTL "top-domains (1)")"
+ read -r -a top_client_raw <<< "$(pihole-FTL "top-clients (1)")"
+
+ top_ad="${top_ad_raw[2]}"
+ top_domain="${top_domain_raw[2]}"
+ if [[ "${top_client_raw[3]}" ]]; then
+ top_client="${top_client_raw[3]}"
+ else
+ top_client="${top_client_raw[2]}"
+ fi
fi
}
+get_strings() {
+ # Expand or contract strings depending on screen size
+ if [[ "$chrono_width" == "large" ]]; then
+ phc_str=" ${COL_DARK_GRAY}Core"
+ lte_str=" ${COL_DARK_GRAY}Web"
+ ftl_str=" ${COL_DARK_GRAY}FTL"
+ api_str="${COL_LIGHT_RED}API Offline"
+
+ host_info="$sys_type"
+ sys_info="$sys_throttle"
+ sys_info2="Active: $cpu_taskact of $cpu_tasks tasks"
+ used_str="Used: "
+ leased_str="Leased: "
+ domains_being_blocked=$(printf "%'.0f" "$domains_being_blocked")
+ ads_blocked_today=$(printf "%'.0f" "$ads_blocked_today")
+ dns_queries_today=$(printf "%'.0f" "$dns_queries_today")
+ ph_info="Blocking: $domains_being_blocked sites"
+ total_str="Total: "
+ else
+ phc_str=" ${COL_DARK_GRAY}Core"
+ lte_str=" ${COL_DARK_GRAY}Web"
+ ftl_str=" ${COL_DARK_GRAY}FTL"
+ api_str="${COL_LIGHT_RED}API Down"
+ ph_info="$domains_being_blocked blocked"
+ fi
+
+ [[ "$sys_cores" -ne 1 ]] && sys_cores_txt="${sys_cores}x "
+ cpu_info="$sys_cores_txt$cpu_freq$cpu_temp_str"
+ ram_info="$used_str$(hrBytes "$ram_used") of $(hrBytes "$ram_total")"
+ disk_info="$used_str$(hrBytes "$disk_used") of $(hrBytes "$disk_total")"
+
+ lan_info="Gateway: $net_gateway"
+ dhcp_info="$leased_str$ph_dhcp_num of $ph_dhcp_max"
+
+ ads_info="$total_str$ads_blocked_today of $dns_queries_today"
+ dns_info="$dns_count DNS servers"
+
+ [[ "$recent_blocked" == "0" ]] && recent_blocked="${COL_LIGHT_RED}FTL offline${COL_NC}"
+}
+
chronoFunc() {
get_init_stats
-
+
for (( ; ; )); do
get_sys_stats
get_ftl_stats
-
- # Do not print LTE/FTL strings if API is unavailable
- ph_core_str=" ${COL_DARK_GRAY}Pi-hole: $ph_core_ver${COL_NC}"
- if [[ -n "$ph_lte_ver" ]]; then
- ph_lte_str=" ${COL_DARK_GRAY}AdminLTE: $ph_lte_ver${COL_NC}"
- ph_ftl_str=" ${COL_DARK_GRAY}FTL: $ph_ftl_ver${COL_NC}"
+ get_strings
+
+ # Strip excess development version numbers
+ if [[ "$ph_core_ver" != "-1" ]]; then
+ phc_ver_str="$phc_str: ${ph_core_ver%-*}${COL_NC}"
+ lte_ver_str="$lte_str: ${ph_lte_ver%-*}${COL_NC}"
+ ftl_ver_str="$ftl_str: ${ph_ftl_ver%-*}${COL_NC}"
+ else
+ phc_ver_str="$phc_str: $api_str${COL_NC}"
fi
-
+
+ # Get refresh number
+ if [[ "$*" == *"-r"* ]]; then
+ num="$*"
+ num="${num/*-r /}"
+ num="${num/ */}"
+ num_str="Refresh set for every $num seconds"
+ else
+ num_str=""
+ fi
+
clear
-
- echo -e "|¯¯¯(¯)__|¯|_ ___|¯|___$ph_core_str
-| ¯_/¯|__| ' \/ _ \ / -_)$ph_lte_str
-|_| |_| |_||_\___/_\___|$ph_ftl_str
- ${COL_DARK_GRAY}——————————————————————————————————————————————————————————${COL_NC}"
- printFunc " Hostname: " "$sys_name"
- [ -n "$sys_type" ] && printf "%s(%s)%s\n" "$COL_DARK_GRAY" "$sys_type" "$COL_NC" || printf "\n"
-
- printf "%s\n" " Uptime: $sys_uptime"
-
- printFunc " Task Load: " "$sys_loadavg"
- printf "%s(%s)%s\n" "$COL_DARK_GRAY" "Active: $cpu_taskact of $cpu_tasks tasks" "$COL_NC"
-
- printFunc " CPU usage: " "$cpu_perc%"
- printf "%s(%s)%s\n" "$COL_DARK_GRAY" "$sys_cores $sys_cores_plu$cpu_freq_str$cpu_temp_str" "$COL_NC"
-
- printFunc " RAM usage: " "$ram_perc%"
- printf "%s(%s)%s\n" "$COL_DARK_GRAY" "Used: $(hrBytes "$ram_used") of $(hrBytes "$ram_total")" "$COL_NC"
-
- printFunc " HDD usage: " "$disk_perc"
- printf "%s(%s)%s\n" "$COL_DARK_GRAY" "Used: $(hrBytes "$disk_used") of $(hrBytes "$disk_total")" "$COL_NC"
-
- printFunc " LAN addr: " "${IPV4_ADDRESS/\/*/}"
- printf "%s(%s)%s\n" "$COL_DARK_GRAY" "Gateway: $net_gateway" "$COL_NC"
-
+ # Remove exit message heading on third refresh
+ if [[ "$count" -le 2 ]] && [[ "$*" != *"-e"* ]]; then
+ echo -e " ${COL_LIGHT_GREEN}Pi-hole Chronometer${COL_NC}
+ $num_str
+ ${COL_LIGHT_RED}Press Ctrl-C to exit${COL_NC}
+ ${COL_DARK_GRAY}$scr_line_str${COL_NC}"
+ else
+ echo -e "|¯¯¯(¯)_|¯|_ ___|¯|___$phc_ver_str
+| ¯_/¯|_| ' \\/ _ \\ / -_)$lte_ver_str
+|_| |_| |_||_\\___/_\\___|$ftl_ver_str
+ ${COL_DARK_GRAY}$scr_line_str${COL_NC}"
+ fi
+
+ printFunc " Hostname: " "$sys_name" "$host_info"
+ printFunc " Uptime: " "$sys_uptime" "$sys_info"
+ printFunc " Task Load: " "$sys_loadavg" "$sys_info2"
+ printFunc " CPU usage: " "$cpu_perc%" "$cpu_info"
+ printFunc " RAM usage: " "$ram_perc%" "$ram_info"
+ printFunc " HDD usage: " "$disk_perc" "$disk_info"
+
+ if [[ "$scr_lines" -gt 17 ]] && [[ "$chrono_width" != "small" ]]; then
+ printFunc " LAN addr: " "${IPV4_ADDRESS/\/*/}" "$lan_info"
+ fi
+
if [[ "$DHCP_ACTIVE" == "true" ]]; then
- printFunc " DHCP: " "$DHCP_START to $ph_dhcp_eip"
- printf "%s(%s)%s\n" "$COL_DARK_GRAY" "Leased: $ph_dhcp_num of $ph_dhcp_max" "$COL_NC"
+ printFunc "DHCP usage: " "$ph_dhcp_percent%" "$dhcp_info"
fi
-
- printFunc " Pi-hole: " "$ph_status"
- printf "%s(%s)%s\n" "$COL_DARK_GRAY" "Blocking: $domains_being_blocked sites" "$COL_NC"
-
- printFunc " Ads Today: " "$ads_percentage_today%"
- printf "%s(%s)%s\n" "$COL_DARK_GRAY" "$ads_blocked_today of $dns_queries_today queries" "$COL_NC"
-
- printFunc " Fwd DNS: " "$PIHOLE_DNS_1"
- printf "%s(%s)%s\n" "$COL_DARK_GRAY" "Alt DNS: $ph_alts" "$COL_NC"
-
- echo -e " ${COL_DARK_GRAY}——————————————————————————————————————————————————————————${COL_NC}"
- echo " Recently blocked: $recent_blocked"
- echo " Top Advertiser: $top_ad"
- echo " Top Domain: $top_domain"
- printFunc " Top Client: " "$top_client" "last"
-
- if [[ "$1" == "exit" ]]; then
+
+ printFunc " Pi-hole: " "$ph_status" "$ph_info"
+ printFunc " Ads Today: " "$ads_percentage_today%" "$ads_info"
+ printFunc "Local Qrys: " "$queries_cached_percentage%" "$dns_info"
+
+ printFunc " Blocked: " "$recent_blocked"
+ printFunc "Top Advert: " "$top_ad"
+
+ # Provide more stats on screens with more lines
+ if [[ "$scr_lines" -eq 17 ]]; then
+ if [[ "$DHCP_ACTIVE" == "true" ]]; then
+ printFunc "Top Domain: " "$top_domain" "last"
+ else
+ print_client="true"
+ fi
+ else
+ print_client="true"
+ fi
+
+ if [[ -n "$print_client" ]]; then
+ printFunc "Top Domain: " "$top_domain"
+ printFunc "Top Client: " "$top_client" "last"
+ fi
+
+ # Handle exit/refresh options
+ if [[ "$*" == *"-e"* ]]; then
exit 0
else
- if [[ -n "$1" ]]; then
- sleep "${1}"
+ if [[ "$*" == *"-r"* ]]; then
+ sleep "$num"
else
sleep 5
fi
@@ -409,14 +547,14 @@ helpFunc() {
echo "Usage: pihole -c [options]
Example: 'pihole -c -j'
Calculates stats and displays to an LCD
-
+
Options:
-j, --json Output stats as JSON formatted string
-r, --refresh Set update frequency (in seconds)
-e, --exit Output stats and exit witout refreshing
-h, --help Display this help text"
fi
-
+
exit 0
}
@@ -428,8 +566,8 @@ for var in "$@"; do
case "$var" in
"-j" | "--json" ) jsonFunc;;
"-h" | "--help" ) helpFunc;;
- "-r" | "--refresh" ) chronoFunc "$2";;
- "-e" | "--exit" ) chronoFunc "exit";;
+ "-r" | "--refresh" ) chronoFunc "$@";;
+ "-e" | "--exit" ) chronoFunc "$@";;
* ) helpFunc "?";;
esac
done
diff --git a/advanced/Scripts/list.sh b/advanced/Scripts/list.sh
index 308e1f5e..72250afd 100755
--- a/advanced/Scripts/list.sh
+++ b/advanced/Scripts/list.sh
@@ -19,11 +19,14 @@ addmode=true
verbose=true
domList=()
-domToRemoveList=()
listMain=""
listAlt=""
+colfile="/opt/pihole/COL_TABLE"
+source ${colfile}
+
+
helpFunc() {
if [[ "${listMain}" == "${whitelist}" ]]; then
param="w"
@@ -45,7 +48,8 @@ Options:
-nr, --noreload Update ${type}list without refreshing dnsmasq
-q, --quiet Make output less verbose
-h, --help Show this help dialog
- -l, --list Display all your ${type}listed domains"
+ -l, --list Display all your ${type}listed domains
+ --nuke Removes all entries in a list"
exit 0
}
@@ -58,15 +62,19 @@ EscapeRegexp() {
}
HandleOther() {
- # First, convert everything to lowercase
- domain=$(sed -e "y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/" <<< "$1")
+ # Convert to lowercase
+ domain="${1,,}"
# Check validity of domain
- validDomain=$(echo "${domain}" | perl -lne 'print if /(?!.*[^a-z0-9-\.].*)^((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9-]+\.)*[a-z]{2,63}/')
- if [[ -z "${validDomain}" ]]; then
- echo "::: $1 is not a valid argument or domain name"
- else
+ if [[ "${#domain}" -le 253 ]]; then
+ validDomain=$(grep -P "^((-|_)*[a-z\d]((-|_)*[a-z\d])*(-|_)*)(\.(-|_)*([a-z\d]((-|_)*[a-z\d])*))*$" <<< "${domain}") # Valid chars check
+ validDomain=$(grep -P "^[^\.]{1,63}(\.[^\.]{1,63})*$" <<< "${validDomain}") # Length of each label
+ fi
+
+ if [[ -n "${validDomain}" ]]; then
domList=("${domList[@]}" ${validDomain})
+ else
+ echo -e " ${CROSS} ${domain} is not a valid argument or domain name!"
fi
}
@@ -94,7 +102,13 @@ AddDomain() {
list="$2"
domain=$(EscapeRegexp "$1")
+ [[ "${list}" == "${whitelist}" ]] && listname="whitelist"
+ [[ "${list}" == "${blacklist}" ]] && listname="blacklist"
+ [[ "${list}" == "${wildcardlist}" ]] && listname="wildcard blacklist"
+
if [[ "${list}" == "${whitelist}" || "${list}" == "${blacklist}" ]]; then
+ [[ "${list}" == "${whitelist}" && -z "${type}" ]] && type="--whitelist-only"
+ [[ "${list}" == "${blacklist}" && -z "${type}" ]] && type="--blacklist-only"
bool=true
# Is the domain in the list we want to add it to?
grep -Ex -q "${domain}" "${list}" > /dev/null 2>&1 || bool=false
@@ -102,101 +116,122 @@ AddDomain() {
if [[ "${bool}" == false ]]; then
# Domain not found in the whitelist file, add it!
if [[ "${verbose}" == true ]]; then
- echo "::: Adding $1 to $list..."
+ echo -e " ${INFO} Adding $1 to $listname..."
fi
reload=true
# Add it to the list we want to add it to
echo "$1" >> "${list}"
else
if [[ "${verbose}" == true ]]; then
- echo "::: ${1} already exists in ${list}, no need to add!"
+ echo -e " ${INFO} ${1} already exists in ${listname}, no need to add!"
fi
fi
elif [[ "${list}" == "${wildcardlist}" ]]; then
source "${piholeDir}/setupVars.conf"
- # Remove the /* from the end of the IPv4addr.
+ # Remove the /* from the end of the IP addresses
IPV4_ADDRESS=${IPV4_ADDRESS%/*}
- IPV6_ADDRESS=${IPV6_ADDRESS}
-
+ IPV6_ADDRESS=${IPV6_ADDRESS%/*}
+ [[ -z "${type}" ]] && type="--wildcard-only"
bool=true
# Is the domain in the list?
grep -e "address=\/${domain}\/" "${wildcardlist}" > /dev/null 2>&1 || bool=false
if [[ "${bool}" == false ]]; then
if [[ "${verbose}" == true ]]; then
- echo "::: Adding $1 to wildcard blacklist..."
+ echo -e " ${INFO} Adding $1 to wildcard blacklist..."
fi
- reload=true
+ reload="restart"
echo "address=/$1/${IPV4_ADDRESS}" >> "${wildcardlist}"
if [[ "${#IPV6_ADDRESS}" > 0 ]]; then
echo "address=/$1/${IPV6_ADDRESS}" >> "${wildcardlist}"
fi
else
if [[ "${verbose}" == true ]]; then
- echo "::: ${1} already exists in wildcard blacklist, no need to add!"
+ echo -e " ${INFO} ${1} already exists in wildcard blacklist, no need to add!"
fi
fi
fi
}
RemoveDomain() {
- list="$2"
- domain=$(EscapeRegexp "$1")
-
- if [[ "${list}" == "${whitelist}" || "${list}" == "${blacklist}" ]]; then
- bool=true
- # 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..."
- # /I flag: search case-insensitive
- sed -i "/${domain}/Id" "${list}"
- reload=true
- else
- if [[ "${verbose}" == true ]]; then
- echo "::: ${1} does not exist in ${list}, no need to remove!"
- fi
+ list="$2"
+ domain=$(EscapeRegexp "$1")
+
+ [[ "${list}" == "${whitelist}" ]] && listname="whitelist"
+ [[ "${list}" == "${blacklist}" ]] && listname="blacklist"
+ [[ "${list}" == "${wildcardlist}" ]] && listname="wildcard blacklist"
+
+ if [[ "${list}" == "${whitelist}" || "${list}" == "${blacklist}" ]]; then
+ bool=true
+ [[ "${list}" == "${whitelist}" && -z "${type}" ]] && type="--whitelist-only"
+ [[ "${list}" == "${blacklist}" && -z "${type}" ]] && type="--blacklist-only"
+ # 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 -e " ${INFO} Removing $1 from $listname..."
+ # /I flag: search case-insensitive
+ sed -i "/${domain}/Id" "${list}"
+ reload=true
+ else
+ if [[ "${verbose}" == true ]]; then
+ echo -e " ${INFO} ${1} does not exist in ${listname}, no need to remove!"
fi
- elif [[ "${list}" == "${wildcardlist}" ]]; then
- bool=true
- # Is it in the list?
- grep -e "address=\/${domain}\/" "${wildcardlist}" > /dev/null 2>&1 || bool=false
- if [[ "${bool}" == true ]]; then
- # Remove it from the other one
- echo "::: Removing $1 from $list..."
- # /I flag: search case-insensitive
- sed -i "/address=\/${domain}/Id" "${list}"
- reload=true
- else
- if [[ "${verbose}" == true ]]; then
- echo "::: ${1} does not exist in ${list}, no need to remove!"
- fi
+ fi
+ elif [[ "${list}" == "${wildcardlist}" ]]; then
+ [[ -z "${type}" ]] && type="--wildcard-only"
+ bool=true
+ # Is it in the list?
+ grep -e "address=\/${domain}\/" "${wildcardlist}" > /dev/null 2>&1 || bool=false
+ if [[ "${bool}" == true ]]; then
+ # Remove it from the other one
+ echo -e " ${INFO} Removing $1 from $listname..."
+ # /I flag: search case-insensitive
+ sed -i "/address=\/${domain}/Id" "${list}"
+ reload=true
+ else
+ if [[ "${verbose}" == true ]]; then
+ echo -e " ${INFO} ${1} does not exist in ${listname}, no need to remove!"
fi
fi
+ fi
}
+# Update Gravity
Reload() {
- # Reload hosts file
- pihole -g -sd
+ echo ""
+ pihole -g --skip-download "${type:-}"
}
Displaylist() {
- if [[ "${listMain}" == "${whitelist}" ]]; then
- string="gravity resistant domains"
+ if [[ -f ${listMain} ]]; then
+ if [[ "${listMain}" == "${whitelist}" ]]; then
+ string="gravity resistant domains"
+ else
+ string="domains caught in the sinkhole"
+ fi
+ verbose=false
+ echo -e "Displaying $string:\n"
+ count=1
+ while IFS= read -r RD; do
+ echo " ${count}: ${RD}"
+ count=$((count+1))
+ done < "${listMain}"
else
- string="domains caught in the sinkhole"
+ echo -e " ${COL_LIGHT_RED}${listMain} does not exist!${COL_NC}"
fi
- verbose=false
- echo -e "Displaying $string:\n"
- count=1
- while IFS= read -r RD; do
- echo "${count}: ${RD}"
- count=$((count+1))
- done < "${listMain}"
exit 0;
}
+NukeList() {
+ if [[ -f "${listMain}" ]]; then
+ # Back up original list
+ cp "${listMain}" "${listMain}.bck~"
+ # Empty out file
+ echo "" > "${listMain}"
+ fi
+}
+
for var in "$@"; do
case "${var}" in
"-w" | "whitelist" ) listMain="${whitelist}"; listAlt="${blacklist}";;
@@ -204,10 +239,10 @@ for var in "$@"; do
"-wild" | "wildcard" ) listMain="${wildcardlist}";;
"-nr"| "--noreload" ) reload=false;;
"-d" | "--delmode" ) addmode=false;;
- "-f" | "--force" ) force=true;;
"-q" | "--quiet" ) verbose=false;;
"-h" | "--help" ) helpFunc;;
"-l" | "--list" ) Displaylist;;
+ "--nuke" ) NukeList;;
* ) HandleOther "${var}";;
esac
done
@@ -220,6 +255,7 @@ fi
PoplistFile
-if ${reload}; then
- Reload
+if [[ "${reload}" != false ]]; then
+ # Ensure that "restart" is used for Wildcard updates
+ Reload "${reload}"
fi
diff --git a/advanced/Scripts/piholeCheckout.sh b/advanced/Scripts/piholeCheckout.sh
index e2c0ab11..9e97c69c 100644
--- a/advanced/Scripts/piholeCheckout.sh
+++ b/advanced/Scripts/piholeCheckout.sh
@@ -3,13 +3,14 @@
# (c) 2017 Pi-hole, LLC (https://pi-hole.net)
# Network-wide ad blocking via your own hardware.
#
-# Switch Pi-hole subsystems to a different Github branch
+# Switch Pi-hole subsystems to a different Github branch.
#
# This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license.
readonly PI_HOLE_FILES_DIR="/etc/.pihole"
-PH_TEST="true" source "${PI_HOLE_FILES_DIR}/automated install/basic-install.sh"
+PH_TEST="true"
+source "${PI_HOLE_FILES_DIR}/automated install/basic-install.sh"
# webInterfaceGitUrl set in basic-install.sh
# webInterfaceDir set in basic-install.sh
@@ -20,9 +21,100 @@ PH_TEST="true" source "${PI_HOLE_FILES_DIR}/automated install/basic-install.sh"
source "${setupVars}"
update="false"
-# Colour codes
-red="\e[1;31m"
-def="\e[0m"
+coltable="/opt/pihole/COL_TABLE"
+source ${coltable}
+
+check_download_exists() {
+ status=$(curl --head --silent "https://ftl.pi-hole.net/${1}" | head -n 1)
+ if grep -q "404" <<< "$status"; then
+ return 1
+ else
+ return 0
+ fi
+}
+
+FTLinstall() {
+ # Download and install FTL binary
+ local binary
+ binary="${1}"
+ local path
+ path="${2}"
+ local str
+ str="Installing FTL"
+ echo -ne " ${INFO} ${str}..."
+
+ if curl -sSL --fail "https://ftl.pi-hole.net/${path}" -o "/tmp/${binary}"; then
+ # Get sha1 of the binary we just downloaded for verification.
+ curl -sSL --fail "https://ftl.pi-hole.net/${path}.sha1" -o "/tmp/${binary}.sha1"
+ # Check if we just downloaded text, or a binary file.
+ cd /tmp || return 1
+ if sha1sum --status --quiet -c "${binary}".sha1; then
+ echo -n "transferred... "
+ stop_service pihole-FTL &> /dev/null
+ install -T -m 0755 "/tmp/${binary}" "/usr/bin/pihole-FTL"
+ rm "/tmp/${binary}" "/tmp/${binary}.sha1"
+ start_service pihole-FTL &> /dev/null
+ echo -e "${OVER} ${TICK} ${str}"
+ return 0
+ else
+ echo -e "${OVER} ${CROSS} ${str}"
+ echo -e " ${COL_LIGHT_RED}Error: Download of binary from ftl.pi-hole.net failed${COL_NC}"
+ return 1
+ fi
+ else
+ echo -e "${OVER} ${CROSS} ${str}"
+ echo -e " ${COL_LIGHT_RED}Error: URL not found${COL_NC}"
+ fi
+}
+
+get_binary_name() {
+ local machine
+ machine=$(uname -m)
+
+ local str
+ str="Detecting architecture"
+ echo -ne " ${INFO} ${str}..."
+ if [[ "${machine}" == "arm"* || "${machine}" == *"aarch"* ]]; then
+ # ARM
+ local rev
+ rev=$(uname -m | sed "s/[^0-9]//g;")
+ local lib
+ lib=$(ldd /bin/ls | grep -E '^\s*/lib' | awk '{ print $1 }')
+ if [[ "${lib}" == "/lib/ld-linux-aarch64.so.1" ]]; then
+ echo -e "${OVER} ${TICK} Detected ARM-aarch64 architecture"
+ binary="pihole-FTL-aarch64-linux-gnu"
+ elif [[ "${lib}" == "/lib/ld-linux-armhf.so.3" ]]; then
+ if [[ "$rev" -gt "6" ]]; then
+ echo -e "${OVER} ${TICK} Detected ARM-hf architecture (armv7+)"
+ binary="pihole-FTL-arm-linux-gnueabihf"
+ else
+ echo -e "${OVER} ${TICK} Detected ARM-hf architecture (armv6 or lower) Using ARM binary"
+ binary="pihole-FTL-arm-linux-gnueabi"
+ fi
+ else
+ echo -e "${OVER} ${TICK} Detected ARM architecture"
+ binary="pihole-FTL-arm-linux-gnueabi"
+ fi
+ elif [[ "${machine}" == "ppc" ]]; then
+ # PowerPC
+ echo -e "${OVER} ${TICK} Detected PowerPC architecture"
+ binary="pihole-FTL-powerpc-linux-gnu"
+ elif [[ "${machine}" == "x86_64" ]]; then
+ # 64bit
+ echo -e "${OVER} ${TICK} Detected x86_64 architecture"
+ binary="pihole-FTL-linux-x86_64"
+ else
+ # Something else - we try to use 32bit executable and warn the user
+ if [[ ! "${machine}" == "i686" ]]; then
+ echo -e "${OVER} ${CROSS} ${str}...
+ ${COL_LIGHT_RED}Not able to detect architecture (unknown: ${machine}), trying 32bit executable
+ Contact support if you experience issues (e.g: FTL not running)${COL_NC}"
+ else
+ echo -e "${OVER} ${TICK} Detected 32bit (i686) architecture"
+ fi
+ binary="pihole-FTL-linux-x86_32"
+ fi
+}
fully_fetch_repo() {
# Add upstream branches to shallow clone
@@ -40,61 +132,78 @@ fully_fetch_repo() {
get_available_branches() {
# Return available branches
- local directory="${1}"
+ local directory
+ directory="${1}"
+ local output
cd "${directory}" || return 1
- # Get reachable remote branches
- git remote show origin | grep 'tracked' | sed 's/tracked//;s/ //g'
+ # Get reachable remote branches, but store STDERR as STDOUT variable
+ output=$( { git remote show origin | grep 'tracked' | sed 's/tracked//;s/ //g'; } 2>&1 )
+ echo "$output"
return
}
-
fetch_checkout_pull_branch() {
# Check out specified branch
- local directory="${1}"
- local branch="${2}"
+ local directory
+ directory="${1}"
+ local branch
+ branch="${2}"
# Set the reference for the requested branch, fetch, check it put and pull it
- cd "${directory}"
+ cd "${directory}" || return 1
git remote set-branches origin "${branch}" || return 1
git stash --all --quiet &> /dev/null || true
- git clean --force -d || true
+ git clean --quiet --force -d || true
git fetch --quiet || return 1
checkout_pull_branch "${directory}" "${branch}" || return 1
}
checkout_pull_branch() {
# Check out specified branch
- local directory="${1}"
- local branch="${2}"
+ local directory
+ directory="${1}"
+ local branch
+ branch="${2}"
local oldbranch
cd "${directory}" || return 1
oldbranch="$(git symbolic-ref HEAD)"
- git checkout "${branch}" || return 1
+ str="Switching to branch: '${branch}' from '${oldbranch}'"
+ echo -ne " ${INFO} $str"
+ git checkout "${branch}" --quiet || return 1
+ echo -e "${OVER} ${TICK} $str"
- if [ "$(git diff "${oldbranch}" | grep -c "^")" -gt "0" ]; then
+
+ if [[ "$(git diff "${oldbranch}" | grep -c "^")" -gt "0" ]]; then
update="true"
fi
- git pull || return 1
+ git_pull=$(git pull || return 1)
+
+ if [[ "$git_pull" == *"up-to-date"* ]]; then
+ echo -e " ${INFO} ${git_pull}"
+ else
+ echo -e "$git_pull\\n"
+ fi
+
return 0
}
warning1() {
echo " Please note that changing branches severely alters your Pi-hole subsystems"
echo " Features that work on the master branch, may not on a development branch"
- echo -e " ${red}This feature is NOT supported unless a Pi-hole developer explicitly asks!${def}"
+ echo -e " ${COL_LIGHT_RED}This feature is NOT supported unless a Pi-hole developer explicitly asks!${COL_NC}"
read -r -p " Have you read and understood this? [y/N] " response
- case ${response} in
+ case "${response}" in
[yY][eE][sS]|[yY])
- echo "::: Continuing with branch change."
+ echo ""
return 0
;;
*)
- echo "::: Branch change has been cancelled."
+ echo -e "\\n ${INFO} Branch change has been cancelled"
return 1
;;
esac
@@ -107,24 +216,23 @@ checkout() {
# Avoid globbing
set -f
- #This is unlikely
+ # This is unlikely
if ! is_repo "${PI_HOLE_FILES_DIR}" ; then
- echo "::: Critical Error: Core Pi-hole repo is missing from system!"
- echo "::: Please re-run install script from https://github.com/pi-hole/pi-hole"
+ echo -e " ${COL_LIGHT_RED}Error: Core Pi-hole repo is missing from system!
+ Please re-run install script from https://github.com/pi-hole/pi-hole${COL_NC}"
exit 1;
fi
- if [[ ${INSTALL_WEB} == "true" ]]; then
+ if [[ "${INSTALL_WEB}" == "true" ]]; then
if ! is_repo "${webInterfaceDir}" ; then
- echo "::: Critical Error: Web Admin repo is missing from system!"
- echo "::: Please re-run install script from https://github.com/pi-hole/pi-hole"
+ echo -e " ${COL_LIGHT_RED}Error: Web Admin repo is missing from system!
+ Please re-run install script from https://github.com/pi-hole/pi-hole${COL_NC}"
exit 1;
fi
fi
if [[ -z "${1}" ]]; then
- echo "::: No option detected. Please use 'pihole checkout <master|dev>'."
- echo "::: Or enter the repository and branch you would like to check out:"
- echo "::: 'pihole checkout <web|core> <branchname>'"
+ echo -e " ${COL_LIGHT_RED}Invalid option${COL_NC}
+ Try 'pihole checkout --help' for more information."
exit 1
fi
@@ -134,72 +242,117 @@ checkout() {
if [[ "${1}" == "dev" ]] ; then
# Shortcut to check out development branches
- echo "::: Shortcut \"dev\" detected - checking out development / devel branches ..."
- echo "::: Pi-hole core"
- fetch_checkout_pull_branch "${PI_HOLE_FILES_DIR}" "development" || { echo "Unable to pull Core developement branch"; exit 1; }
- if [[ ${INSTALL_WEB} == "true" ]]; then
- echo "::: Web interface"
- fetch_checkout_pull_branch "${webInterfaceDir}" "devel" || { echo "Unable to pull Web development branch"; exit 1; }
+ echo -e " ${INFO} Shortcut \"dev\" detected - checking out development / devel branches..."
+ echo ""
+ echo -e " ${INFO} Pi-hole Core"
+ fetch_checkout_pull_branch "${PI_HOLE_FILES_DIR}" "development" || { echo " ${CROSS} Unable to pull Core developement branch"; exit 1; }
+ if [[ "${INSTALL_WEB}" == "true" ]]; then
+ echo ""
+ echo -e " ${INFO} Web interface"
+ fetch_checkout_pull_branch "${webInterfaceDir}" "devel" || { echo " ${CROSS} Unable to pull Web development branch"; exit 1; }
fi
- echo "::: done!"
+ #echo -e " ${TICK} Pi-hole Core"
+
+ get_binary_name
+ local path
+ path="development/${binary}"
+ FTLinstall "${binary}" "${path}"
elif [[ "${1}" == "master" ]] ; then
# Shortcut to check out master branches
- echo "::: Shortcut \"master\" detected - checking out master branches ..."
- echo "::: Pi-hole core"
- fetch_checkout_pull_branch "${PI_HOLE_FILES_DIR}" "master" || { echo "Unable to pull Core master branch"; exit 1; }
+ echo -e " ${INFO} Shortcut \"master\" detected - checking out master branches..."
+ echo -e " ${INFO} Pi-hole core"
+ fetch_checkout_pull_branch "${PI_HOLE_FILES_DIR}" "master" || { echo " ${CROSS} Unable to pull Core master branch"; exit 1; }
if [[ ${INSTALL_WEB} == "true" ]]; then
- echo "::: Web interface"
- fetch_checkout_pull_branch "${webInterfaceDir}" "master" || { echo "Unable to pull web master branch"; exit 1; }
+ echo -e " ${INFO} Web interface"
+ fetch_checkout_pull_branch "${webInterfaceDir}" "master" || { echo " ${CROSS} Unable to pull Web master branch"; exit 1; }
fi
- echo "::: done!"
+ #echo -e " ${TICK} Web Interface"
+ get_binary_name
+ local path
+ path="master/${binary}"
+ FTLinstall "${binary}" "${path}"
elif [[ "${1}" == "core" ]] ; then
- echo -n "::: Fetching remote branches for Pi-hole core from ${piholeGitUrl} ... "
+ str="Fetching branches from ${piholeGitUrl}"
+ echo -ne " ${INFO} $str"
if ! fully_fetch_repo "${PI_HOLE_FILES_DIR}" ; then
- echo "::: Fetching all branches for Pi-hole core repo failed!"
+ echo -e "${OVER} ${CROSS} $str"
exit 1
fi
corebranches=($(get_available_branches "${PI_HOLE_FILES_DIR}"))
- echo " done!"
- echo "::: ${#corebranches[@]} branches available"
- echo ":::"
- # Have to user chosing the branch he wants
+
+ if [[ "${corebranches[*]}" == *"master"* ]]; then
+ echo -e "${OVER} ${TICK} $str
+ ${INFO} ${#corebranches[@]} branches available for Pi-hole Core"
+ else
+ # Print STDERR output from get_available_branches
+ echo -e "${OVER} ${CROSS} $str\\n\\n${corebranches[*]}"
+ exit 1
+ fi
+
+ echo ""
+ # Have the user choose the branch they want
if ! (for e in "${corebranches[@]}"; do [[ "$e" == "${2}" ]] && exit 0; done); then
- echo "::: Requested branch \"${2}\" is not available!"
- echo "::: Available branches for core are:"
- for e in "${corebranches[@]}"; do echo "::: $e"; done
+ echo -e " ${INFO} Requested branch \"${2}\" is not available"
+ echo -e " ${INFO} Available branches for Core are:"
+ for e in "${corebranches[@]}"; do echo " - $e"; done
exit 1
fi
checkout_pull_branch "${PI_HOLE_FILES_DIR}" "${2}"
- elif [[ "${1}" == "web" && "${INSTALL_WEB}" == "true" ]] ; then
- echo -n "::: Fetching remote branches for the web interface from ${webInterfaceGitUrl} ... "
+ elif [[ "${1}" == "web" ]] && [[ "${INSTALL_WEB}" == "true" ]] ; then
+ str="Fetching branches from ${webInterfaceGitUrl}"
+ echo -ne " ${INFO} $str"
if ! fully_fetch_repo "${webInterfaceDir}" ; then
- echo "::: Fetching all branches for Pi-hole web interface repo failed!"
+ echo -e "${OVER} ${CROSS} $str"
exit 1
fi
webbranches=($(get_available_branches "${webInterfaceDir}"))
- echo " done!"
- echo "::: ${#webbranches[@]} branches available"
- echo ":::"
- # Have to user chosing the branch he wants
+
+ if [[ "${webbranches[*]}" == *"master"* ]]; then
+ echo -e "${OVER} ${TICK} $str
+ ${INFO} ${#webbranches[@]} branches available for Web Admin"
+ else
+ # Print STDERR output from get_available_branches
+ echo -e "${OVER} ${CROSS} $str\\n\\n${webbranches[*]}"
+ exit 1
+ fi
+
+ echo ""
+ # Have the user choose the branch they want
if ! (for e in "${webbranches[@]}"; do [[ "$e" == "${2}" ]] && exit 0; done); then
- echo "::: Requested branch \"${2}\" is not available!"
- echo "::: Available branches for web are:"
- for e in "${webbranches[@]}"; do echo "::: $e"; done
+ echo -e " ${INFO} Requested branch \"${2}\" is not available"
+ echo -e " ${INFO} Available branches for Web Admin are:"
+ for e in "${webbranches[@]}"; do echo " - $e"; done
exit 1
fi
checkout_pull_branch "${webInterfaceDir}" "${2}"
+ elif [[ "${1}" == "ftl" ]] ; then
+ get_binary_name
+ local path
+ path="${2}/${binary}"
+
+ if check_download_exists "$path"; then
+ echo " ${TICK} Branch ${2} exists"
+ FTLinstall "${binary}" "${path}"
+ else
+ echo " ${CROSS} Requested branch \"${2}\" is not available"
+ ftlbranches=( $(git ls-remote https://github.com/pi-hole/ftl | grep 'heads' | sed 's/refs\/heads\///;s/ //g' | awk '{print $2}') )
+ echo -e " ${INFO} Available branches for FTL are:"
+ for e in "${ftlbranches[@]}"; do echo " - $e"; done
+ exit 1
+ fi
+
else
- echo "::: Requested option \"${1}\" is not available!"
+ echo -e " ${INFO} Requested option \"${1}\" is not available"
exit 1
fi
# Force updating everything
- if [[ ! "${1}" == "web" && "${update}" == "true" ]]; then
- echo "::: Running installer to upgrade your installation"
+ if [[ ( ! "${1}" == "web" && ! "${1}" == "ftl" ) && "${update}" == "true" ]]; then
+ echo -e " ${INFO} Running installer to upgrade your installation"
if "${PI_HOLE_FILES_DIR}/automated install/basic-install.sh" --unattended; then
exit 0
else
- echo "Unable to complete update, contact Pi-hole"
+ echo -e " ${COL_LIGHT_RED} Error: Unable to complete update, please contact support${COL_NC}"
exit 1
fi
fi
diff --git a/advanced/Scripts/piholeDebug.sh b/advanced/Scripts/piholeDebug.sh
index 8020cc80..d69c5e4d 100755
--- a/advanced/Scripts/piholeDebug.sh
+++ b/advanced/Scripts/piholeDebug.sh
@@ -9,534 +9,1156 @@
# Please see LICENSE file for your rights under this license.
-
+# -e option instructs bash to immediately exit if any command [1] has a non-zero exit status
+# -u a reference to any variable you haven't previously defined
+# with the exceptions of $* and $@ - is an error, and causes the program to immediately exit
+# -o pipefail prevents errors in a pipeline from being masked. If any command in a pipeline fails,
+# that return code will be used as the return code of the whole pipeline. By default, the
+# pipeline's return code is that of the last command - even if it succeeds
set -o pipefail
+#IFS=$'\n\t'
######## GLOBAL VARS ########
-VARSFILE="/etc/pihole/setupVars.conf"
-DEBUG_LOG="/var/log/pihole_debug.log"
-DNSMASQFILE="/etc/dnsmasq.conf"
-DNSMASQCONFDIR="/etc/dnsmasq.d/*"
-LIGHTTPDFILE="/etc/lighttpd/lighttpd.conf"
-LIGHTTPDERRFILE="/var/log/lighttpd/error.log"
-GRAVITYFILE="/etc/pihole/gravity.list"
-WHITELISTFILE="/etc/pihole/whitelist.txt"
-BLACKLISTFILE="/etc/pihole/blacklist.txt"
-ADLISTFILE="/etc/pihole/adlists.list"
-PIHOLELOG="/var/log/pihole.log"
-PIHOLEGITDIR="/etc/.pihole/"
-ADMINGITDIR="/var/www/html/admin/"
-WHITELISTMATCHES="/tmp/whitelistmatches.list"
-readonly FTLLOG="/var/log/pihole-FTL.log"
-
-TIMEOUT=60
-# Header info and introduction
-cat << EOM
-::: Beginning Pi-hole debug at $(date)!
-:::
-::: This process collects information from your Pi-hole, and optionally uploads
-::: it to a unique and random directory on tricorder.pi-hole.net.
-:::
-::: NOTE: All log files auto-delete after 48 hours and ONLY the Pi-hole developers
-::: can access your data via the given token. We have taken these extra steps to
-::: secure your data and will work to further reduce any personal information gathered.
-:::
-::: Please read and note any issues, and follow any directions advised during this process.
-EOM
-
-source ${VARSFILE}
-
-### Private functions exist here ###
-log_write() {
- echo "${@}" >&3
-}
+# These variables would normally be next to the other files
+# but we need them to be first in order to get the colors needed for the script output
+PIHOLE_SCRIPTS_DIRECTORY="/opt/pihole"
+PIHOLE_COLTABLE_FILE="${PIHOLE_SCRIPTS_DIRECTORY}/COL_TABLE"
+
+# These provide the colors we need for making the log more readable
+if [[ -f ${PIHOLE_COLTABLE_FILE} ]]; then
+ source ${PIHOLE_COLTABLE_FILE}
+else
+ COL_NC='\e[0m' # No Color
+ COL_RED='\e[1;91m'
+ COL_GREEN='\e[1;32m'
+ COL_YELLOW='\e[1;33m'
+ COL_PURPLE='\e[1;35m'
+ COL_CYAN='\e[0;36m'
+ TICK="[${COL_GREEN}✓${COL_NC}]"
+ CROSS="[${COL_RED}✗${COL_NC}]"
+ INFO="[i]"
+ OVER="\r\033[K"
+fi
-log_echo() {
- case ${1} in
- -n)
- echo -n "::: ${2}"
- log_write "${2}"
- ;;
- -r)
- echo "::: ${2}"
- log_write "${2}"
- ;;
- -l)
- echo "${2}"
- log_write "${2}"
- ;;
- *)
- echo "::: ${1}"
- log_write "${1}"
- esac
+OBFUSCATED_PLACEHOLDER="<DOMAIN OBFUSCATED>"
+
+# FAQ URLs for use in showing the debug log
+FAQ_UPDATE_PI_HOLE="${COL_CYAN}https://discourse.pi-hole.net/t/how-do-i-update-pi-hole/249${COL_NC}"
+FAQ_CHECKOUT_COMMAND="${COL_CYAN}https://discourse.pi-hole.net/t/the-pihole-command-with-examples/738#checkout${COL_NC}"
+FAQ_HARDWARE_REQUIREMENTS="${COL_CYAN}https://discourse.pi-hole.net/t/hardware-software-requirements/273${COL_NC}"
+FAQ_HARDWARE_REQUIREMENTS_PORTS="${COL_CYAN}https://discourse.pi-hole.net/t/hardware-software-requirements/273#ports${COL_NC}"
+FAQ_GATEWAY="${COL_CYAN}https://discourse.pi-hole.net/t/why-is-a-default-gateway-important-for-pi-hole/3546${COL_NC}"
+FAQ_ULA="${COL_CYAN}https://discourse.pi-hole.net/t/use-ipv6-ula-addresses-for-pi-hole/2127${COL_NC}"
+FAQ_FTL_COMPATIBILITY="${COL_CYAN}https://github.com/pi-hole/FTL#compatibility-list${COL_NC}"
+FAQ_BAD_ADDRESS="${COL_CYAN}https://discourse.pi-hole.net/t/why-do-i-see-bad-address-at-in-pihole-log/3972${COL_NC}"
+
+# Other URLs we may use
+FORUMS_URL="${COL_CYAN}https://discourse.pi-hole.net${COL_NC}"
+TRICORDER_CONTEST="${COL_CYAN}https://pi-hole.net/2016/11/07/crack-our-medical-tricorder-win-a-raspberry-pi-3/${COL_NC}"
+
+# Port numbers used for uploading the debug log
+TRICORDER_NC_PORT_NUMBER=9999
+TRICORDER_SSL_PORT_NUMBER=9998
+
+# Directories required by Pi-hole
+# https://discourse.pi-hole.net/t/what-files-does-pi-hole-use/1684
+CORE_GIT_DIRECTORY="/etc/.pihole"
+CRON_D_DIRECTORY="/etc/cron.d"
+DNSMASQ_D_DIRECTORY="/etc/dnsmasq.d"
+PIHOLE_DIRECTORY="/etc/pihole"
+PIHOLE_SCRIPTS_DIRECTORY="/opt/pihole"
+BIN_DIRECTORY="/usr/local/bin"
+RUN_DIRECTORY="/run"
+LOG_DIRECTORY="/var/log"
+WEB_SERVER_LOG_DIRECTORY="${LOG_DIRECTORY}/lighttpd"
+WEB_SERVER_CONFIG_DIRECTORY="/etc/lighttpd"
+HTML_DIRECTORY="/var/www/html"
+WEB_GIT_DIRECTORY="${HTML_DIRECTORY}/admin"
+BLOCK_PAGE_DIRECTORY="${HTML_DIRECTORY}/pihole"
+
+# Files required by Pi-hole
+# https://discourse.pi-hole.net/t/what-files-does-pi-hole-use/1684
+PIHOLE_CRON_FILE="${CRON_D_DIRECTORY}/pihole"
+
+PIHOLE_DNS_CONFIG_FILE="${DNSMASQ_D_DIRECTORY}/01-pihole.conf"
+PIHOLE_DHCP_CONFIG_FILE="${DNSMASQ_D_DIRECTORY}/02-pihole-dhcp.conf"
+PIHOLE_WILDCARD_CONFIG_FILE="${DNSMASQ_D_DIRECTORY}/03-wildcard.conf"
+
+WEB_SERVER_CONFIG_FILE="${WEB_SERVER_CONFIG_DIRECTORY}/lighttpd.conf"
+WEB_SERVER_CUSTOM_CONFIG_FILE="${WEB_SERVER_CONFIG_DIRECTORY}/external.conf"
+
+PIHOLE_DEFAULT_AD_LISTS="${PIHOLE_DIRECTORY}/adlists.default"
+PIHOLE_USER_DEFINED_AD_LISTS="${PIHOLE_DIRECTORY}/adlists.list"
+PIHOLE_BLACKLIST_FILE="${PIHOLE_DIRECTORY}/blacklist.txt"
+PIHOLE_BLOCKLIST_FILE="${PIHOLE_DIRECTORY}/gravity.list"
+PIHOLE_INSTALL_LOG_FILE="${PIHOLE_DIRECTORY}/install.log"
+PIHOLE_RAW_BLOCKLIST_FILES=${PIHOLE_DIRECTORY}/list.*
+PIHOLE_LOCAL_HOSTS_FILE="${PIHOLE_DIRECTORY}/local.list"
+PIHOLE_LOGROTATE_FILE="${PIHOLE_DIRECTORY}/logrotate"
+PIHOLE_SETUP_VARS_FILE="${PIHOLE_DIRECTORY}/setupVars.conf"
+PIHOLE_WHITELIST_FILE="${PIHOLE_DIRECTORY}/whitelist.txt"
+
+PIHOLE_COMMAND="${BIN_DIRECTORY}/pihole"
+PIHOLE_COLTABLE_FILE="${BIN_DIRECTORY}/COL_TABLE"
+
+FTL_PID="${RUN_DIRECTORY}/pihole-FTL.pid"
+FTL_PORT="${RUN_DIRECTORY}/pihole-FTL.port"
+
+PIHOLE_LOG="${LOG_DIRECTORY}/pihole.log"
+PIHOLE_LOG_GZIPS=${LOG_DIRECTORY}/pihole.log.[0-9].*
+PIHOLE_DEBUG_LOG="${LOG_DIRECTORY}/pihole_debug.log"
+PIHOLE_DEBUG_LOG_SANITIZED="${LOG_DIRECTORY}/pihole_debug-sanitized.log"
+PIHOLE_FTL_LOG="${LOG_DIRECTORY}/pihole-FTL.log"
+
+PIHOLE_WEB_SERVER_ACCESS_LOG_FILE="${WEB_SERVER_LOG_DIRECTORY}/access.log"
+PIHOLE_WEB_SERVER_ERROR_LOG_FILE="${WEB_SERVER_LOG_DIRECTORY}/error.log"
+
+# An array of operating system "pretty names" that we officialy support
+# We can loop through the array at any time to see if it matches a value
+SUPPORTED_OS=("Raspbian" "Ubuntu" "Fedora" "Debian" "CentOS")
+
+# Store Pi-hole's processes in an array for easy use and parsing
+PIHOLE_PROCESSES=( "dnsmasq" "lighttpd" "pihole-FTL" )
+
+# Store the required directories in an array so it can be parsed through
+REQUIRED_DIRECTORIES=(${CORE_GIT_DIRECTORY}
+${CRON_D_DIRECTORY}
+${DNSMASQ_D_DIRECTORY}
+${PIHOLE_DIRECTORY}
+${PIHOLE_SCRIPTS_DIRECTORY}
+${BIN_DIRECTORY}
+${RUN_DIRECTORY}
+${LOG_DIRECTORY}
+${WEB_SERVER_LOG_DIRECTORY}
+${WEB_SERVER_CONFIG_DIRECTORY}
+${HTML_DIRECTORY}
+${WEB_GIT_DIRECTORY}
+${BLOCK_PAGE_DIRECTORY})
+
+# Store the required directories in an array so it can be parsed through
+mapfile -t array <<< "$var"
+REQUIRED_FILES=(${PIHOLE_CRON_FILE}
+${PIHOLE_DNS_CONFIG_FILE}
+${PIHOLE_DHCP_CONFIG_FILE}
+${PIHOLE_WILDCARD_CONFIG_FILE}
+${WEB_SERVER_CONFIG_FILE}
+${PIHOLE_DEFAULT_AD_LISTS}
+${PIHOLE_USER_DEFINED_AD_LISTS}
+${PIHOLE_BLACKLIST_FILE}
+${PIHOLE_BLOCKLIST_FILE}
+${PIHOLE_INSTALL_LOG_FILE}
+${PIHOLE_RAW_BLOCKLIST_FILES}
+${PIHOLE_LOCAL_HOSTS_FILE}
+${PIHOLE_LOGROTATE_FILE}
+${PIHOLE_SETUP_VARS_FILE}
+${PIHOLE_WHITELIST_FILE}
+${PIHOLE_COMMAND}
+${PIHOLE_COLTABLE_FILE}
+${FTL_PID}
+${FTL_PORT}
+${PIHOLE_LOG}
+${PIHOLE_LOG_GZIPS}
+${PIHOLE_DEBUG_LOG}
+${PIHOLE_FTL_LOG}
+${PIHOLE_WEB_SERVER_ACCESS_LOG_FILE}
+${PIHOLE_WEB_SERVER_ERROR_LOG_FILE})
+
+DISCLAIMER="This process collects information from your Pi-hole, and optionally uploads it to a unique and random directory on tricorder.pi-hole.net.
+
+The intent of this script is to allow users to self-diagnose their installations. This is accomplished by running tests against our software and providing the user with links to FAQ articles when a problem is detected. Since we are a small team and Pi-hole has been growing steadily, it is our hope that this will help us spend more time on development.
+
+NOTE: All log files auto-delete after 48 hours and ONLY the Pi-hole developers can access your data via the given token. We have taken these extra steps to secure your data and will work to further reduce any personal information gathered.
+"
+
+show_disclaimer(){
+ log_write "${DISCLAIMER}"
}
-header_write() {
- log_echo ""
- log_echo "---= ${1}"
- log_write ""
+source_setup_variables() {
+ # Display the current test that is running
+ log_write "\n${COL_PURPLE}*** [ INITIALIZING ]${COL_NC} Sourcing setup variables"
+ # If the variable file exists,
+ if ls "${PIHOLE_SETUP_VARS_FILE}" 1> /dev/null 2>&1; then
+ log_write "${INFO} Sourcing ${PIHOLE_SETUP_VARS_FILE}...";
+ # source it
+ source ${PIHOLE_SETUP_VARS_FILE}
+ else
+ # If it can't, show an error
+ log_write "${PIHOLE_SETUP_VARS_FILE} ${COL_RED}does not exist or cannot be read.${COL_NC}"
+ fi
}
-file_parse() {
- while read -r line; do
- if [ ! -z "${line}" ]; then
- [[ "${line}" =~ ^#.*$ || ! "${line}" || "${line}" == "WEBPASSWORD="* ]] && continue
- log_write "${line}"
- fi
- done < "${1}"
- log_write ""
+make_temporary_log() {
+ # Create a random temporary file for the log
+ TEMPLOG=$(mktemp /tmp/pihole_temp.XXXXXX)
+ # Open handle 3 for templog
+ # https://stackoverflow.com/questions/18460186/writing-outputs-to-log-file-and-console
+ exec 3>"$TEMPLOG"
+ # Delete templog, but allow for addressing via file handle
+ # This lets us write to the log without having a temporary file on the drive, which
+ # is meant to be a security measure so there is not a lingering file on the drive during the debug process
+ rm "$TEMPLOG"
}
-block_parse() {
- log_write "${1}"
+log_write() {
+ # echo arguments to both the log and the console
+ echo -e "${@}" | tee -a /proc/$$/fd/3
}
-lsof_parse() {
- local user
- local process
-
- user=$(echo ${1} | cut -f 3 -d ' ' | cut -c 2-)
- process=$(echo ${1} | cut -f 2 -d ' ' | cut -c 2-)
- [[ ${2} -eq ${process} ]] \
- && echo "::: Correctly configured." \
- || log_echo "::: Failure: Incorrectly configured daemon."
-
- log_write "Found user ${user} with process ${process}"
+copy_to_debug_log() {
+ # Copy the contents of file descriptor 3 into the debug log
+ cat /proc/$$/fd/3 > "${PIHOLE_DEBUG_LOG}"
+ # Since we use color codes such as '\e[1;33m', they should be removed before being
+ # uploaded to our server, since it can't properly display in color
+ # This is accomplished by use sed to remove characters matching that patter
+ # The entire file is then copied over to a sanitized version of the log
+ sed 's/\[[0-9;]\{1,5\}m//g' > "${PIHOLE_DEBUG_LOG_SANITIZED}" <<< cat "${PIHOLE_DEBUG_LOG}"
}
+initiate_debug() {
+ # Clear the screen so the debug log is readable
+ clear
+ show_disclaimer
+ # Display that the debug process is beginning
+ log_write "${COL_PURPLE}*** [ INITIALIZING ]${COL_NC}"
+ # Timestamp the start of the log
+ log_write "${INFO} $(date "+%Y-%m-%d:%H:%M:%S") debug log has been initiated."
+}
-version_check() {
- header_write "Detecting Installed Package Versions:"
-
- local error_found
- local pi_hole_ver
- local pi_hole_branch
- local pi_hole_commit
- local admin_ver
- local admin_branch
- local admin_commit
- local light_ver
- local php_ver
- local status
- error_found=0
+# This is a function for visually displaying the curent test that is being run.
+# Accepts one variable: the name of what is being diagnosed
+# Colors do not show in the dasboard, but the icons do: [i], [✓], and [✗]
+echo_current_diagnostic() {
+ # Colors are used for visually distinguishing each test in the output
+ # These colors do not show in the GUI, but the formatting will
+ log_write "\n${COL_PURPLE}*** [ DIAGNOSING ]:${COL_NC} ${1}"
+}
- cd "${PIHOLEGITDIR}" &> /dev/null || \
- { status="Pi-hole git directory not found."; error_found=1; }
- if git status &> /dev/null; then
- pi_hole_ver=$(git describe --tags --abbrev=0)
- pi_hole_branch=$(git rev-parse --abbrev-ref HEAD)
- pi_hole_commit=$(git describe --long --dirty --tags --always)
- log_echo -r "Pi-hole: ${pi_hole_ver:-Untagged} (${pi_hole_branch:-Detached}:${pi_hole_commit})"
- else
- status=${status:-"Pi-hole repository damaged."}
- error_found=1
+compare_local_version_to_git_version() {
+ # The git directory to check
+ local git_dir="${1}"
+ # The named component of the project (Core or Web)
+ local pihole_component="${2}"
+ # If we are checking the Core versions,
+ if [[ "${pihole_component}" == "Core" ]]; then
+ # We need to search for "Pi-hole" when using pihole -v
+ local search_term="Pi-hole"
+ elif [[ "${pihole_component}" == "Web" ]]; then
+ # We need to search for "AdminLTE" so store it in a variable as well
+ local search_term="AdminLTE"
fi
- if [[ "${status}" ]]; then
- log_echo "${status}"
- unset status
+ # Display what we are checking
+ echo_current_diagnostic "${pihole_component} version"
+ # Store the error message in a variable in case we want to change and/or reuse it
+ local error_msg="git status failed"
+ # If the pihole git directory exists,
+ if [[ -d "${git_dir}" ]]; then
+ # move into it
+ cd "${git_dir}" || \
+ # If not, show an error
+ log_write "${COL_RED}Could not cd into ${git_dir}$COL_NC"
+ if git status &> /dev/null; then
+ # The current version the user is on
+ local remote_version
+ remote_version=$(git describe --tags --abbrev=0);
+ # What branch they are on
+ local remote_branch
+ remote_branch=$(git rev-parse --abbrev-ref HEAD);
+ # The commit they are on
+ local remote_commit
+ remote_commit=$(git describe --long --dirty --tags --always)
+ # echo this information out to the user in a nice format
+ # If the current version matches what pihole -v produces, the user is up-to-date
+ if [[ "${remote_version}" == "$(pihole -v | awk '/${search_term}/ {print $6}' | cut -d ')' -f1)" ]]; then
+ log_write "${TICK} ${pihole_component}: ${COL_GREEN}${remote_version}${COL_NC}"
+ # If not,
+ else
+ # echo the current version in yellow, signifying it's something to take a look at, but not a critical error
+ # Also add a URL to an FAQ
+ log_write "${INFO} ${pihole_component}: ${COL_YELLOW}${remote_version:-Untagged}${COL_NC} (${FAQ_UPDATE_PI_HOLE})"
+ fi
+
+ # If the repo is on the master branch, they are on the stable codebase
+ if [[ "${remote_branch}" == "master" ]]; then
+ # so the color of the text is green
+ log_write "${INFO} Branch: ${COL_GREEN}${remote_branch}${COL_NC}"
+ # If it is any other branch, they are in a developement branch
+ else
+ # So show that in yellow, signifying it's something to take a look at, but not a critical error
+ log_write "${INFO} Branch: ${COL_YELLOW}${remote_branch:-Detached}${COL_NC} (${FAQ_CHECKOUT_COMMAND})"
+ fi
+ # echo the current commit
+ log_write "${INFO} Commit: ${remote_commit}"
+ # If git status failed,
+ else
+ # Return an error message
+ log_write "${error_msg}"
+ # and exit with a non zero code
+ return 1
+ fi
+ else
+ :
fi
+}
- cd "${ADMINGITDIR}" || \
- { status="Pi-hole Dashboard git directory not found."; error_found=1; }
- if git status &> /dev/null; then
- admin_ver=$(git describe --tags --abbrev=0)
- admin_branch=$(git rev-parse --abbrev-ref HEAD)
- admin_commit=$(git describe --long --dirty --tags --always)
- log_echo -r "Pi-hole Dashboard: ${admin_ver:-Untagged} (${admin_branch:-Detached}:${admin_commit})"
+check_ftl_version() {
+ local ftl_name="FTL"
+ echo_current_diagnostic "${ftl_name} version"
+ # Use the built in command to check FTL's version
+ FTL_VERSION=$(pihole-FTL version)
+ # Compare the current FTL version to the remote version
+ if [[ "${FTL_VERSION}" == "$(pihole -v | awk '/FTL/ {print $6}' | cut -d ')' -f1)" ]]; then
+ # If they are the same, FTL is up-to-date
+ log_write "${TICK} ${ftl_name}: ${COL_GREEN}${FTL_VERSION}${COL_NC}"
else
- status=${status:-"Pi-hole Dashboard repository damaged."}
- error_found=1
+ # If not, show it in yellow, signifying there is an update
+ log_write "${TICK} ${ftl_name}: ${COL_YELLOW}${FTL_VERSION}${COL_NC} (${FAQ_UPDATE_PI_HOLE})"
fi
- if [[ "${status}" ]]; then
- log_echo "${status}"
- unset status
+}
+
+# Checks the core version of the Pi-hole codebase
+check_component_versions() {
+ # Check the Web version, branch, and commit
+ compare_local_version_to_git_version "${CORE_GIT_DIRECTORY}" "Core"
+ # Check the Web version, branch, and commit
+ compare_local_version_to_git_version "${WEB_GIT_DIRECTORY}" "Web"
+ # Check the FTL version
+ check_ftl_version
+}
+
+
+get_program_version() {
+ local program_name="${1}"
+ # Create a loval variable so this function can be safely reused
+ local program_version
+ echo_current_diagnostic "${program_name} version"
+ # Evalutate the program we are checking, if it is any of the ones below, show the version
+ case "${program_name}" in
+ "lighttpd") program_version="$(${program_name} -v |& head -n1 | cut -d '/' -f2 | cut -d ' ' -f1)"
+ ;;
+ "dnsmasq") program_version="$(${program_name} -v |& head -n1 | awk '{print $3}')"
+ ;;
+ "php") program_version="$(${program_name} -v |& head -n1 | cut -d '-' -f1 | cut -d ' ' -f2)"
+ ;;
+ # If a match is not found, show an error
+ *) echo "Unrecognized program";
+ esac
+ # If the program does not have a version (the variable is empty)
+ if [[ -z "${program_version}" ]]; then
+ # Display and error
+ log_write "${CROSS} ${COL_RED}${program_name} version could not be detected.${COL_NC}"
+ else
+ # Otherwise, display the version
+ log_write "${INFO} ${program_version}"
fi
+}
- if light_ver=$(lighttpd -v |& head -n1 | cut -d " " -f1); then
- log_echo -r "${light_ver}"
- else
- log_echo "lighttpd not installed."
- error_found=1
- fi
- if php_ver=$(php -v |& head -n1); then
- log_echo -r "${php_ver}"
- else
- log_echo "PHP not installed."
- error_found=1
- fi
+# These are the most critical dependencies of Pi-hole, so we check for them
+# and their versions, using the functions above.
+check_critical_program_versions() {
+ # Use the function created earlier and bundle them into one function that checks all the version numbers
+ get_program_version "dnsmasq"
+ get_program_version "lighttpd"
+ get_program_version "php"
+}
- return "${error_found}"
+is_os_supported() {
+ local os_to_check="${1}"
+ # Strip just the base name of the system using sed
+ the_os=$(echo ${os_to_check} | sed 's/ .*//')
+ # If the variable is one of our supported OSes,
+ case "${the_os}" in
+ # Print it in green
+ "Raspbian") log_write "${TICK} ${COL_GREEN}${os_to_check}${COL_NC}";;
+ "Ubuntu") log_write "${TICK} ${COL_GREEN}${os_to_check}${COL_NC}";;
+ "Fedora") log_write "${TICK} ${COL_GREEN}${os_to_check}${COL_NC}";;
+ "Debian") log_write "${TICK} ${COL_GREEN}${os_to_check}${COL_NC}";;
+ "CentOS") log_write "${TICK} ${COL_GREEN}${os_to_check}${COL_NC}";;
+ # If not, show it in red and link to our software requirements page
+ *) log_write "${CROSS} ${COL_RED}${os_to_check}${COL_NC} (${FAQ_HARDWARE_REQUIREMENTS})";
+ esac
}
-dir_check() {
- header_write "Detecting contents of ${1}:"
- for file in $1*; do
- header_write "File ${file} found"
- echo -n "::: Parsing..."
- file_parse "${file}"
- echo "done"
+get_distro_attributes() {
+ # Put the current Internal Field Separator into another variable so it can be restored later
+ OLD_IFS="$IFS"
+ # Store the distro info in an array and make it global since the OS won't change,
+ # but we'll keep it within the function for better unit testing
+ IFS=$'\r\n' command eval 'distro_info=( $(cat /etc/*release) )'
+
+ # Set a named variable for better readability
+ local distro_attribute
+ # For each line found in an /etc/*release file,
+ for distro_attribute in "${distro_info[@]}"; do
+ # store the key in a variable
+ local pretty_name_key=$(echo "${distro_attribute}" | grep "PRETTY_NAME" | cut -d '=' -f1)
+ # we need just the OS PRETTY_NAME,
+ if [[ "${pretty_name_key}" == "PRETTY_NAME" ]]; then
+ # so save in in a variable when we find it
+ PRETTY_NAME_VALUE=$(echo "${distro_attribute}" | grep "PRETTY_NAME" | cut -d '=' -f2- | tr -d '"')
+ # then pass it as an argument that checks if the OS is supported
+ is_os_supported "${PRETTY_NAME_VALUE}"
+ else
+ # Since we only need the pretty name, we can just skip over anything that is not a match
+ :
+ fi
done
- echo ":::"
-}
-
-files_check() {
- #Check non-zero length existence of ${1}
- header_write "Detecting existence of ${1}:"
- local search_file="${1}"
- if [[ -s ${search_file} ]]; then
- echo -n "::: File exists, parsing..."
- file_parse "${search_file}"
- echo "done"
- return 0
+ # Set the IFS back to what it was
+ IFS="$OLD_IFS"
+}
+
+diagnose_operating_system() {
+ # error message in a variable so we can easily modify it later (or re-use it)
+ local error_msg="Distribution unknown -- most likely you are on an unsupported platform and may run into issues."
+ # Display the current test that is running
+ echo_current_diagnostic "Operating system"
+
+ # If there is a /etc/*release file, it's probably a supported operating system, so we can
+ if ls /etc/*release 1> /dev/null 2>&1; then
+ # display the attributes to the user from the function made earlier
+ get_distro_attributes
else
- log_echo "${1} not found!"
- return 1
+ # If it doesn't exist, it's not a system we currently support and link to FAQ
+ log_write "${CROSS} ${COL_RED}${error_msg}${COL_NC} (${FAQ_HARDWARE_REQUIREMENTS})"
fi
- echo ":::"
}
-source_file() {
- local file_found=$(files_check "${1}") \
- && (source "${1}" &> /dev/null && echo "${file_found} and was successfully sourced") \
- || log_echo -l "${file_found} and could not be sourced"
+check_selinux() {
+ # SELinux is not supported by the Pi-hole
+ echo_current_diagnostic "SELinux"
+ # Check if a SELinux configuration file exists
+ if [[ -f /etc/selinux/config ]]; then
+ # If a SELinux configuration file was found, check the default SELinux mode.
+ DEFAULT_SELINUX=$(awk -F= '/^SELINUX=/ {print $2}' /etc/selinux/config)
+ case "${DEFAULT_SELINUX,,}" in
+ enforcing)
+ log_write "${CROSS} ${COL_RED}Default SELinux: $DEFAULT_SELINUX${COL_NC}"
+ ;;
+ *) # 'permissive' and 'disabled'
+ log_write "${TICK} ${COL_GREEN}Default SELinux: $DEFAULT_SELINUX${COL_NC}";
+ ;;
+ esac
+ # Check the current state of SELinux
+ CURRENT_SELINUX=$(getenforce)
+ case "${CURRENT_SELINUX,,}" in
+ enforcing)
+ log_write "${CROSS} ${COL_RED}Current SELinux: $CURRENT_SELINUX${COL_NC}"
+ ;;
+ *) # 'permissive' and 'disabled'
+ log_write "${TICK} ${COL_GREEN}Current SELinux: $CURRENT_SELINUX${COL_NC}";
+ ;;
+ esac
+ else
+ log_write "${INFO} ${COL_GREEN}SELinux not detected${COL_NC}";
+ fi
}
-distro_check() {
- local soft_fail
- header_write "Detecting installed OS Distribution"
- soft_fail=0
- local distro="$(cat /etc/*release)" && block_parse "${distro}" || (log_echo "Distribution details not found." && soft_fail=1)
- return "${soft_fail}"
+processor_check() {
+ echo_current_diagnostic "Processor"
+ # Store the processor type in a variable
+ PROCESSOR=$(uname -m)
+ # If it does not contain a value,
+ if [[ -z "${PROCESSOR}" ]]; then
+ # we couldn't detect it, so show an error
+ PROCESSOR=$(lscpu | awk '/Architecture/ {print $2}')
+ log_write "${CROSS} ${COL_RED}${PROCESSOR}${COL_NC} has not been tested with FTL, but may still work: (${FAQ_FTL_COMPATIBILITY})"
+ else
+ # Check if the architecture is currently supported for FTL
+ case "${PROCESSOR}" in
+ "amd64") "${TICK} ${COL_GREEN}${PROCESSOR}${COL_NC}"
+ ;;
+ "armv6l") "${TICK} ${COL_GREEN}${PROCESSOR}${COL_NC}"
+ ;;
+ "armv6") "${TICK} ${COL_GREEN}${PROCESSOR}${COL_NC}"
+ ;;
+ "armv7l") "${TICK} ${COL_GREEN}${PROCESSOR}${COL_NC}"
+ ;;
+ "aarch64") "${TICK} ${COL_GREEN}${PROCESSOR}${COL_NC}"
+ ;;
+ # Otherwise, show the processor type
+ *) log_write "${INFO} ${PROCESSOR}";
+ esac
+ fi
}
-processor_check() {
- header_write "Checking processor variety"
- log_write $(uname -m) && return 0 || return 1
+parse_setup_vars() {
+ echo_current_diagnostic "Setup variables"
+ # If the file exists,
+ if [[ -r "${PIHOLE_SETUP_VARS_FILE}" ]]; then
+ # parse it
+ parse_file "${PIHOLE_SETUP_VARS_FILE}"
+ else
+ # If not, show an error
+ log_write "${CROSS} ${COL_RED}Could not read ${PIHOLE_SETUP_VARS_FILE}.${COL_NC}"
+ fi
}
-ipv6_check() {
- # Check if system is IPv6 enabled, for use in other functions
- if [[ $IPV6_ADDRESS ]]; then
- ls /proc/net/if_inet6 &>/dev/null
- return 0
+does_ip_match_setup_vars() {
+ # Check for IPv4 or 6
+ local protocol="${1}"
+ # IP address to check for
+ local ip_address="${2}"
+ # See what IP is in the setupVars.conf file
+ local setup_vars_ip=$(< ${PIHOLE_SETUP_VARS_FILE} grep IPV${protocol}_ADDRESS | cut -d '=' -f2)
+ # If it's an IPv6 address
+ if [[ "${protocol}" == "6" ]]; then
+ # Strip off the / (CIDR notation)
+ if [[ "${ip_address%/*}" == "${setup_vars_ip%/*}" ]]; then
+ # if it matches, show it in green
+ log_write " ${COL_GREEN}${ip_address%/*}${COL_NC} matches the IP found in ${PIHOLE_SETUP_VARS_FILE}"
+ else
+ # otherwise show it in red with an FAQ URL
+ log_write " ${COL_RED}${ip_address%/*}${COL_NC} does not match the IP found in ${PIHOLE_SETUP_VARS_FILE} (${FAQ_ULA})"
+ fi
+
else
- return 1
+ # if the protocol isn't 6, it's 4 so no need to strip the CIDR notation
+ # since it exists in the setupVars.conf that way
+ if [[ "${ip_address}" == "${setup_vars_ip}" ]]; then
+ # show in green if it matches
+ log_write " ${COL_GREEN}${ip_address}${COL_NC} matches the IP found in ${PIHOLE_SETUP_VARS_FILE}"
+ else
+ # otherwise show it in red
+ log_write " ${COL_RED}${ip_address}${COL_NC} does not match the IP found in ${PIHOLE_SETUP_VARS_FILE} (${FAQ_ULA})"
+ fi
fi
}
-ip_check() {
+detect_ip_addresses() {
+ # First argument should be a 4 or a 6
local protocol=${1}
- local gravity=${2}
- header_write "Checking IPv${protocol} Stack"
+ # Use ip to show the addresses for the chosen protocol
+ # Store the values in an arry so they can be looped through
+ # Get the lines that are in the file(s) and store them in an array for parsing later
+ declare -a ip_addr_list=( $(ip -${protocol} addr show dev ${PIHOLE_INTERFACE} | awk -F ' ' '{ for(i=1;i<=NF;i++) if ($i ~ '/^inet/') print $(i+1) }') )
- local ip_addr_list="$(ip -${protocol} addr show dev ${PIHOLE_INTERFACE} | awk -F ' ' '{ for(i=1;i<=NF;i++) if ($i ~ '/^inet/') print $(i+1) }')"
+ # If there is something in the IP address list,
if [[ -n ${ip_addr_list} ]]; then
- log_write "IPv${protocol} on ${PIHOLE_INTERFACE}"
- log_write "Gravity configured for: ${2:-NOT CONFIGURED}"
- log_write "----"
- log_write "${ip_addr_list}"
- echo "::: IPv${protocol} addresses located on ${PIHOLE_INTERFACE}"
- ip_ping_check ${protocol}
- return $(( 0 + $? ))
+ # Local iterator
+ local i
+ # Display the protocol and interface
+ log_write "${TICK} IPv${protocol} address(es) bound to the ${PIHOLE_INTERFACE} interface:"
+ # Since there may be more than one IP address, store them in an array
+ for i in "${!ip_addr_list[@]}"; do
+ # For each one in the list, print it out
+ does_ip_match_setup_vars "${protocol}" "${ip_addr_list[$i]}"
+ done
+ # Print a blank line just for formatting
+ log_write ""
else
- log_echo "No IPv${protocol} found on ${PIHOLE_INTERFACE}"
+ # If there are no IPs detected, explain that the protocol is not configured
+ log_write "${CROSS} ${COL_RED}No IPv${protocol} address(es) found on the ${PIHOLE_INTERFACE}${COL_NC} interace.\n"
return 1
fi
+ # If the protocol is v6
+ if [[ "${protocol}" == "6" ]]; then
+ # let the user know that as long as there is one green address, things should be ok
+ log_write " ^ Please note that you may have more than one IP address listed."
+ log_write " As long as one of them is green, and it matches what is in ${PIHOLE_SETUP_VARS_FILE}, there is no need for concern.\n"
+ log_write " The link to the FAQ is for an issue that sometimes occurs when the IPv6 address changes, which is why we check for it.\n"
+ fi
}
-ip_ping_check() {
- local protocol=${1}
- local cmd
-
+ping_ipv4_or_ipv6() {
+ # Give the first argument a readable name (a 4 or a six should be the argument)
+ local protocol="${1}"
+ # If the protocol is 6,
if [[ ${protocol} == "6" ]]; then
+ # use ping6
cmd="ping6"
- g_addr="2001:4860:4860::8888"
+ # and Google's public IPv6 address
+ public_address="2001:4860:4860::8888"
else
+ # Otherwise, just use ping
cmd="ping"
- g_addr="8.8.8.8"
+ # and Google's public IPv4 address
+ public_address="8.8.8.8"
fi
+}
- local ip_def_gateway=$(ip -${protocol} route | grep default | cut -d ' ' -f 3)
- if [[ -n ${ip_def_gateway} ]]; then
- echo -n "::: Pinging default IPv${protocol} gateway: "
- if ! ping_gateway="$(${cmd} -q -W 3 -c 3 -n ${ip_def_gateway} -I ${PIHOLE_INTERFACE} | tail -n 3)"; then
- log_echo "Gateway did not respond."
- return 1
- else
- log_echo "Gateway responded."
- log_write "${ping_gateway}"
- fi
- echo -n "::: Pinging Internet via IPv${protocol}: "
- if ! ping_inet="$(${cmd} -q -W 3 -c 3 -n ${g_addr} -I ${PIHOLE_INTERFACE} | tail -n 3)"; then
- log_echo "Query did not respond."
+ping_gateway() {
+ local protocol="${1}"
+ ping_ipv4_or_ipv6 "${protocol}"
+ # Check if we are using IPv4 or IPv6
+ # Find the default gateway using IPv4 or IPv6
+ local gateway
+ gateway="$(ip -${protocol} route | grep default | cut -d ' ' -f 3)"
+
+ # If the gateway variable has a value (meaning a gateway was found),
+ if [[ -n "${gateway}" ]]; then
+ log_write "${INFO} Default IPv${protocol} gateway: ${gateway}"
+ # Let the user know we will ping the gateway for a response
+ log_write " * Pinging ${gateway}..."
+ # Try to quietly ping the gateway 3 times, with a timeout of 3 seconds, using numeric output only,
+ # on the pihole interface, and tail the last three lines of the output
+ # If pinging the gateway is not successful,
+ if ! ${cmd} -c 3 -W 2 -n ${gateway} -I ${PIHOLE_INTERFACE} >/dev/null; then
+ # let the user know
+ log_write "${CROSS} ${COL_RED}Gateway did not respond.${COL_NC} ($FAQ_GATEWAY)\n"
+ # and return an error code
return 1
+ # Otherwise,
else
- log_echo "Query responded."
- log_write "${ping_inet}"
+ # show a success
+ log_write "${TICK} ${COL_GREEN}Gateway responded.${COL_NC}"
+ # and return a success code
+ return 0
fi
+ fi
+}
+
+ping_internet() {
+ local protocol="${1}"
+ # Ping a public address using the protocol passed as an argument
+ ping_ipv4_or_ipv6 "${protocol}"
+ log_write "* Checking Internet connectivity via IPv${protocol}..."
+ # Try to ping the address 3 times
+ if ! ${cmd} -W 2 -c 3 -n ${public_address} -I ${PIHOLE_INTERFACE} >/dev/null; then
+ # if it's unsuccessful, show an error
+ log_write "${CROSS} ${COL_RED}Cannot reach the Internet.${COL_NC}\n"
+ return 1
else
- log_echo " No gateway detected."
+ # Otherwise, show success
+ log_write "${TICK} ${COL_GREEN}Query responded.${COL_NC}\n"
+ return 0
fi
- return 0
}
-port_check() {
- local lsof_value
+compare_port_to_service_assigned() {
+ local service_name="${1}"
+ # The programs we use may change at some point, so they are in a varible here
+ local resolver="dnsmasq"
+ local web_server="lighttpd"
+ local ftl="pihole-FTL"
+ if [[ "${service_name}" == "${resolver}" ]] || [[ "${service_name}" == "${web_server}" ]] || [[ "${service_name}" == "${ftl}" ]]; then
+ # if port 53 is dnsmasq, show it in green as it's standard
+ log_write "[${COL_GREEN}${port_number}${COL_NC}] is in use by ${COL_GREEN}${service_name}${COL_NC}"
+ # Otherwise,
+ else
+ # Show the service name in red since it's non-standard
+ log_write "[${COL_RED}${port_number}${COL_NC}] is in use by ${COL_RED}${service_name}${COL_NC} (${FAQ_HARDWARE_REQUIREMENTS_PORTS})"
+ fi
+}
- lsof_value=$(lsof -i ${1}:${2} -FcL | tr '\n' ' ') \
- && lsof_parse "${lsof_value}" "${3}" \
- || log_echo "Failure: IPv${1} Port not in use"
+check_required_ports() {
+ echo_current_diagnostic "Ports in use"
+ # Since Pi-hole needs 53, 80, and 4711, check what they are being used by
+ # so we can detect any issues
+ local resolver="dnsmasq"
+ local web_server="lighttpd"
+ local ftl="pihole-FTL"
+ # Create an array for these ports in use
+ ports_in_use=()
+ # Sort the addresses and remove duplicates
+ while IFS= read -r line; do
+ ports_in_use+=( "$line" )
+ done < <( lsof -i -P -n | awk -F' ' '/LISTEN/ {print $9, $1}' | sort -n | uniq | cut -d':' -f2 )
+
+ # Now that we have the values stored,
+ for i in "${!ports_in_use[@]}"; do
+ # loop through them and assign some local variables
+ local port_number
+ port_number="$(echo "${ports_in_use[$i]}" | awk '{print $1}')"
+ local service_name
+ service_name=$(echo "${ports_in_use[$i]}" | awk '{print $2}')
+ # Use a case statement to determine if the right services are using the right ports
+ case "${port_number}" in
+ 53) compare_port_to_service_assigned "${resolver}"
+ ;;
+ 80) compare_port_to_service_assigned "${web_server}"
+ ;;
+ 4711) compare_port_to_service_assigned "${ftl}"
+ ;;
+ # If it's not a default port that Pi-hole needs, just print it out for the user to see
+ *) log_write "[${port_number}] is in use by ${service_name}";
+ esac
+ done
}
-daemon_check() {
- # Check for daemon ${1} on port ${2}
- header_write "Daemon Process Information"
+check_networking() {
+ # Runs through several of the functions made earlier; we just clump them
+ # together since they are all related to the networking aspect of things
+ echo_current_diagnostic "Networking"
+ detect_ip_addresses "4"
+ detect_ip_addresses "6"
+ ping_gateway "4"
+ ping_gateway "6"
+ check_required_ports
+}
- echo "::: Checking ${2} port for ${1} listener."
+check_x_headers() {
+ # The X-Headers allow us to determine from the command line if the Web
+ # lighttpd.conf has a directive to show "X-Pi-hole: A black hole for Internet advertisements."
+ # in the header of any Pi-holed domain
+ # Similarly, it will show "X-Pi-hole: The Pi-hole Web interface is working!" if you view the header returned
+ # when accessing the dashboard (i.e curl -I pi.hole/admin/)
+ # server is operating correctly
+ echo_current_diagnostic "Dashboard and block page"
+ # Use curl -I to get the header and parse out just the X-Pi-hole one
+ local block_page
+ block_page=$(curl -Is localhost | awk '/X-Pi-hole/' | tr -d '\r')
+ # Do it for the dashboard as well, as the header is different than above
+ local dashboard
+ dashboard=$(curl -Is localhost/admin/ | awk '/X-Pi-hole/' | tr -d '\r')
+ # Store what the X-Header shoud be in variables for comparision later
+ local block_page_working
+ block_page_working="X-Pi-hole: A black hole for Internet advertisements."
+ local dashboard_working
+ dashboard_working="X-Pi-hole: The Pi-hole Web interface is working!"
+ local full_curl_output_block_page
+ full_curl_output_block_page="$(curl -Is localhost)"
+ local full_curl_output_dashboard
+ full_curl_output_dashboard="$(curl -Is localhost/admin/)"
+ # If the X-header found by curl matches what is should be,
+ if [[ $block_page == "$block_page_working" ]]; then
+ # display a success message
+ log_write "$TICK ${COL_GREEN}${block_page}${COL_NC}"
+ else
+ # Otherwise, show an error
+ log_write "$CROSS ${COL_RED}X-Header does not match or could not be retrieved.${COL_NC}"
+ log_write "${COL_RED}${full_curl_output_block_page}${COL_NC}"
+ fi
- if [[ ${IPV6_READY} ]]; then
- port_check 6 "${2}" "${1}"
+ # Same logic applies to the dashbord as above, if the X-Header matches what a working system shoud have,
+ if [[ $dashboard == "$dashboard_working" ]]; then
+ # then we can show a success
+ log_write "$TICK ${COL_GREEN}${dashboard}${COL_NC}"
+ else
+ # Othewise, it's a failure since the X-Headers either don't exist or have been modified in some way
+ log_write "$CROSS ${COL_RED}X-Header does not match or could not be retrieved.${COL_NC}"
+ log_write "${COL_RED}${full_curl_output_dashboard}${COL_NC}"
fi
- lsof_value=$(lsof -i 4:${2} -FcL | tr '\n' ' ') \
- port_check 4 "${2}" "${1}"
}
-testResolver() {
+dig_at() {
+ # We need to test if Pi-hole can properly resolve domain names
+ # as it is an essential piece of the software
+
+ # Store the arguments as variables with names
local protocol="${1}"
- header_write "Resolver Functions Check (IPv${protocol})"
local IP="${2}"
- local g_addr
- local l_addr
- local url
- local testurl
- local localdig
- local piholedig
- local remotedig
-
+ echo_current_diagnostic "Name resolution (IPv${protocol}) using a random blocked domain and a known ad-serving domain"
+ # Set more local variables
+ # We need to test name resolution locally, via Pi-hole, and via a public resolver
+ local local_dig
+ local pihole_dig
+ local remote_dig
+ # Use a static domain that we know has IPv4 and IPv6 to avoid false positives
+ # Sometimes the randomly chosen domains don't use IPv6, or something else is wrong with them
+ local remote_url="doubleclick.com"
+
+ # If the protocol (4 or 6) is 6,
if [[ ${protocol} == "6" ]]; then
- g_addr="2001:4860:4860::8888"
- l_addr="::1"
- r_type="AAAA"
+ # Set the IPv6 variables and record type
+ local local_address="::1"
+ local pihole_address="${IPV6_ADDRESS%/*}"
+ local remote_address="2001:4860:4860::8888"
+ local record_type="AAAA"
+ # Othwerwise, it should be 4
else
- g_addr="8.8.8.8"
- l_addr="127.0.0.1"
- r_type="A"
+ # so use the IPv4 values
+ local local_address="127.0.0.1"
+ local pihole_address="${IPV4_ADDRESS%/*}"
+ local remote_address="8.8.8.8"
+ local record_type="A"
fi
- # Find a blocked url that has not been whitelisted.
- url=$(shuf -n 1 "${GRAVITYFILE}" | awk -F ' ' '{ print $2 }')
+ # Find a random blocked url that has not been whitelisted.
+ # This helps emulate queries to different domains that a user might query
+ # It will also give extra assurance that Pi-hole is correctly resolving and blocking domains
+ local random_url=$(shuf -n 1 "${PIHOLE_BLOCKLIST_FILE}" | awk -F ' ' '{ print $2 }')
- testurl="${url:-doubleclick.com}"
+ # First, do a dig on localhost to see if Pi-hole can use itself to block a domain
+ if local_dig=$(dig +tries=1 +time=2 -"${protocol}" "${random_url}" @${local_address} +short "${record_type}"); then
+ # If it can, show sucess
+ log_write "${TICK} ${random_url} ${COL_GREEN}is ${local_dig}${COL_NC} via ${COL_CYAN}localhost$COL_NC (${local_address})"
+ else
+ # Otherwise, show a failure
+ log_write "${CROSS} ${COL_RED}Failed to resolve${COL_NC} ${random_url} via ${COL_RED}localhost${COL_NC} (${local_address})"
+ fi
+ # Next we need to check if Pi-hole can resolve a domain when the query is sent to it's IP address
+ # This better emulates how clients will interact with Pi-hole as opposed to above where Pi-hole is
+ # just asing itself locally
+ # The default timeouts and tries are reduced in case the DNS server isn't working, so the user isn't waiting for too long
- log_write "Resolution of ${testurl} from Pi-hole (${l_addr}):"
- if localdig=$(dig -"${protocol}" "${testurl}" @${l_addr} +short "${r_type}"); then
- log_write "${localdig}"
- else
- log_write "Failed to resolve ${testurl} on Pi-hole (${l_addr})"
- fi
- log_write ""
+ # If Pi-hole can dig itself from it's IP (not the loopback address)
+ if pihole_dig=$(dig +tries=1 +time=2 -"${protocol}" "${random_url}" @${pihole_address} +short "${record_type}"); then
+ # show a success
+ log_write "${TICK} ${random_url} ${COL_GREEN}is ${pihole_dig}${COL_NC} via ${COL_CYAN}Pi-hole${COL_NC} (${pihole_address})"
+ else
+ # Othewise, show a failure
+ log_write "${CROSS} ${COL_RED}Failed to resolve${COL_NC} ${random_url} via ${COL_RED}Pi-hole${COL_NC} (${pihole_address})"
+ fi
- log_write "Resolution of ${testurl} from Pi-hole (${IP}):"
- if piholedig=$(dig -"${protocol}" "${testurl}" @"${IP}" +short "${r_type}"); then
- log_write "${piholedig}"
- else
- log_write "Failed to resolve ${testurl} on Pi-hole (${IP})"
- fi
- log_write ""
+ # Finally, we need to make sure legitimate queries can out to the Internet using an external, public DNS server
+ # We are using the static remote_url here instead of a random one because we know it works with IPv4 and IPv6
+ if remote_dig=$(dig +tries=1 +time=2 -"${protocol}" "${remote_url}" @${remote_address} +short "${record_type}" | head -n1); then
+ # If successful, the real IP of the domain will be returned instead of Pi-hole's IP
+ log_write "${TICK} ${remote_url} ${COL_GREEN}is ${remote_dig}${COL_NC} via ${COL_CYAN}a remote, public DNS server${COL_NC} (${remote_address})"
+ else
+ # Otherwise, show an error
+ log_write "${CROSS} ${COL_RED}Failed to resolve${COL_NC} ${remote_url} via ${COL_RED}a remote, public DNS server${COL_NC} (${remote_address})"
+ fi
+}
+process_status(){
+ # Check to make sure Pi-hole's services are running and active
+ echo_current_diagnostic "Pi-hole processes"
+ # Local iterator
+ local i
+ # For each process,
+ for i in "${PIHOLE_PROCESSES[@]}"; do
+ # get its status via systemctl
+ local status_of_process=$(systemctl is-active "${i}")
+ # and print it out to the user
+ if [[ "${status_of_process}" == "active" ]]; then
+ # If it's active, show it in green
+ log_write "${TICK} ${COL_GREEN}${i}${COL_NC} daemon is ${COL_GREEN}${status_of_process}${COL_NC}"
+ else
+ # If it's not, show it in red
+ log_write "${CROSS} ${COL_RED}${i}${COL_NC} daemon is ${COL_RED}${status_of_process}${COL_NC}"
+ fi
+ done
+}
- log_write "Resolution of ${testurl} from ${g_addr}:"
- if remotedig=$(dig -"${protocol}" "${testurl}" @${g_addr} +short "${r_type}"); then
- log_write "${remotedig:-NXDOMAIN}"
- else
- log_write "Failed to resolve ${testurl} on upstream server ${g_addr}"
- fi
- log_write ""
+make_array_from_file() {
+ local filename="${1}"
+ # The second argument can put a limit on how many line should be read from the file
+ # Since some of the files are so large, this is helpful to limit the output
+ local limit=${2}
+ # A local iterator for testing if we are at the limit above
+ local i=0
+ # Set the array to be empty so we can start fresh when the function is used
+ local file_content=()
+ # If the file is a directory
+ if [[ -d "${filename}" ]]; then
+ # do nothing since it cannot be parsed
+ :
+ else
+ # Otherwise, read the file line by line
+ while IFS= read -r line;do
+ # Othwerise, strip out comments and blank lines
+ new_line=$(echo "${line}" | sed -e 's/#.*$//' -e '/^$/d')
+ # If the line still has content (a non-zero value)
+ if [[ -n "${new_line}" ]]; then
+ # Put it into the array
+ file_content+=("${new_line}")
+ else
+ # Otherwise, it's a blank line or comment, so do nothing
+ :
+ fi
+ # Increment the iterator +1
+ i=$((i+1))
+ # but if the limit of lines we want to see is exceeded
+ if [[ -z ${limit} ]]; then
+ # do nothing
+ :
+ elif [[ $i -eq ${limit} ]]; then
+ break
+ fi
+ done < "${filename}"
+ # Now the we have made an array of the file's content
+ for each_line in "${file_content[@]}"; do
+ # Print each line
+ # At some point, we may want to check the file line-by-line, so that's the reason for an array
+ log_write " ${each_line}"
+ done
+ fi
}
-testChaos(){
- # Check Pi-hole specific records
+parse_file() {
+ # Set the first argument passed to this function as a named variable for better readability
+ local filename="${1}"
+ # Put the current Internal Field Separator into another variable so it can be restored later
+ OLD_IFS="$IFS"
+ # Get the lines that are in the file(s) and store them in an array for parsing later
+ IFS=$'\r\n' command eval 'file_info=( $(cat "${filename}") )'
+
+ # Set a named variable for better readability
+ local file_lines
+ # For each line in the file,
+ for file_lines in "${file_info[@]}"; do
+ if [[ ! -z "${file_lines}" ]]; then
+ # don't include the Web password hash
+ [[ "${file_linesline}" =~ ^\#.*$ || ! "${file_lines}" || "${file_lines}" == "WEBPASSWORD="* ]] && continue
+ # otherwise, display the lines of the file
+ log_write " ${file_lines}"
+ fi
+ done
+ # Set the IFS back to what it was
+ IFS="$OLD_IFS"
+}
- log_write "Pi-hole dnsmasq specific records lookups"
- log_write "Cache Size:"
- log_write $(dig +short chaos txt cachesize.bind)
- log_write "Upstream Servers:"
- log_write $(dig +short chaos txt servers.bind)
- log_write ""
+check_name_resolution() {
+ # Check name resoltion from localhost, Pi-hole's IP, and Google's name severs
+ # using the function we created earlier
+ dig_at 4 "${IPV4_ADDRESS%/*}"
+ # If IPv6 enabled,
+ if [[ "${IPV6_ADDRESS}" ]]; then
+ # check resolution
+ dig_at 6 "${IPV6_ADDRESS%/*}"
+ fi
+}
+# This function can check a directory exists
+# Pi-hole has files in several places, so we will reuse this function
+dir_check() {
+ # Set the first argument passed to tihs function as a named variable for better readability
+ local directory="${1}"
+ # Display the current test that is running
+ echo_current_diagnostic "contents of ${COL_CYAN}${directory}${COL_NC}"
+ # For each file in the directory,
+ for filename in ${directory}; do
+ # check if exists first; if it does,
+ if ls "${filename}" 1> /dev/null 2>&1; then
+ # do nothing
+ :
+ else
+ # Otherwise, show an error
+ log_write "${COL_RED}${directory} does not exist.${COL_NC}"
+ fi
+ done
+}
+
+list_files_in_dir() {
+ # Set the first argument passed to tihs function as a named variable for better readability
+ local dir_to_parse="${1}"
+ # Store the files found in an array
+ local files_found=( $(ls "${dir_to_parse}") )
+ # For each file in the array,
+ for each_file in "${files_found[@]}"; do
+ if [[ -d "${dir_to_parse}/${each_file}" ]]; then
+ # If it's a directoy, do nothing
+ :
+ elif [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_BLOCKLIST_FILE}" ]] || \
+ [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_DEBUG_LOG}" ]] || \
+ [[ ${dir_to_parse}/${each_file} == ${PIHOLE_RAW_BLOCKLIST_FILES} ]] || \
+ [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_INSTALL_LOG_FILE}" ]] || \
+ [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_SETUP_VARS_FILE}" ]] || \
+ [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_LOG}" ]] || \
+ [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_WEB_SERVER_ACCESS_LOG_FILE}" ]] || \
+ [[ ${dir_to_parse}/${each_file} == ${PIHOLE_LOG_GZIPS} ]]; then
+ :
+ else
+ # Then, parse the file's content into an array so each line can be analyzed if need be
+ for i in "${!REQUIRED_FILES[@]}"; do
+ if [[ "${dir_to_parse}/${each_file}" == ${REQUIRED_FILES[$i]} ]]; then
+ # display the filename
+ log_write "\n${COL_GREEN}$(ls -ld ${dir_to_parse}/${each_file})${COL_NC}"
+ # Check if the file we want to view has a limit (because sometimes we just need a little bit of info from the file, not the entire thing)
+ case "${dir_to_parse}/${each_file}" in
+ # If it's Web server error log, just give the first 25 lines
+ "${PIHOLE_WEB_SERVER_ERROR_LOG_FILE}") make_array_from_file "${dir_to_parse}/${each_file}" 25
+ ;;
+ # Same for the FTL log
+ "${PIHOLE_FTL_LOG}") make_array_from_file "${dir_to_parse}/${each_file}" 25
+ ;;
+ # parse the file into an array in case we ever need to analyze it line-by-line
+ *) make_array_from_file "${dir_to_parse}/${each_file}";
+ esac
+ else
+ # Otherwise, do nothing since it's not a file needed for Pi-hole so we don't care about it
+ :
+ fi
+ done
+ fi
+ done
+}
+
+show_content_of_files_in_dir() {
+ # Set a local variable for better readability
+ local directory="${1}"
+ # Check if the directory exists
+ dir_check "${directory}"
+ # if it does, list the files in it
+ list_files_in_dir "${directory}"
}
-checkProcesses() {
- header_write "Processes Check"
- echo "::: Logging status of lighttpd, dnsmasq and pihole-FTL..."
- PROCESSES=( lighttpd dnsmasq pihole-FTL )
- for i in "${PROCESSES[@]}"; do
- log_write "Status for ${i} daemon:"
- log_write $(systemctl is-active "${i}")
- done
- log_write ""
+show_content_of_pihole_files() {
+ # Show the content of the files in each of Pi-hole's folders
+ show_content_of_files_in_dir "${PIHOLE_DIRECTORY}"
+ show_content_of_files_in_dir "${DNSMASQ_D_DIRECTORY}"
+ show_content_of_files_in_dir "${WEB_SERVER_CONFIG_DIRECTORY}"
+ show_content_of_files_in_dir "${CRON_D_DIRECTORY}"
+ show_content_of_files_in_dir "${WEB_SERVER_LOG_DIRECTORY}"
+ show_content_of_files_in_dir "${LOG_DIRECTORY}"
}
-debugLighttpd() {
- echo "::: Checking for necessary lighttpd files."
- files_check "${LIGHTTPDFILE}"
- files_check "${LIGHTTPDERRFILE}"
- echo ":::"
+analyze_gravity_list() {
+ echo_current_diagnostic "Gravity list"
+ local head_line
+ local tail_line
+ # Put the current Internal Field Separator into another variable so it can be restored later
+ OLD_IFS="$IFS"
+ # Get the lines that are in the file(s) and store them in an array for parsing later
+ IFS=$'\r\n'
+ local gravity_permissions=$(ls -ld "${PIHOLE_BLOCKLIST_FILE}")
+ log_write "${COL_GREEN}${gravity_permissions}${COL_NC}"
+ local gravity_head=()
+ gravity_head=( $(head -n 4 ${PIHOLE_BLOCKLIST_FILE}) )
+ log_write " ${COL_CYAN}-----head of $(basename ${PIHOLE_BLOCKLIST_FILE})------${COL_NC}"
+ for head_line in "${gravity_head[@]}"; do
+ log_write " ${head_line}"
+ done
+ log_write ""
+ local gravity_tail=()
+ gravity_tail=( $(tail -n 4 ${PIHOLE_BLOCKLIST_FILE}) )
+ log_write " ${COL_CYAN}-----tail of $(basename ${PIHOLE_BLOCKLIST_FILE})------${COL_NC}"
+ for tail_line in "${gravity_tail[@]}"; do
+ log_write " ${tail_line}"
+ done
+ # Set the IFS back to what it was
+ IFS="$OLD_IFS"
}
-countdown() {
- local tuvix
- tuvix=${TIMEOUT}
- printf "::: Logging will automatically teminate in %s seconds\n" "${TIMEOUT}"
- while [ $tuvix -ge 1 ]
- do
- printf ":::\t%s seconds left. " "${tuvix}"
- if [[ -z "${WEBCALL}" ]]; then
- printf "\r"
+analyze_pihole_log() {
+ echo_current_diagnostic "Pi-hole log"
+ local head_line
+ # Put the current Internal Field Separator into another variable so it can be restored later
+ OLD_IFS="$IFS"
+ # Get the lines that are in the file(s) and store them in an array for parsing later
+ IFS=$'\r\n'
+ local pihole_log_permissions=$(ls -ld "${PIHOLE_LOG}")
+ log_write "${COL_GREEN}${pihole_log_permissions}${COL_NC}"
+ local pihole_log_head=()
+ pihole_log_head=( $(head -n 20 ${PIHOLE_LOG}) )
+ log_write " ${COL_CYAN}-----head of $(basename ${PIHOLE_LOG})------${COL_NC}"
+ local error_to_check_for
+ local line_to_obfuscate
+ local obfuscated_line
+ for head_line in "${pihole_log_head[@]}"; do
+ # A common error in the pihole.log is when there is a non-hosts formatted file
+ # that the DNS server is attempting to read. Since it's not formatted
+ # correctly, there will be an entry for "bad address at line n"
+ # So we can check for that here and highlight it in red so the user can see it easily
+ error_to_check_for=$(echo ${head_line} | grep 'bad address at')
+ # Some users may not want to have the domains they visit sent to us
+ # To that end, we check for lines in the log that would contain a domain name
+ line_to_obfuscate=$(echo ${head_line} | grep ': query\|: forwarded\|: reply')
+ # If the variable contains a value, it found an error in the log
+ if [[ -n ${error_to_check_for} ]]; then
+ # So we can print it in red to make it visible to the user
+ log_write " ${CROSS} ${COL_RED}${head_line}${COL_NC} (${FAQ_BAD_ADDRESS})"
else
- printf "\n"
+ # If the variable does not a value (the current default behavior), so do not obfuscate anything
+ if [[ -z ${OBFUSCATE} ]]; then
+ log_write " ${head_line}"
+ # Othwerise, a flag was passed to this command to obfuscate domains in the log
+ else
+ # So first check if there are domains in the log that should be obfuscated
+ if [[ -n ${line_to_obfuscate} ]]; then
+ # If there are, we need to use awk to replace only the domain name (the 6th field in the log)
+ # so we substitue the domain for the placeholder value
+ obfuscated_line=$(echo ${line_to_obfuscate} | awk -v placeholder="${OBFUSCATED_PLACEHOLDER}" '{sub($6,placeholder); print $0}')
+ log_write " ${obfuscated_line}"
+ else
+ log_write " ${head_line}"
+ fi
+ fi
fi
- sleep 5
- tuvix=$(( tuvix - 5 ))
done
+ log_write ""
+ # Set the IFS back to what it was
+ IFS="$OLD_IFS"
}
-# Continuously append the pihole.log file to the pihole_debug.log file
-dumpPiHoleLog() {
- trap '{ echo -e "\n::: Finishing debug write from interrupt... Quitting!" ; exit 1; }' INT
- echo "::: "
- echo "::: --= User Action Required =--"
- 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
- # Dummy process to use for flagging down tail to terminate
- countdown &
- tail -n0 -f --pid=$! "${PIHOLELOG}" >&4
- else
- log_write "No pihole.log file found!"
- printf ":::\tNo pihole.log file found!\n"
- fi
+tricorder_use_nc_or_ssl() {
+ # Users can submit their debug logs using nc (unencrypted) or openssl (enrypted) if available
+ # Check for openssl first since encryption is a good thing
+ if command -v openssl &> /dev/null; then
+ # If the command exists,
+ log_write " * Using ${COL_GREEN}openssl${COL_NC} for transmission."
+ # encrypt and transmit the log and store the token returned in a variable
+ tricorder_token=$(< ${PIHOLE_DEBUG_LOG_SANITIZED} openssl s_client -quiet -connect tricorder.pi-hole.net:${TRICORDER_SSL_PORT_NUMBER} 2> /dev/null)
+ # Otherwise,
+ else
+ # use net cat
+ log_write "${INFO} Using ${COL_YELLOW}netcat${COL_NC} for transmission."
+ # Save the token returned by our server in a variable
+ tricorder_token=$(< ${PIHOLE_DEBUG_LOG_SANITIZED} nc tricorder.pi-hole.net ${TRICORDER_NC_PORT_NUMBER})
+ fi
}
-# Anything to be done after capturing of pihole.log terminates
-finalWork() {
- local tricorder
- echo "::: Finshed debugging!"
- # Ensure the file exists, create if not, clear if exists.
- truncate --size=0 "${DEBUG_LOG}"
- chmod 644 ${DEBUG_LOG}
- chown "$USER":pihole ${DEBUG_LOG}
- # copy working temp file to final log location
- cat /proc/$$/fd/3 >> "${DEBUG_LOG}"
- # Straight dump of tailing the logs, can sanitize later if needed.
- cat /proc/$$/fd/4 >> "${DEBUG_LOG}"
+upload_to_tricorder() {
+ local username="pihole"
+ # Set the permissions and owner
+ chmod 644 ${PIHOLE_DEBUG_LOG}
+ chown "$USER":"${username}" ${PIHOLE_DEBUG_LOG}
- echo "::: The debug log can be uploaded to tricorder.pi-hole.net for sharing with developers only."
+ # Let the user know debugging is complete with something strikingly visual
+ log_write ""
+ log_write "${COL_PURPLE}********************************************${COL_NC}"
+ log_write "${COL_PURPLE}********************************************${COL_NC}"
+ log_write "${TICK} ${COL_GREEN}** FINISHED DEBUGGING! **${COL_NC}\n"
+
+ # Provide information on what they should do with their token
+ log_write " * The debug log can be uploaded to tricorder.pi-hole.net for sharing with developers only."
+ log_write " * For more information, see: ${TRICORDER_CONTEST}"
+ log_write " * If available, we'll use openssl to upload the log, otherwise it will fall back to netcat."
+ # If pihole -d is running automatically (usually throught the dashboard)
if [[ "${AUTOMATED}" ]]; then
- echo "::: Debug script running in automated mode, uploading log to tricorder..."
- tricorder=$(cat /var/log/pihole_debug.log | nc tricorder.pi-hole.net 9999)
+ # let the user know
+ log_write "${INFO} Debug script running in automated mode"
+ # and then decide again which tool to use to submit it
+ tricorder_use_nc_or_ssl
+ # If we're not running in automated mode,
else
- read -r -p "::: Would you like to upload the log? [y/N] " response
+ echo ""
+ # give the user a choice of uploading it or not
+ # Users can review the log file locally (or the output of the script since they are the same) and try to self-diagnose their problem
+ read -r -p "[?] Would you like to upload the log? [y/N] " response
case ${response} in
- [yY][eE][sS]|[yY])
- tricorder=$(cat /var/log/pihole_debug.log | nc tricorder.pi-hole.net 9999)
- ;;
- *)
- echo "::: Log will NOT be uploaded to tricorder."
- ;;
+ # If they say yes, run our function for uploading the log
+ [yY][eE][sS]|[yY]) tricorder_use_nc_or_ssl;;
+ # If they choose no, just exit out of the script
+ *) log_write " * Log will ${COL_GREEN}NOT${COL_NC} be uploaded to tricorder.";exit;
esac
fi
- # Check if tricorder.pi-hole.net is reachable and provide token.
- if [ -n "${tricorder}" ]; then
- echo "::: ---=== Your debug token is : ${tricorder} Please make a note of it. ===---"
- echo "::: 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."
+ # Check if tricorder.pi-hole.net is reachable and provide token
+ # along with some additional useful information
+ if [[ -n "${tricorder_token}" ]]; then
+ # Again, try to make this visually striking so the user realizes they need to do something with this information
+ # Namely, provide the Pi-hole devs with the token
+ log_write ""
+ log_write "${COL_PURPLE}***********************************${COL_NC}"
+ log_write "${COL_PURPLE}***********************************${COL_NC}"
+ log_write "${TICK} Your debug token is: ${COL_GREEN}${tricorder_token}${COL_NC}"
+ log_write "${COL_PURPLE}***********************************${COL_NC}"
+ log_write "${COL_PURPLE}***********************************${COL_NC}"
+ log_write ""
+ log_write " * Provide the token above to the Pi-hole team for assistance at"
+ log_write " * ${FORUMS_URL}"
+ log_write " * Your log will self-destruct on our server after ${COL_RED}48 hours${COL_NC}."
+ # If no token was generated
+ else
+ # Show an error and some help instructions
+ log_write "${CROSS} ${COL_RED}There was an error uploading your debug log.${COL_NC}"
+ log_write " * Please try again or contact the Pi-hole team for assistance."
fi
- echo "::: A local copy of the Debug log can be found at : /var/log/pihole_debug.log"
-}
-
-### END FUNCTIONS ###
-# Create temporary file for log
-TEMPLOG=$(mktemp /tmp/pihole_temp.XXXXXX)
-# Open handle 3 for templog
-exec 3>"$TEMPLOG"
-# Delete templog, but allow for addressing via file handle.
-rm "$TEMPLOG"
-
-# Create temporary file for logdump using file handle 4
-DUMPLOG=$(mktemp /tmp/pihole_temp.XXXXXX)
-exec 4>"$DUMPLOG"
-rm "$DUMPLOG"
-
-# Gather version of required packages / repositories
-version_check || echo "REQUIRED FILES MISSING"
-# Check for newer setupVars storage file
-source_file "/etc/pihole/setupVars.conf"
-# Gather information about the running distribution
-distro_check || echo "Distro Check soft fail"
-# Gather processor type
-processor_check || echo "Processor Check soft fail"
-
-ip_check 6 ${IPV6_ADDRESS}
-ip_check 4 ${IPV4_ADDRESS}
-
-daemon_check lighttpd http
-daemon_check dnsmasq domain
-daemon_check pihole-FTL 4711
-checkProcesses
-
-# Check local/IP/Google for IPv4 Resolution
-testResolver 4 "${IPV4_ADDRESS%/*}"
-# If IPv6 enabled, check resolution
-if [[ "${IPV6_ADDRESS}" ]]; then
- testResolver 6 "${IPV6_ADDRESS%/*}"
-fi
-# Poll dnsmasq Pi-hole specific queries
-testChaos
-
-debugLighttpd
-
-files_check "${DNSMASQFILE}"
-dir_check "${DNSMASQCONFDIR}"
-files_check "${WHITELISTFILE}"
-files_check "${BLACKLISTFILE}"
-files_check "${ADLISTFILE}"
-
-
-header_write "Analyzing gravity.list"
-
- gravity_length=$(grep -c ^ "${GRAVITYFILE}") \
- && log_write "${GRAVITYFILE} is ${gravity_length} lines long." \
- || log_echo "Warning: No gravity.list file found!"
-
-header_write "Analyzing pihole.log"
-
- pihole_length=$(grep -c ^ "${PIHOLELOG}") \
- && log_write "${PIHOLELOG} is ${pihole_length} lines long." \
- || log_echo "Warning: No pihole.log file found!"
-
- pihole_size=$(du -h "${PIHOLELOG}" | awk '{ print $1 }') \
- && log_write "${PIHOLELOG} is ${pihole_size}." \
- || log_echo "Warning: No pihole.log file found!"
-
-header_write "Analyzing pihole-FTL.log"
-
- FTL_length=$(grep -c ^ "${FTLLOG}") \
- && log_write "${FTLLOG} is ${FTL_length} lines long." \
- || log_echo "Warning: No pihole-FTL.log file found!"
-
- FTL_size=$(du -h "${FTLLOG}" | awk '{ print $1 }') \
- && log_write "${FTLLOG} is ${FTL_size}." \
- || log_echo "Warning: No pihole-FTL.log file found!"
-
-tail -n50 "${FTLLOG}" >&3
-
-trap finalWork EXIT
+ # Finally, show where the log file is no matter the outcome of the function so users can look at it
+ log_write " * A local copy of the debug log can be found at: ${COL_CYAN}${PIHOLE_DEBUG_LOG_SANITIZED}${COL_NC}\n"
+}
-### Method calls for additional logging ###
-dumpPiHoleLog
+# Run through all the functions we made
+make_temporary_log
+initiate_debug
+# setupVars.conf needs to be sourced before the networking so the values are
+# available to the other functions
+source_setup_variables
+check_component_versions
+check_critical_program_versions
+diagnose_operating_system
+check_selinux
+processor_check
+check_networking
+check_name_resolution
+process_status
+parse_setup_vars
+check_x_headers
+analyze_gravity_list
+show_content_of_pihole_files
+analyze_pihole_log
+copy_to_debug_log
+upload_to_tricorder
diff --git a/advanced/Scripts/piholeLogFlush.sh b/advanced/Scripts/piholeLogFlush.sh
index cc553b32..2187f3ac 100755
--- a/advanced/Scripts/piholeLogFlush.sh
+++ b/advanced/Scripts/piholeLogFlush.sh
@@ -8,8 +8,11 @@
# This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license.
+colfile="/opt/pihole/COL_TABLE"
+source ${colfile}
+
if [[ "$@" != *"quiet"* ]]; then
- echo -n "::: Flushing /var/log/pihole.log ..."
+ echo -ne " ${INFO} Flushing /var/log/pihole.log ..."
fi
if [[ "$@" == *"once"* ]]; then
# Nightly logrotation
@@ -41,5 +44,5 @@ else
fi
if [[ "$@" != *"quiet"* ]]; then
- echo "... done!"
+ echo -e "${OVER} ${TICK} Flushed /var/log/pihole.log"
fi
diff --git a/advanced/Scripts/update.sh b/advanced/Scripts/update.sh
index 4281d69f..a4ada4c8 100755
--- a/advanced/Scripts/update.sh
+++ b/advanced/Scripts/update.sh
@@ -10,10 +10,7 @@
# This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license.
-
-
# Variables
-
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"
@@ -22,9 +19,10 @@ readonly PI_HOLE_FILES_DIR="/etc/.pihole"
# shellcheck disable=SC2034
PH_TEST=true
-# Have to ignore the following rule as spaces in paths are not supported by ShellCheck
-#shellcheck disable=SC1090
+# shellcheck disable=SC1090
source "${PI_HOLE_FILES_DIR}/automated install/basic-install.sh"
+# shellcheck disable=SC1091
+source "/opt/pihole/COL_TABLE"
# is_repo() sourced from basic-install.sh
# make_repo() sourced from basic-install.sh
@@ -52,15 +50,15 @@ GitCheckUpdateAvail() {
# defaults to the current one.
REMOTE="$(git rev-parse "@{upstream}")"
- if [[ ${#LOCAL} == 0 ]]; then
- echo "::: Error: Local revision could not be obtained, ask Pi-hole support."
- echo "::: Additional debugging output:"
+ if [[ "${#LOCAL}" == 0 ]]; then
+ echo -e "\\n ${COL_LIGHT_RED}Error: Local revision could not be obtained, please contact Pi-hole Support
+ Additional debugging output:${COL_NC}"
git status
exit
fi
- if [[ ${#REMOTE} == 0 ]]; then
- echo "::: Error: Remote revision could not be obtained, ask Pi-hole support."
- echo "::: Additional debugging output:"
+ if [[ "${#REMOTE}" == 0 ]]; then
+ echo -e "\\n ${COL_LIGHT_RED}Error: Remote revision could not be obtained, please contact Pi-hole Support
+ Additional debugging output:${COL_NC}"
git status
exit
fi
@@ -80,7 +78,6 @@ GitCheckUpdateAvail() {
}
FTLcheckUpdate() {
-
local FTLversion
FTLversion=$(/usr/bin/pihole-FTL tag)
local FTLlatesttag
@@ -96,57 +93,59 @@ FTLcheckUpdate() {
main() {
local pihole_version_current
local web_version_current
- #shellcheck disable=1090,2154
+ local basicError="\\n ${COL_LIGHT_RED}Unable to complete update, please contact Pi-hole Support${COL_NC}"
+
+ # shellcheck disable=1090,2154
source "${setupVars}"
- #This is unlikely
+ # This is unlikely
if ! is_repo "${PI_HOLE_FILES_DIR}" ; then
- echo "::: Critical Error: Core Pi-hole repo is missing from system!"
- echo "::: Please re-run install script from https://github.com/pi-hole/pi-hole"
+ echo -e "\\n ${COL_LIGHT_RED}Error: Core Pi-hole repo is missing from system!
+ Please re-run install script from https://pi-hole.net${COL_NC}"
exit 1;
fi
- echo "::: Checking for updates..."
+ echo -e " ${INFO} Checking for updates..."
if GitCheckUpdateAvail "${PI_HOLE_FILES_DIR}" ; then
core_update=true
- echo "::: Pi-hole Core: update available"
+ echo -e " ${INFO} Pi-hole Core:\\t${COL_YELLOW}update available${COL_NC}"
else
core_update=false
- echo "::: Pi-hole Core: up to date"
+ echo -e " ${INFO} Pi-hole Core:\\t${COL_LIGHT_GREEN}up to date${COL_NC}"
fi
if FTLcheckUpdate ; then
FTL_update=true
- echo "::: FTL: update available"
+ echo -e " ${INFO} FTL:\\t\\t${COL_YELLOW}update available${COL_NC}"
else
FTL_update=false
- echo "::: FTL: up to date"
+ echo -e " ${INFO} FTL:\\t\\t${COL_LIGHT_GREEN}up to date${COL_NC}"
fi
# Logic: Don't update FTL when there is a core update available
# since the core update will run the installer which will itself
# re-install (i.e. update) FTL
if ${FTL_update} && ! ${core_update}; then
- echo ":::"
- echo "::: FTL out of date"
+ echo ""
+ echo -e " ${INFO} FTL out of date"
FTLdetect
- echo ":::"
+ echo ""
fi
- if [[ ${INSTALL_WEB} == true ]]; then
+ if [[ "${INSTALL_WEB}" == true ]]; then
if ! is_repo "${ADMIN_INTERFACE_DIR}" ; then
- echo "::: Critical Error: Web Admin repo is missing from system!"
- echo "::: Please re-run install script from https://github.com/pi-hole/pi-hole"
+ echo -e "\\n ${COL_LIGHT_RED}Error: Web Admin repo is missing from system!
+ Please re-run install script from https://pi-hole.net${COL_NC}"
exit 1;
fi
if GitCheckUpdateAvail "${ADMIN_INTERFACE_DIR}" ; then
web_update=true
- echo "::: Web Interface: update available"
+ echo -e " ${INFO} Web Interface:\\t${COL_YELLOW}update available${COL_NC}"
else
web_update=false
- echo "::: Web Interface: up to date"
+ echo -e " ${INFO} Web Interface:\\t${COL_LIGHT_GREEN}up to date${COL_NC}"
fi
# Logic
@@ -161,72 +160,69 @@ main() {
if ! ${core_update} && ! ${web_update} ; then
if ! ${FTL_update} ; then
- echo ":::"
- echo "::: Everything is up to date!"
+ echo ""
+ echo -e " ${TICK} Everything is up to date!"
exit 0
fi
-
elif ! ${core_update} && ${web_update} ; then
- echo ":::"
- echo "::: Pi-hole Web Admin files out of date"
+ echo ""
+ echo -e " ${INFO} Pi-hole Web Admin files out of date"
getGitFiles "${ADMIN_INTERFACE_DIR}" "${ADMIN_INTERFACE_GIT_URL}"
-
elif ${core_update} && ! ${web_update} ; then
- echo ":::"
- echo "::: Pi-hole core files out of date"
+ echo ""
+ echo -e " ${INFO} Pi-hole core files out of date"
getGitFiles "${PI_HOLE_FILES_DIR}" "${PI_HOLE_GIT_URL}"
- ${PI_HOLE_FILES_DIR}/automated\ install/basic-install.sh --reconfigure --unattended || echo "Unable to complete update, contact Pi-hole" && exit 1
-
+ ${PI_HOLE_FILES_DIR}/automated\ install/basic-install.sh --reconfigure --unattended || \
+ echo -e "${basicError}" && exit 1
elif ${core_update} && ${web_update} ; then
- echo ":::"
- echo "::: Updating Pi-hole core and web admin files"
+ echo ""
+ echo -e " ${INFO} Updating Pi-hole core and web admin files"
getGitFiles "${PI_HOLE_FILES_DIR}" "${PI_HOLE_GIT_URL}"
- ${PI_HOLE_FILES_DIR}/automated\ install/basic-install.sh --unattended || echo "Unable to complete update, contact Pi-hole" && exit 1
+ ${PI_HOLE_FILES_DIR}/automated\ install/basic-install.sh --unattended || \
+ echo -e "${basicError}" && exit 1
else
- echo "*** Update script has malfunctioned, fallthrough reached. Please contact support"
+ echo -e " ${COL_LIGHT_RED}Update script has malfunctioned, please contact Pi-hole Support${COL_NC}"
exit 1
fi
else # Web Admin not installed, so only verify if core is up to date
if ! ${core_update}; then
if ! ${FTL_update} ; then
- echo ":::"
- echo "::: Everything is up to date!"
+ echo ""
+ echo -e " ${INFO} Everything is up to date!"
exit 0
fi
else
- echo ":::"
- echo "::: Pi-hole core files out of date"
+ echo ""
+ echo -e " ${INFO} Pi-hole Core files out of date"
getGitFiles "${PI_HOLE_FILES_DIR}" "${PI_HOLE_GIT_URL}"
- ${PI_HOLE_FILES_DIR}/automated\ install/basic-install.sh --reconfigure --unattended || echo "Unable to complete update, contact Pi-hole" && exit 1
+ ${PI_HOLE_FILES_DIR}/automated\ install/basic-install.sh --reconfigure --unattended || \
+ echo -e "${basicError}" && exit 1
fi
fi
if [[ "${web_update}" == true ]]; then
web_version_current="$(/usr/local/bin/pihole version --admin --current)"
- echo ":::"
- echo "::: Web Admin version is now at ${web_version_current/* v/v}}"
- echo "::: If you had made any changes in '/var/www/html/admin/', they have been stashed using 'git stash'"
+ echo ""
+ echo -e " ${INFO} Web Admin version is now at ${web_version_current/* v/v}
+ ${INFO} If you had made any changes in '/var/www/html/admin/', they have been stashed using 'git stash'"
fi
if [[ "${core_update}" == true ]]; then
pihole_version_current="$(/usr/local/bin/pihole version --pihole --current)"
- echo ":::"
- echo "::: Pi-hole version is now at ${pihole_version_current/* v/v}}"
- echo "::: If you had made any changes in '/etc/.pihole/', they have been stashed using 'git stash'"
+ echo ""
+ echo -e " ${INFO} Pi-hole version is now at ${pihole_version_current/* v/v}
+ ${INFO} If you had made any changes in '/etc/.pihole/', they have been stashed using 'git stash'"
fi
- if [[ ${FTL_update} == true ]]; then
- FTL_version_current="$(/usr/local/bin/pihole version --ftl --current)"
- echo ":::"
- echo "::: FTL version is now at ${FTL_version_current/* v/v}}"
+ if [[ "${FTL_update}" == true ]]; then
+ FTL_version_current="$(/usr/bin/pihole-FTL tag)"
+ echo -e "\\n ${INFO} FTL version is now at ${FTL_version_current/* v/v}"
start_service pihole-FTL
enable_service pihole-FTL
fi
-
echo ""
exit 0
-
}
main
diff --git a/advanced/Scripts/updatecheck.sh b/advanced/Scripts/updatecheck.sh
new file mode 100755
index 00000000..9b79c4cb
--- /dev/null
+++ b/advanced/Scripts/updatecheck.sh
@@ -0,0 +1,59 @@
+#!/usr/bin/env bash
+# Pi-hole: A black hole for Internet advertisements
+# (c) 2017 Pi-hole, LLC (https://pi-hole.net)
+# Network-wide ad blocking via your own hardware.
+#
+# Checks for updates via GitHub
+#
+# This file is copyright under the latest version of the EUPL.
+# Please see LICENSE file for your rights under this license.
+
+# Credit: https://stackoverflow.com/a/46324904
+function json_extract() {
+ local key=$1
+ local json=$2
+
+ local string_regex='"([^"\]|\\.)*"'
+ local number_regex='-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?'
+ local value_regex="${string_regex}|${number_regex}|true|false|null"
+ local pair_regex="\"${key}\"[[:space:]]*:[[:space:]]*(${value_regex})"
+
+ if [[ ${json} =~ ${pair_regex} ]]; then
+ echo $(sed 's/^"\|"$//g' <<< "${BASH_REMATCH[1]}")
+ else
+ return 1
+ fi
+}
+
+GITHUB_CORE_VERSION="$(json_extract tag_name "$(curl -q 'https://api.github.com/repos/pi-hole/pi-hole/releases/latest' 2> /dev/null)")"
+GITHUB_WEB_VERSION="$(json_extract tag_name "$(curl -q 'https://api.github.com/repos/pi-hole/AdminLTE/releases/latest' 2> /dev/null)")"
+GITHUB_FTL_VERSION="$(json_extract tag_name "$(curl -q 'https://api.github.com/repos/pi-hole/FTL/releases/latest' 2> /dev/null)")"
+
+echo "${GITHUB_CORE_VERSION} ${GITHUB_WEB_VERSION} ${GITHUB_FTL_VERSION}" > "/etc/pihole/GitHubVersions"
+
+function get_local_branch() {
+ # Return active branch
+ cd "${1}" 2> /dev/null || return 1
+ git rev-parse --abbrev-ref HEAD || return 1
+}
+
+CORE_BRANCH="$(get_local_branch /etc/.pihole)"
+WEB_BRANCH="$(get_local_branch /var/www/html/admin)"
+#FTL_BRANCH="$(pihole-FTL branch)"
+# Don't store FTL branch until the next release of FTL which
+# supports returning the branch in an easy way
+FTL_BRANCH="XXX"
+
+echo "${CORE_BRANCH} ${WEB_BRANCH} ${FTL_BRANCH}" > "/etc/pihole/localbranches"
+
+function get_local_version() {
+ # Return active branch
+ cd "${1}" 2> /dev/null || return 1
+ git describe --long --dirty --tags || return 1
+}
+
+CORE_VERSION="$(get_local_version /etc/.pihole)"
+WEB_VERSION="$(get_local_version /var/www/html/admin)"
+FTL_VERSION="$(pihole-FTL version)"
+
+echo "${CORE_VERSION} ${WEB_VERSION} ${FTL_VERSION}" > "/etc/pihole/localversions"
diff --git a/advanced/Scripts/webpage.sh b/advanced/Scripts/webpage.sh
index 8419aa8d..07bc160f 100755
--- a/advanced/Scripts/webpage.sh
+++ b/advanced/Scripts/webpage.sh
@@ -1,4 +1,6 @@
#!/usr/bin/env bash
+# shellcheck disable=SC1090
+
# Pi-hole: A black hole for Internet advertisements
# (c) 2017 Pi-hole, LLC (https://pi-hole.net)
# Network-wide ad blocking via your own hardware.
@@ -14,20 +16,26 @@ readonly dhcpconfig="/etc/dnsmasq.d/02-pihole-dhcp.conf"
# 03 -> wildcards
readonly dhcpstaticconfig="/etc/dnsmasq.d/04-pihole-static-dhcp.conf"
+coltable="/opt/pihole/COL_TABLE"
+if [[ -f ${coltable} ]]; then
+ source ${coltable}
+fi
+
helpFunc() {
echo "Usage: pihole -a [options]
Example: pihole -a -p password
Set options for the Admin Console
Options:
- -f, flush Flush the Pi-hole log
-p, password Set Admin Console password
-c, celsius Set Celsius as preferred temperature unit
-f, fahrenheit Set Fahrenheit as preferred temperature unit
-k, kelvin Set Kelvin as preferred temperature unit
+ -r, hostrecord Add a name to the DNS associated to an IPv4/IPv6 address
+ -e, email Set an administrative contact address for the Block Page
-h, --help Show this help dialog
-i, interface Specify dnsmasq's interface listening behavior
- Add '-h' for more info on interface usage"
+ Add '-h' for more info on interface usage"
exit 0
}
@@ -58,6 +66,7 @@ delete_dnsmasq_setting() {
SetTemperatureUnit() {
change_setting "TEMPERATUREUNIT" "${unit}"
+ echo -e " ${TICK} Set temperature unit to ${unit}"
}
HashPassword() {
@@ -84,12 +93,15 @@ SetWebPassword() {
readonly PASSWORD="${args[2]}"
readonly CONFIRM="${PASSWORD}"
else
+ # Prevents a bug if the user presses Ctrl+C and it continues to hide the text typed.
+ # So we reset the terminal via stty if the user does press Ctrl+C
+ trap '{ echo -e "\nNo password will be set" ; stty sane ; exit 1; }' INT
read -s -p "Enter New Password (Blank for no password): " PASSWORD
echo ""
if [ "${PASSWORD}" == "" ]; then
change_setting "WEBPASSWORD" ""
- echo "Password Removed"
+ echo -e " ${TICK} Password Removed"
exit 0
fi
@@ -98,12 +110,12 @@ SetWebPassword() {
fi
if [ "${PASSWORD}" == "${CONFIRM}" ] ; then
- hash=$(HashPassword ${PASSWORD})
+ hash=$(HashPassword "${PASSWORD}")
# Save hash to file
change_setting "WEBPASSWORD" "${hash}"
- echo "New password set"
+ echo -e " ${TICK} New password set"
else
- echo "Passwords don't match. Your password has not been changed"
+ echo -e " ${CROSS} Passwords don't match. Your password has not been changed"
exit 1
fi
}
@@ -208,16 +220,16 @@ SetExcludeClients() {
change_setting "API_EXCLUDE_CLIENTS" "${args[2]}"
}
+Poweroff(){
+ nohup bash -c "sleep 5; poweroff" &> /dev/null </dev/null &
+}
+
Reboot() {
nohup bash -c "sleep 5; reboot" &> /dev/null </dev/null &
}
RestartDNS() {
- if [ -x "$(command -v systemctl)" ]; then
- systemctl restart dnsmasq &> /dev/null
- else
- service dnsmasq restart &> /dev/null
- fi
+ /usr/local/bin/pihole restartdns
}
SetQueryLogOptions() {
@@ -243,7 +255,12 @@ ProcessDHCPSettings() {
if [[ "${DHCP_LEASETIME}" == "0" ]]; then
leasetime="infinite"
elif [[ "${DHCP_LEASETIME}" == "" ]]; then
- leasetime="24h"
+ leasetime="24"
+ change_setting "DHCP_LEASETIME" "${leasetime}"
+ elif [[ "${DHCP_LEASETIME}" == "24h" ]]; then
+ #Installation is affected by known bug, introduced in a previous version.
+ #This will automatically clean up setupVars.conf and remove the unnecessary "h"
+ leasetime="24"
change_setting "DHCP_LEASETIME" "${leasetime}"
else
leasetime="${DHCP_LEASETIME}h"
@@ -275,7 +292,9 @@ ra-param=*,0,0
fi
else
- rm "${dhcpconfig}" &> /dev/null
+ if [[ -f "${dhcpconfig}" ]]; then
+ rm "${dhcpconfig}" &> /dev/null
+ fi
fi
}
@@ -373,12 +392,23 @@ RemoveDHCPStaticAddress() {
}
SetHostRecord() {
- if [ -n "${args[3]}" ]; then
+ if [[ "${1}" == "-h" ]] || [[ "${1}" == "--help" ]]; then
+ echo "Usage: pihole -a hostrecord <domain> [IPv4-address],[IPv6-address]
+Example: 'pihole -a hostrecord home.domain.com 192.168.1.1,2001:db8:a0b:12f0::1'
+Add a name to the DNS associated to an IPv4/IPv6 address
+
+Options:
+ \"\" Empty: Remove host record
+ -h, --help Show this help dialog"
+ exit 0
+ fi
+
+ if [[ -n "${args[3]}" ]]; then
change_setting "HOSTRECORD" "${args[2]},${args[3]}"
- echo "Setting host record for ${args[2]} -> ${args[3]}"
+ echo -e " ${TICK} Setting host record for ${args[2]} to ${args[3]}"
else
change_setting "HOSTRECORD" ""
- echo "Removing host record"
+ echo -e " ${TICK} Removing host record"
fi
ProcessDNSSettings
@@ -387,9 +417,30 @@ SetHostRecord() {
RestartDNS
}
+SetAdminEmail() {
+ if [[ "${1}" == "-h" ]] || [[ "${1}" == "--help" ]]; then
+ echo "Usage: pihole -a email <address>
+Example: 'pihole -a email admin@address.com'
+Set an administrative contact address for the Block Page
+
+Options:
+ \"\" Empty: Remove admin contact
+ -h, --help Show this help dialog"
+ exit 0
+ fi
+
+ if [[ -n "${args[2]}" ]]; then
+ change_setting "ADMIN_EMAIL" "${args[2]}"
+ echo -e " ${TICK} Setting admin contact to ${args[2]}"
+ else
+ change_setting "ADMIN_EMAIL" ""
+ echo -e " ${TICK} Removing admin contact"
+ fi
+}
+
SetListeningMode() {
source "${setupVars}"
-
+
if [[ "$3" == "-h" ]] || [[ "$3" == "--help" ]]; then
echo "Usage: pihole -a -i [interface]
Example: 'pihole -a -i local'
@@ -402,15 +453,15 @@ Interfaces:
all Listen on all interfaces, permit all origins"
exit 0
fi
-
+
if [[ "${args[2]}" == "all" ]]; then
- echo "Listening on all interfaces, permiting all origins, hope you have a firewall!"
+ echo -e " ${INFO} Listening on all interfaces, permiting all origins. Please use a firewall!"
change_setting "DNSMASQ_LISTENING" "all"
elif [[ "${args[2]}" == "local" ]]; then
- echo "Listening on all interfaces, permitting only origins that are at most one hop away (local devices)"
+ echo -e " ${INFO} Listening on all interfaces, permiting origins from one hop away (LAN)"
change_setting "DNSMASQ_LISTENING" "local"
else
- echo "Listening only on interface ${PIHOLE_INTERFACE}"
+ echo -e " ${INFO} Listening only on interface ${PIHOLE_INTERFACE}"
change_setting "DNSMASQ_LISTENING" "single"
fi
@@ -428,6 +479,11 @@ Teleporter() {
php /var/www/html/admin/scripts/pi-hole/php/teleporter.php > "pi-hole-teleporter_${datetimestamp}.zip"
}
+audit()
+{
+ echo "${args[2]}" >> /etc/pihole/auditlog.list
+}
+
main() {
args=("$@")
@@ -439,6 +495,7 @@ main() {
"setdns" ) SetDNSServers;;
"setexcludedomains" ) SetExcludeDomains;;
"setexcludeclients" ) SetExcludeClients;;
+ "poweroff" ) Poweroff;;
"reboot" ) Reboot;;
"restartdns" ) RestartDNS;;
"setquerylog" ) SetQueryLogOptions;;
@@ -450,10 +507,12 @@ main() {
"resolve" ) ResolutionSettings;;
"addstaticdhcp" ) AddDHCPStaticAddress;;
"removestaticdhcp" ) RemoveDHCPStaticAddress;;
- "hostrecord" ) SetHostRecord;;
+ "-r" | "hostrecord" ) SetHostRecord "$3";;
+ "-e" | "email" ) SetAdminEmail "$3";;
"-i" | "interface" ) SetListeningMode "$@";;
"-t" | "teleporter" ) Teleporter;;
"adlist" ) CustomizeAdLists;;
+ "audit" ) audit;;
* ) helpFunc;;
esac
diff --git a/advanced/blockingpage.css b/advanced/blockingpage.css
index 7e11dbd0..e74844d1 100644
--- a/advanced/blockingpage.css
+++ b/advanced/blockingpage.css
@@ -1,136 +1,383 @@
-/* CSS Reset */
-html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline; }
-article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; }
-body { line-height: 1; }
-ol, ul { list-style: none; }
-blockquote, q { quotes: none; }
-blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; }
-table { border-collapse: collapse; border-spacing: 0; }
-html { height: 100%; overflow-x: hidden; }
-
-/* General Style */
-a { color: rgba(0,60,120,0.95); text-decoration: none; } /* 1E3C5A */
-a:hover { color: rgba(210,120,0,0.95); transition-duration: .2s; } /* 255, 128, 0 */
-divs a { border-bottom: 1px dashed rgba(30,60,90,0.3); }
-b { font-weight: bold; }
-i { font-style: italic; }
-
-footer, pre, td { font-family: monospace; padding-left: 15px; }
-/*body, header { background: #E1E1E1; }*/
+/* Pi-hole: A black hole for Internet advertisements
+* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
+* Network-wide ad blocking via your own hardware.
+*
+* This file is copyright under the latest version of the EUPL.
+* Please see LICENSE file for your rights under this license. */
+
+/* Text Customisation Options ======> */
+.title:before { content: "Website Blocked"; }
+.altBtn:before { content: "Why am I here?"; }
+.linkPH:before { content: "About Pi-hole"; }
+.linkEmail:before { content: "Contact Admin"; }
+
+#bpOutput.add:before { content: "Info"; }
+#bpOutput.add:after { content: "The domain is being whitelisted..."; }
+#bpOutput.error:before, .unhandled:before { content: "Error"; }
+#bpOutput.unhandled:after { content: "An unhandled exception occured. This may happen when your browser is unable to load jQuery, or when the webserver is denying access to the Pi-hole API."; }
+#bpOutput.success:before { content: "Success"; }
+#bpOutput.success:after { content: "Website has been whitelisted! You may need to flush your DNS cache"; }
+
+.recentwl:before { content: "This site has been whitelisted. Please flush your DNS cache and/or restart your browser."; }
+.unknown:before { content: "This website is not found in any of Pi-hole's blacklists. The reason you have arrived here is unknown."; }
+.cname:before { content: "This site is an alias for "; } /* <a href="http://cname.com">cname.com</a> */
+.cname:after { content: ", which may be blocked by Pi-hole."; }
+
+.blacklist:before { content: "Manually Blacklisted"; }
+.wildcard:before { content: "Manually Blacklisted by Wildcard"; }
+.noblock:before { content: "Not found on any Blacklist"; }
+
+#bpBlock:before { content: "Access to the following website has been denied:"; }
+#bpFlag:before { content: "This is primarily due to being flagged as:"; }
+
+#bpHelpTxt:before { content: "If you have an ongoing use for this website, please "; }
+#bpHelpTxt a:before, #bpHelpTxt span:before { content: "ask the administrator"; }
+#bpHelpTxt:after{ content: " of the Pi-hole on this network to have it whitelisted"; }
+
+#bpBack:before { content: "Back to safety"; }
+#bpInfo:before { content: "Technical Info"; }
+#bpFoundIn:before { content: "This site is found in "; }
+#bpFoundIn span:after { content: " of "; }
+#bpFoundIn:after { content: " lists:"; }
+#bpWhitelist:before { content: "Whitelist"; }
+
+footer span:before { content: "Page generated on "; }
+
+/* Hide whitelisting form entirely */
+/* #bpWLButtons { display: none; } */
+/* Text Customisation Options <=============================== */
+
+/* http://necolas.github.io/normalize.css ======> */
+html { font-family: sans-serif; line-height: 1.15; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; }
+body { margin: 0; }
+article, aside, footer, header, nav, section { display: block; }
+h1 { font-size: 2em; margin: 0.67em 0; }
+figcaption, figure, main { display: block; }
+figure { margin: 1em 40px; }
+hr { box-sizing: content-box; height: 0; overflow: visible; }
+pre { font-family: monospace, monospace; font-size: 1em; }
+a { background-color: transparent; -webkit-text-decoration-skip: objects; }
+a:active, a:hover { outline-width: 0; }
+abbr[title] { border-bottom: none; text-decoration: underline; text-decoration: underline dotted; }
+b, strong { font-weight: inherit; }
+b, strong { font-weight: bolder; }
+code, kbd, samp { font-family: monospace, monospace; font-size: 1em; }
+dfn { font-style: italic; }
+mark { background-color: #ff0; color: #000; }
+small { font-size: 80%; }
+sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
+sub { bottom: -0.25em; }
+sup { top: -0.5em; }
+audio, video { display: inline-block; }
+audio:not([controls]) { display: none; height: 0; }
+img { border-style: none; }
+svg:not(:root) { overflow: hidden; }
+button, input, optgroup, select, textarea { font-family: sans-serif; font-size: 100%; line-height: 1.15; margin: 0; }
+button, input { overflow: visible; }
+button, select { text-transform: none; }
+button, html [type="button"], [type="reset"], [type="submit"] { -webkit-appearance: button; }
+button::-moz-focus-inner, [type="button"]::-moz-focus-inner, [type="reset"]::-moz-focus-inner, [type="submit"]::-moz-focus-inner { border-style: none; padding: 0; }
+button:-moz-focusring, [type="button"]:-moz-focusring, [type="reset"]:-moz-focusring, [type="submit"]:-moz-focusring { outline: 1px dotted ButtonText; }
+fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em; }
+legend { box-sizing: border-box; color: inherit; display: table; max-width: 100%; padding: 0; white-space: normal; }
+progress { display: inline-block; vertical-align: baseline; }
+textarea { overflow: auto; }
+[type="checkbox"], [type="radio"] { box-sizing: border-box; padding: 0; }
+[type="number"]::-webkit-inner-spin-button, [type="number"]::-webkit-outer-spin-button { height: auto; }
+[type="search"] { -webkit-appearance: textfield; outline-offset: -2px; }
+[type="search"]::-webkit-search-cancel-button, [type="search"]::-webkit-search-decoration { -webkit-appearance: none; }
+::-webkit-file-upload-button { -webkit-appearance: button; font: inherit; }
+details, menu { display: block; }
+summary { display: list-item; }
+canvas { display: inline-block; }
+template { display: none; }
+[hidden] { display: none; }
+/* Normalize.css <=============================== */
+
+html { font-size: 62.5%; }
+
+a { color: #3c8dbc; text-decoration: none; }
+a:hover { color: #72afda; text-decoration: underline; }
+b { color: rgb(68,68,68); }
+p { margin: 0; }
+
+label, .buttons a {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+label, .buttons *:not([disabled]) { cursor: pointer; }
+
+/* Touch device dark tap highlight */
+header h1 a, label, .buttons * { -webkit-tap-highlight-color: transparent; }
+
+/* Webkit Focus Glow */
+textarea, input, button { outline: none; }
+
+@font-face {
+ font-family: "Source Sans Pro";
+ font-style: normal;
+ font-weight: 400;
+ src: local("Source Sans Pro"), local("SourceSansPro-Regular"), url("/admin/style/vendor/SourceSansPro/SourceSansPro-Regular.ttf") format("truetype");
+}
+
+@font-face {
+ font-family: "Source Sans Pro";
+ font-style: normal;
+ font-weight: 700;
+ src: local("Source Sans Pro Bold"), local("SourceSansPro-Bold"), url("/admin/style/vendor/SourceSansPro/SourceSansPro-Bold.ttf") format("truetype");
+}
body {
- background-image: -webkit-linear-gradient(top, rgba(240,240,240,0.95), rgba(190,190,190,0.95));
- background-image: linear-gradient(to bottom, rgba(240,240,240,0.95), rgba(190,190,190,0.95));
- background-attachment: fixed;
- color: rgba(64,64,64,0.95);
- font: 14px, sans-serif;
- line-height: 1em;
+ background: #dbdbdb url("/admin/img/boxed-bg.jpg") repeat fixed;
+ color: #333;
+ font: 1.4rem "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ line-height: 2.2rem;
+}
+
+/* User is greeted with a splash page when browsing to Pi-hole IP address */
+#splashpage { background: #222; color: rgba(255,255,255,0.7); text-align: center; }
+#splashpage img { margin: 5px; width: 256px; }
+#splashpage b { color: inherit; }
+
+#bpWrapper {
+ margin: 0 auto;
+ max-width: 1250px;
+ box-shadow: 0 0 8px rgba(0,0,0,0.5);
}
header {
- min-width: 320px;
- width: 100%;
- text-shadow: 0 1px rgba(255,255,255,0.6);
- display: table;
- table-layout: fixed;
- border: 1px solid rgba(0,0,0,0.25);
- border-top-color: rgba(255,255,255,0.85);
- border-style: solid none;
- background-image: -webkit-linear-gradient(top, rgba(240,240,240,0.95), rgba(220,220,220,0.95));
- background-image: linear-gradient(to bottom, rgba(240,240,240,0.95), rgba(220,220,220,0.95));
- box-shadow: 0 0 1px 1px rgba(0,0,0,0.04);
-}
-
-header h1, header div {
- display: table-cell;
- color: inherit;
- font-weight: bold;
- vertical-align: middle;
- white-space: nowrap;
- overflow: hidden;
- box-sizing: border-box;
-}
-
-header h1 {
- font-size: 22px;
- font-weight: bold;
- width: 100%;
- padding: 8px 0;
- text-indent: 32px;
- background: url("http://pi.hole/admin/img/logo.svg") left no-repeat;
- background-size: 30px 22px;
-}
-
-header h1 a, h1 a:hover { color: inherit; }
-header .alt { width: 85px; font-size: 0.8em; padding-right: 4px; text-align: right; line-height: 1.25em; }
-.active { color: green; }
-.inactive { color: red; }
+ background: #3c8dbc;
+ display: table;
+ position: relative;
+ width: 100%;
+}
+
+header h1, header h1 a, header .spc, header #bpAlt label {
+ display: table-cell;
+ color: #fff;
+ white-space: nowrap;
+ vertical-align: middle;
+ height: 50px; /* Must match #bpAbout top value */
+}
+
+h1 a {
+ background-color: rgba(0,0,0,0.1);
+ font-family: "Helvetica Neue", Helvetica, Arial ,sans-serif;
+ font-size: 2rem;
+ font-weight: normal;
+ min-width: 230px;
+ text-align: center;
+}
+
+h1 a:hover, header #bpAlt:hover { background-color: rgba(0,0,0,0.12); color: inherit; text-decoration: none; }
+
+header .spc { width: 100%; }
+
+header #bpAlt label {
+ background: url("/admin/img/logo.svg") no-repeat center left 15px;
+ background-size: 15px 23px;
+ padding: 0 15px;
+ text-indent: 30px;
+}
+
+[type=checkbox][id$="Toggle"] { display: none; }
+[type=checkbox][id$="Toggle"]:checked ~ #bpAbout,
+[type=checkbox][id$="Toggle"]:checked ~ #bpMoreInfo {
+ display: block; }
+
+/* Click anywhere else on screen to hide #bpAbout */
+#bpAboutToggle:checked {
+ display: block;
+ height: 300px; /* VH Fallback */
+ height: 100vh;
+ left: 0;
+ top: 0;
+ opacity: 0;
+ position: absolute;
+ width: 100%;
+}
+
+#bpAbout {
+ background: #3c8dbc;
+ border-bottom-left-radius: 5px;
+ border: 1px solid #FFF;
+ border-right-width: 0;
+ box-shadow: -1px 1px 1px rgba(0,0,0,0.12);
+ box-sizing: border-box;
+ display: none;
+ font-size: 1.7rem;
+ top: 50px;
+ position: absolute;
+ right: 0;
+ width: 280px;
+ z-index: 1;
+}
+
+.aboutPH {
+ box-sizing: border-box;
+ color: rgba(255,255,255,0.8);
+ display: block;
+ padding: 10px;
+ width: 100%;
+ text-align: center;
+}
+
+.aboutImg {
+ background: url("/admin/img/logo.svg") no-repeat center;
+ background-size: 90px 90px;
+ height: 90px;
+ margin: 0 auto;
+ padding: 2px;
+ width: 90px;
+}
+
+.aboutPH p { margin: 10px 0; }
+.aboutPH small { display: block; font-size: 1.2rem; }
+
+.aboutLink {
+ background: #fff;
+ border-top: 1px solid #ddd;
+ display: table;
+ font-size: 1.4rem;
+ text-align: center;
+ width: 100%;
+}
+
+.aboutLink a {
+ display: table-cell;
+ padding: 14px;
+ min-width: 50%;
+}
main {
- display: block;
- width: 80%;
- padding: 10px;
- font-size: 1em;
- background-color: rgba(255,255,255,0.85);
- margin: 8px auto;
- box-sizing: border-box;
- border: 1px solid rgba(0,0,0,0.25);
- box-shadow: 4px 4px rgba(0,0,0,0.1);
- line-height: 1.2em;
- border-radius: 8px;
-}
-
-h2 { /* Rgba is shared with .transparent th */
- font: 1.15em sans-serif;
- background-color: rgba(255,0,0,0.4);
- text-shadow: none;
- line-height: 1.1em;
- padding-bottom: 1px;
- margin-top: 8px;
- margin-bottom: 4px;
- background: -webkit-linear-gradient(left, rgba(0,0,0,0.25), transparent 80%) no-repeat;
- background: linear-gradient(to right, rgba(0,0,0,0.25), transparent 80%) no-repeat;
- background-size: 100% 1px;
- background-position: 0 17px;
-}
-
-h2:first-child { margin-top: 0; }
-h2 ~ *:not(h2) { margin-left: 4px; }
-li { padding: 2px 0; }
-li::before { content: "\00BB\00a0"; }
-li a { position: relative; top: 1px; } /* Center bullet-point arrows */
-
-/* Button Style */
-.buttons a, button, input, .transparent th a { /* Swapped rgba is shared with input[type='url'] */
- display: inline-block;
- color: rgba(32,32,32,0.9);
- font-weight: bold;
- text-align: center;
- cursor: pointer;
- text-shadow: 0 1px rgba(255,255,255,0.2);
- line-height: 0.86em;
- font-size: 1em;
- padding: 4px 8px;
- background: #FAFAFA;
- background-image: -webkit-linear-gradient(top, rgba(255,255,255,0.05), rgba(0,0,0,0.05));
- background-image: linear-gradient(to bottom, rgba(255,255,255,0.05), rgba(0,0,0,0.05));
- border: 1px solid rgba(0,0,0,0.25);
- border-radius: 4px;
- box-shadow: 0 1px 0 rgba(0,0,0,0.04);
-}
-
-.buttons { white-space: nowrap; width: 100%; display: table; }
-.buttons33 { white-space: nowrap; width: 33.333%; display: table; text-align: center; margin-left: 33.333% }
-.mini a { width: 50%; }
-a.safe { background-color: rgba(0,220,0,0.5); }
-button.safe { background-color: rgba(0,220,0,0.5); }
-a.warn { background-color: rgba(220,0,0,0.5); }
-
-.blocked a, .mini a { display: table-cell; }
-.blocked a.safe50 { width: 50%; background-color: rgba(0,220,0,0.5); }
-.blocked a.safe33 { width: 33.333%; background-color: rgba(0,220,0,0.5); }
-
-/* Types of text */
-.msg { white-space: pre; overflow: auto; -webkit-overflow-scrolling: touch; display: block; line-height: 1.2em; font-weight: bold; font-size: 1.15em; margin: 4px 8px 8px 8px; white-space: pre-line; }
-
-footer { font-size: 0.8em; text-align: center; width: 87%; margin: 4px auto; }
+ background: #ecf0f5;
+ font-size: 1.65rem;
+ padding: 10px;
+}
+
+#bpOutput {
+ background: #00c0ef;
+ border-radius: 3px;
+ border: 1px solid rgba(0,0,0,0.1);
+ color: #fff;
+ font-size: 1.4rem;
+ margin-bottom: 10px;
+ margin-top: 5px;
+ padding: 15px;
+}
+
+#bpOutput:before {
+ background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='7' height='14' viewBox='0 0 7 14'%3E%3Cpath fill='%23fff' d='M6,11a1.371,1.371,0,0,1,1,1v1a1.371,1.371,0,0,1-1,1H1a1.371,1.371,0,0,1-1-1V12a1.371,1.371,0,0,1,1-1H2V8H1A1.371,1.371,0,0,1,0,7V6A1.371,1.371,0,0,1,1,5H4A1.371,1.371,0,0,1,5,6v5H6ZM3.5,0A1.5,1.5,0,1,1,2,1.5,1.5,1.5,0,0,1,3.5,0Z'/%3E%3C/svg%3E") no-repeat center left;
+ display: block;
+ font-size: 1.8rem;
+ text-indent: 15px;
+}
+
+#bpOutput.hidden { display: none; }
+#bpOutput.success { background: #00a65a; }
+#bpOutput.error { background: #dd4b39; }
+
+.blockMsg, .flagMsg {
+ font: bold 1.8rem Consolas, Courier, monospace;
+ padding: 5px 10px 10px 10px;
+ text-indent: 15px;
+}
+
+#bpHelpTxt { padding-bottom: 10px; }
+
+.buttons {
+ border-spacing: 5px 0;
+ display: table;
+ width: 100%;
+}
+
+.buttons * {
+ -moz-appearance: none;
+ -webkit-appearance: none;
+ border-radius: 3px;
+ border: 1px solid rgba(0,0,0,0.1);
+ box-sizing: content-box;
+ display: table-cell;
+ font-size: 1.65rem;
+ margin-right: 5px;
+ min-height: 20px;
+ padding: 6px 12px;
+ position: relative;
+ text-align: center;
+ vertical-align: top;
+ white-space: nowrap;
+ width: auto;
+}
+
+.buttons a:hover { text-decoration: none; }
+
+/* Button hover dark overlay */
+.buttons *:not(input):not([disabled]):hover {
+ background-image: linear-gradient(to bottom, rgba(0,0,0,0.1), rgba(0,0,0,0.1));
+ color: #FFF;
+}
+
+/* Button active shadow inset */
+.buttons *:not([disabled]):not(input):active {
+ box-shadow: inset 0 3px 5px rgba(0,0,0,0.125);
+}
+
+/* Input border colour */
+.buttons *:not([disabled]):hover, .buttons input:focus {
+ border-color: rgba(0,0,0,0.25);
+}
+
+#bpButtons * { width: 50%; color: #FFF; }
+#bpBack { background-color: #00a65a; }
+#bpInfo { background-color: #3c8dbc; }
+#bpWhitelist { background-color: #dd4b39; }
+
+#blockpage .buttons [type=password][disabled] { color: rgba(0,0,0,1); }
+#blockpage .buttons [disabled] { color: rgba(0,0,0,0.55); background-color: #e3e3e3; }
+#blockpage .buttons [type=password]:-ms-input-placeholder { color: rgba(51,51,51,0.8); }
+
+input[type=password] { font-size: 1.5rem; }
+
+@keyframes slidein { from { max-height: 0; opacity: 0; } to { max-height: 300px; opacity: 1; } }
+#bpMoreToggle:checked ~ #bpMoreInfo { display: block; margin-top: 8px; animation: slidein 0.05s linear; }
+#bpMoreInfo { display: none; margin-top: 10px; }
+
+#bpQueryOutput {
+ font-size: 1.2rem;
+ line-height: 1.65rem;
+ margin: 5px 0 0 0;
+ overflow: auto;
+ padding: 0 5px;
+ -webkit-overflow-scrolling: touch;
+}
+
+#bpQueryOutput span { margin-right: 4px; }
+
+#bpWLButtons { width: auto; margin-top: 10px; }
+#bpWLButtons * { display: inline-block; }
+#bpWLDomain { display: none; }
+#bpWLPassword { width: 160px; }
+#bpWhitelist { color: #fff; }
+
+footer {
+ background: #fff;
+ border-top: 1px solid #d2d6de;
+ color: #444;
+ font: 1.2rem Consolas, Courier, monospace;
+ padding: 8px;
+}
+
+/* Responsive Content */
+@media only screen and (max-width: 500px) {
+ h1 a { font-size: 1.8rem; min-width: 170px; }
+ footer span:before { content: "Generated "; }
+ footer span { display: block; }
+}
+
+@media only screen and (min-width: 1251px) {
+ #bpWrapper, footer { border-radius: 0 0 5px 5px; }
+ #bpAbout { border-right-width: 1px; }
+}
diff --git a/advanced/index.js b/advanced/index.js
deleted file mode 100644
index c9da5aff..00000000
--- a/advanced/index.js
+++ /dev/null
@@ -1 +0,0 @@
-var x = "Pi-hole: A black hole for Internet advertisements."
diff --git a/advanced/index.php b/advanced/index.php
index 1dd5acc7..5c2f250d 100644
--- a/advanced/index.php
+++ b/advanced/index.php
@@ -1,224 +1,345 @@
<?php
-/* Detailed Pi-hole Block Page: Show "Website Blocked" if user browses to site, but not to image/file requests based on the work of WaLLy3K for DietPi & Pi-Hole */
-
-function validIP($address){
- if (preg_match('/[.:0]/', $address) && !preg_match('/[1-9a-f]/', $address)) {
- // Test if address contains either `:` or `0` but not 1-9 or a-f
- return false;
- }
- return !filter_var($address, FILTER_VALIDATE_IP) === false;
+/* Pi-hole: A black hole for Internet advertisements
+* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
+* Network-wide ad blocking via your own hardware.
+*
+* This file is copyright under the latest version of the EUPL.
+* Please see LICENSE file for your rights under this license. */
+
+// Sanitise HTTP_HOST output
+$serverName = htmlspecialchars($_SERVER["HTTP_HOST"]);
+
+if (!is_file("/etc/pihole/setupVars.conf"))
+ die("[ERROR] File not found: <code>/etc/pihole/setupVars.conf</code>");
+
+// Get values from setupVars.conf
+$setupVars = parse_ini_file("/etc/pihole/setupVars.conf");
+$svPasswd = !empty($setupVars["WEBPASSWORD"]);
+$svEmail = (!empty($setupVars["ADMIN_EMAIL"]) && filter_var($setupVars["ADMIN_EMAIL"], FILTER_VALIDATE_EMAIL)) ? $setupVars["ADMIN_EMAIL"] : "";
+unset($setupVars);
+
+// Set landing page location, found within /var/www/html/
+$landPage = "../landing.php";
+
+// Define array for hostnames to be accepted as self address for splash page
+$authorizedHosts = [];
+if (!empty($_SERVER["FQDN"])) {
+ // If setenv.add-environment = ("fqdn" => "true") is configured in lighttpd,
+ // append $serverName to $authorizedHosts
+ array_push($authorizedHosts, $serverName);
+} else if (!empty($_SERVER["VIRTUAL_HOST"])) {
+ // Append virtual hostname to $authorizedHosts
+ array_push($authorizedHosts, $_SERVER["VIRTUAL_HOST"]);
+}
+
+// Set which extension types render as Block Page (Including "" for index.ext)
+$validExtTypes = array("asp", "htm", "html", "php", "rss", "xml", "");
+
+// Get extension of current URL
+$currentUrlExt = pathinfo($_SERVER["REQUEST_URI"], PATHINFO_EXTENSION);
+
+// Check if this is served over HTTP or HTTPS
+if(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == "on") {
+ $proto = "https";
+} else {
+ $proto = "http";
}
-$uri = escapeshellcmd($_SERVER['REQUEST_URI']);
-$serverName = escapeshellcmd($_SERVER['SERVER_NAME']);
+// Set mobile friendly viewport
+$viewPort = '<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>';
-// If the server name is 'pi.hole', it's likely a user trying to get to the admin panel.
-// Let's be nice and redirect them.
-if ($serverName === 'pi.hole')
-{
- header('HTTP/1.1 301 Moved Permanently');
- header("Location: /admin/");
+// Set response header
+function setHeader($type = "x") {
+ header("X-Pi-hole: A black hole for Internet advertisements.");
+ if (isset($type) && $type === "js") header("Content-Type: application/javascript");
}
-// Retrieve server URI extension (EG: jpg, exe, php)
-ini_set('pcre.recursion_limit',100);
-$uriExt = pathinfo($uri, PATHINFO_EXTENSION);
+// Determine block page type
+if ($serverName === "pi.hole") {
+ // Redirect to Web Interface
+ exit(header("Location: /admin"));
+} elseif (filter_var($serverName, FILTER_VALIDATE_IP) || in_array($serverName, $authorizedHosts)) {
+ // Set Splash Page output
+ $splashPage = "
+ <html><head>
+ $viewPort
+ <link rel='stylesheet' href='/pihole/blockingpage.css' type='text/css'/>
+ </head><body id='splashpage'><img src='/admin/img/logo.svg'/><br/>Pi-<b>hole</b>: Your black hole for Internet advertisements</body></html>
+ ";
-// Define which URL extensions get rendered as "Website Blocked"
-$webExt = array('asp', 'htm', 'html', 'php', 'rss', 'xml');
+ // Set splash/landing page based off presence of $landPage
+ $renderPage = is_file(getcwd()."/$landPage") ? include $landPage : "$splashPage";
-// Get IPv4 and IPv6 addresses from setupVars.conf (if available)
-$setupVars = parse_ini_file("/etc/pihole/setupVars.conf");
-$ipv4 = isset($setupVars["IPV4_ADDRESS"]) ? explode("/", $setupVars["IPV4_ADDRESS"])[0] : $_SERVER['SERVER_ADDR'];
-$ipv6 = isset($setupVars["IPV6_ADDRESS"]) ? explode("/", $setupVars["IPV6_ADDRESS"])[0] : $_SERVER['SERVER_ADDR'];
-
-$AUTHORIZED_HOSTNAMES = array(
- $ipv4,
- $ipv6,
- str_replace(array("[","]"), array("",""), $_SERVER["SERVER_ADDR"]),
- "pi.hole",
- "localhost");
-// Allow user set virtual hostnames
-$virtual_host = getenv('VIRTUAL_HOST');
-if (!empty($virtual_host))
- array_push($AUTHORIZED_HOSTNAMES, $virtual_host);
-
-// Immediately quit since we didn't block this page (the IP address or pi.hole is explicitly requested)
-if(validIP($serverName) || in_array($serverName,$AUTHORIZED_HOSTNAMES))
-{
- http_response_code(404);
- die();
+ // Unset variables so as to not be included in $landPage
+ unset($serverName, $svPasswd, $svEmail, $authorizedHosts, $validExtTypes, $currentUrlExt, $viewPort);
+
+ // Render splash/landing page when directly browsing via IP or authorised hostname
+ exit($renderPage);
+} elseif ($currentUrlExt === "js") {
+ // Serve Pi-hole Javascript for blocked domains requesting JS
+ exit(setHeader("js").'var x = "Pi-hole: A black hole for Internet advertisements."');
+} elseif (strpos($_SERVER["REQUEST_URI"], "?") !== FALSE && isset($_SERVER["HTTP_REFERER"])) {
+ // Serve blank image upon receiving REQUEST_URI w/ query string & HTTP_REFERRER
+ // e.g: An iframe of a blocked domain
+ exit(setHeader().'<html>
+ <head><script>window.close();</script></head>
+ <body><img src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs="></body>
+ </html>');
+} elseif (!in_array($currentUrlExt, $validExtTypes) || substr_count($_SERVER["REQUEST_URI"], "?")) {
+ // Serve SVG upon receiving non $validExtTypes URL extension or query string
+ // e.g: Not an iframe of a blocked domain, such as when browsing to a file/query directly
+ // QoL addition: Allow the SVG to be clicked on in order to quickly show the full Block Page
+ $blockImg = '<a href="/"><svg xmlns="http://www.w3.org/2000/svg" width="110" height="16"><defs><style>a {text-decoration: none;} circle {stroke: rgba(152,2,2,0.5); fill: none; stroke-width: 2;} rect {fill: rgba(152,2,2,0.5);} text {opacity: 0.3; font: 11px Arial;}</style></defs><circle cx="8" cy="8" r="7"/><rect x="10.3" y="-6" width="2" height="12" transform="rotate(45)"/><text x="19.3" y="12">Blocked by Pi-hole</text></svg></a>';
+ exit(setHeader()."<html>
+ <head>$viewPort</head>
+ <body>$blockImg</body>
+ </html>");
}
-if(in_array($uriExt, $webExt) || empty($uriExt))
-{
- // Requested resource has an extension listed in $webExt
- // or no extension (index access to some folder incl. the root dir)
- $showPage = true;
+/* Start processing Block Page from here */
+
+// Determine placeholder text based off $svPasswd presence
+$wlPlaceHolder = empty($svPasswd) ? "No admin password set" : "Javascript disabled";
+
+// Define admin email address text based off $svEmail presence
+$bpAskAdmin = !empty($svEmail) ? '<a href="mailto:'.$svEmail.'?subject=Site Blocked: '.$serverName.'"></a>' : "<span/>";
+
+// Determine if at least one block list has been generated
+if (empty(glob("/etc/pihole/list.0.*.domains")))
+ die("[ERROR] There are no domain lists generated lists within <code>/etc/pihole/</code>! Please update gravity by running <code>pihole -g</code>, or repair Pi-hole using <code>pihole -r</code>.");
+
+// Set location of adlists file
+if (is_file("/etc/pihole/adlists.list")) {
+ $adLists = "/etc/pihole/adlists.list";
+} elseif (is_file("/etc/pihole/adlists.default")) {
+ $adLists = "/etc/pihole/adlists.default";
+} else {
+ die("[ERROR] File not found: <code>/etc/pihole/adlists.list</code>");
}
-else
-{
- // Something else
- $showPage = false;
+
+// Get all URLs starting with "http" or "www" from adlists and re-index array numerically
+$adlistsUrls = array_values(preg_grep("/(^http)|(^www)/i", file($adLists, FILE_IGNORE_NEW_LINES)));
+
+if (empty($adlistsUrls))
+ die("[ERROR]: There are no adlist URL's found within <code>$adLists</code>");
+
+// Get total number of blocklists (Including Whitelist, Blacklist & Wildcard lists)
+$adlistsCount = count($adlistsUrls) + 3;
+
+// Set query timeout
+ini_set("default_socket_timeout", 3);
+
+// Logic for querying blocklists
+function queryAds($serverName) {
+ // Determine the time it takes while querying adlists
+ $preQueryTime = microtime(true)-$_SERVER["REQUEST_TIME_FLOAT"];
+ $queryAds = file("http://127.0.0.1/admin/scripts/pi-hole/php/queryads.php?domain=$serverName&bp", FILE_IGNORE_NEW_LINES);
+ $queryAds = array_values(array_filter(preg_replace("/data:\s+/", "", $queryAds)));
+ $queryTime = sprintf("%.0f", (microtime(true)-$_SERVER["REQUEST_TIME_FLOAT"]) - $preQueryTime);
+
+ // Exception Handling
+ try {
+ // Define Exceptions
+ if (strpos($queryAds[0], "No exact results") !== FALSE) {
+ // Return "none" into $queryAds array
+ return array("0" => "none");
+ } else if ($queryTime >= ini_get("default_socket_timeout")) {
+ // Connection Timeout
+ throw new Exception ("Connection timeout (".ini_get("default_socket_timeout")."s)");
+ } elseif (!strpos($queryAds[0], ".") !== false) {
+ // Unknown $queryAds output
+ throw new Exception ("Unhandled error message (<code>$queryAds[0]</code>)");
+ }
+ return $queryAds;
+ } catch (Exception $e) {
+ // Return exception as array
+ return array("0" => "error", "1" => $e->getMessage());
+ }
}
-// Handle incoming URI types
-if (!$showPage)
-{
-?>
-<html>
-<head>
-<script>window.close();</script></head>
-<body>
-<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">
-</body>
-</html>
-<?php
- die();
+// Get results of queryads.php exact search
+$queryAds = queryAds($serverName);
+
+// Pass error through to Block Page
+if ($queryAds[0] === "error")
+ die("[ERROR]: Unable to parse results from <i>queryads.php</i>: <code>".$queryAds[1]."</code>");
+
+// Count total number of matching blocklists
+$featuredTotal = count($queryAds);
+
+// Place results into key => value array
+$queryResults = null;
+foreach ($queryAds as $str) {
+ $value = explode(" ", $str);
+ @$queryResults[$value[0]] .= "$value[1]";
}
-// Get Pi-hole version
-$piHoleVersion = exec('cd /etc/.pihole/ && git describe --tags --abbrev=0');
+// Determine if domain has been blacklisted, whitelisted, wildcarded or CNAME blocked
+if (strpos($queryAds[0], "blacklist") !== FALSE) {
+ $notableFlagClass = "blacklist";
+ $adlistsUrls = array("π" => substr($queryAds[0], 2));
+} elseif (strpos($queryAds[0], "whitelist") !== FALSE) {
+ $notableFlagClass = "noblock";
+ $adlistsUrls = array("π" => substr($queryAds[0], 2));
+ $wlInfo = "recentwl";
+} elseif (strpos($queryAds[0], "wildcard") !== FALSE) {
+ $notableFlagClass = "wildcard";
+ $adlistsUrls = array("π" => substr($queryAds[0], 2));
+} elseif ($queryAds[0] === "none") {
+ $featuredTotal = "0";
+ $notableFlagClass = "noblock";
-// Don't show the URI if it is the root directory
-if($uri == "/")
-{
- $uri = "";
+ // QoL addition: Determine appropriate info message if CNAME exists
+ // Suggests to the user that $serverName has a CNAME (alias) that may be blocked
+ $dnsRecord = dns_get_record("$serverName")[0];
+ if (array_key_exists("target", $dnsRecord)) {
+ $wlInfo = $dnsRecord['target'];
+ } else {
+ $wlInfo = "unknown";
+ }
}
+// Set #bpOutput notification
+$wlOutputClass = (isset($wlInfo) && $wlInfo === "recentwl") ? $wlInfo : "hidden";
+$wlOutput = (isset($wlInfo) && $wlInfo !== "recentwl") ? "<a href='http://$wlInfo'>$wlInfo</a>" : "";
+
+// Get Pi-hole Core version
+$phVersion = exec("cd /etc/.pihole/ && git describe --long --tags");
+
+// Print $execTime on development branches
+// Testing for - is marginally faster than "git rev-parse --abbrev-ref HEAD"
+if (explode("-", $phVersion)[1] != "0")
+ $execTime = microtime(true)-$_SERVER["REQUEST_TIME_FLOAT"];
+
+// Please Note: Text is added via CSS to allow an admin to provide a localised
+// language without the need to edit this file
?>
<!DOCTYPE html>
+<!-- Pi-hole: A black hole for Internet advertisements
+* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
+* Network-wide ad blocking via your own hardware.
+*
+* This file is copyright under the latest version of the EUPL. -->
<html>
<head>
- <meta charset='UTF-8'/>
- <title>Website Blocked</title>
- <link rel='stylesheet' href='http://pi.hole/pihole/blockingpage.css'/>
- <link rel='shortcut icon' href='http://pi.hole/admin/img/favicon.png' type='image/png'/>
- <meta name='viewport' content='width=device-width,initial-scale=1.0,maximum-scale=1.0, user-scalable=no'/>
- <meta name='robots' content='noindex,nofollow'/>
+ <meta charset="UTF-8">
+ <?=$viewPort ?>
+ <?=setHeader() ?>
+ <meta name="robots" content="noindex,nofollow"/>
+ <meta http-equiv="x-dns-prefetch-control" content="off">
+ <link rel="shortcut icon" href="<?=$proto ?>://pi.hole/admin/img/favicon.png" type="image/x-icon"/>
+ <link rel="stylesheet" href="<?=$proto ?>://pi.hole/pihole/blockingpage.css" type="text/css"/>
+ <title>● <?=$serverName ?></title>
+ <script src="<?=$proto ?>://pi.hole/admin/scripts/vendor/jquery.min.js"></script>
+ <script>
+ window.onload = function () {
+ <?php
+ // Remove href fallback from "Back to safety" button
+ if ($featuredTotal > 0) echo '$("#bpBack").removeAttr("href");';
+ // Enable whitelisting if $svPasswd is present & JS is available
+ if (!empty($svPasswd) && $featuredTotal > 0) {
+ echo '$("#bpWLPassword, #bpWhitelist").prop("disabled", false);';
+ echo '$("#bpWLPassword").attr("placeholder", "Password");';
+ }
+ ?>
+ }
+ </script>
</head>
-<body id="body">
+<body id="blockpage"><div id="bpWrapper">
<header>
- <h1><a href='/'>Website Blocked</a></h1>
+ <h1 id="bpTitle">
+ <a class="title" href="/"><?php //Website Blocked ?></a>
+ </h1>
+ <div class="spc"></div>
+
+ <input id="bpAboutToggle" type="checkbox"/>
+ <div id="bpAbout">
+ <div class="aboutPH">
+ <div class="aboutImg"/></div>
+ <p>Open Source Ad Blocker
+ <small>Designed for Raspberry Pi</small>
+ </p>
+ </div>
+ <div class="aboutLink">
+ <a class="linkPH" href="https://github.com/pi-hole/pi-hole/wiki/What-is-Pi-hole%3F-A-simple-explanation"><?php //About PH ?></a>
+ <?php if (!empty($svEmail)) echo '<a class="linkEmail" href="mailto:'.$svEmail.'"></a>'; ?>
+ </div>
+ </div>
+
+ <div id="bpAlt">
+ <label class="altBtn" for="bpAboutToggle"><?php //Why am I here? ?></label>
+ </div>
</header>
+
<main>
- <div>Access to the following site has been blocked:<br/>
- <span class='pre msg'><?php echo $serverName.$uri; ?></span></div>
- <div>If you have an ongoing use for this website, please ask the owner of the Pi-hole in your network to have it whitelisted.</div>
- <input id="domain" type="hidden" value="<?php echo $serverName; ?>">
- <input id="quiet" type="hidden" value="yes">
- <button id="btnSearch" class="buttons blocked" type="button" style="visibility: hidden;"></button>
- This page is blocked because it is explicitly contained within the following block list(s):
- <pre id="output" style="width: 100%; height: 100%;" hidden="true"></pre><br/>
- <div class='buttons blocked'>
- <a class='safe33' href='javascript:history.back()'>Go back</a>
- <a class='safe33' id="whitelisting">Whitelist this page</a>
- <a class='safe33' href='javascript:window.close()'>Close window</a>
- </div>
- <div style="width: 98%; text-align: center; padding: 10px;" hidden="true" id="whitelistingform">
- <p>Note that whitelisting domains which are blocked using the wildcard method won't work.</p>
- <p>Password required!</p><br/>
- <form>
- <input name="list" type="hidden" value="white"><br/>
- Domain:<br/>
- <input name="domain" value="<?php echo $serverName ?>" disabled><br/><br/>
- Password:<br/>
- <input type="password" id="pw" name="pw"><br/><br/>
- <button class="buttons33 safe" id="btnAdd" type="button">Whitelist</button>
- </form><br/>
- <pre id="whitelistingoutput" style="width: 100%; height: 100%; padding: 5px;" hidden="true"></pre><br/>
- </div>
+ <div id="bpOutput" class="<?=$wlOutputClass ?>"><?=$wlOutput ?></div>
+ <div id="bpBlock">
+ <p class="blockMsg"><?=$serverName ?></p>
+ </div>
+ <?php if(isset($notableFlagClass)) { ?>
+ <div id="bpFlag">
+ <p class="flagMsg <?=$notableFlagClass ?>"></p>
+ </div>
+ <?php } ?>
+ <div id="bpHelpTxt"><?=$bpAskAdmin ?></div>
+ <div id="bpButtons" class="buttons">
+ <a id="bpBack" onclick="javascript:history.back()" href="about:home"></a>
+ <?php if ($featuredTotal > 0) echo '<label id="bpInfo" for="bpMoreToggle"></label>'; ?>
+ </div>
+ <input id="bpMoreToggle" type="checkbox">
+ <div id="bpMoreInfo">
+ <span id="bpFoundIn"><span><?=$featuredTotal ?></span><?=$adlistsCount ?></span>
+ <pre id='bpQueryOutput'><?php if ($featuredTotal > 0) foreach ($queryResults as $num => $value) { echo "<span>[$num]:</span>$adlistsUrls[$num]\n"; } ?></pre>
+
+ <form id="bpWLButtons" class="buttons">
+ <input id="bpWLDomain" type="text" value="<?=$serverName ?>" disabled/>
+ <input id="bpWLPassword" type="password" placeholder="<?=$wlPlaceHolder ?>" disabled/><button id="bpWhitelist" type="button" disabled></button>
+ </form>
+ </div>
</main>
-<footer>Generated <?php echo date('D g:i A, M d'); ?> by Pi-hole <?php echo $piHoleVersion; ?></footer>
-<script src="http://pi.hole/admin/scripts/vendor/jquery.min.js"></script>
-<script>
-// Create event for when the output is appended to
-(function($) {
- var origAppend = $.fn.append;
-
- $.fn.append = function () {
- return origAppend.apply(this, arguments).trigger("append");
- };
-})(jQuery);
-</script>
-<script src="http://pi.hole/admin/scripts/pi-hole/js/queryads.js"></script>
-<script>
-function inIframe () {
- try {
- return window.self !== window.top;
- } catch (e) {
- return true;
- }
-}
-// Try to detect if page is loaded within iframe
-if(inIframe())
-{
- // Within iframe
- // hide content of page
- $('#body').hide();
- // remove background
- document.body.style.backgroundImage = "none";
-}
-else
-{
- // Query adlists
- $( "#btnSearch" ).click();
-}
+<footer><span><?=date("l g:i A, F dS"); ?>.</span> Pi-hole <?=$phVersion ?> (<?=gethostname()."/".$_SERVER["SERVER_ADDR"]; if (isset($execTime)) printf("/%.2fs", $execTime); ?>)</footer>
+</div>
-$( "#whitelisting" ).on( "click", function(){ $( "#whitelistingform" ).removeAttr( "hidden" ); });
-
-// Remove whitelist functionality if the domain was blocked because of a wildcard
-$( "#output" ).bind("append", function(){
- if($( "#output" ).contents()[0].data.indexOf("Wildcard blocking") !== -1)
- {
- $( "#whitelisting" ).hide();
- $( "#whitelistingform" ).hide();
- }
-});
-
-function add() {
- var domain = $("#domain");
- var pw = $("#pw");
- if(domain.val().length === 0){
- return;
- }
-
- $.ajax({
- url: "/admin/scripts/pi-hole/php/add.php",
- method: "post",
- data: {"domain":domain.val(), "list":"white", "pw":pw.val()},
- success: function(response) {
- $( "#whitelistingoutput" ).removeAttr( "hidden" );
- if(response.indexOf("Pi-hole blocking") !== -1)
- {
- // Reload page after 5 seconds
- setTimeout(function(){window.location.reload(1);}, 5000);
- $( "#whitelistingoutput" ).html("---> Success <---<br/>You may have to flush your DNS cache");
- }
- else
- {
- $( "#whitelistingoutput" ).html("---> "+response+" <---");
- }
-
- },
- error: function(jqXHR, exception) {
- $( "#whitelistingoutput" ).removeAttr( "hidden" );
- $( "#whitelistingoutput" ).html("---> Unknown Error <---");
- }
- });
-}
-// Handle enter button for adding domains
-$(document).keypress(function(e) {
- if(e.which === 13 && $("#pw").is(":focus")) {
- add();
+<script>
+ function add() {
+ $("#bpOutput").removeClass("hidden error exception");
+ $("#bpOutput").addClass("add");
+ var domain = "<?=$serverName ?>";
+ var pw = $("#bpWLPassword");
+ if(domain.length === 0) {
+ return;
}
-});
-
-// Handle buttons
-$("#btnAdd").on("click", function() {
- add();
-});
+ $.ajax({
+ url: "/admin/scripts/pi-hole/php/add.php",
+ method: "post",
+ data: {"domain":domain, "list":"white", "pw":pw.val()},
+ success: function(response) {
+ if(response.indexOf("Pi-hole blocking") !== -1) {
+ setTimeout(function(){window.location.reload(1);}, 10000);
+ $("#bpOutput").removeClass("add");
+ $("#bpOutput").addClass("success");
+ } else {
+ $("#bpOutput").removeClass("add");
+ $("#bpOutput").addClass("error");
+ $("#bpOutput").html(""+response+"");
+ }
+ },
+ error: function(jqXHR, exception) {
+ $("#bpOutput").removeClass("add");
+ $("#bpOutput").addClass("exception");
+ }
+ });
+ }
+ <?php if ($featuredTotal > 0) { ?>
+ $(document).keypress(function(e) {
+ if(e.which === 13 && $("#bpWLPassword").is(":focus")) {
+ add();
+ }
+ });
+ $("#bpWhitelist").on("click", function() {
+ add();
+ });
+ <?php } ?>
</script>
-</body>
-</html>
+</body></html>
diff --git a/advanced/lighttpd.conf.debian b/advanced/lighttpd.conf.debian
index 3b57756e..b5bece72 100644
--- a/advanced/lighttpd.conf.debian
+++ b/advanced/lighttpd.conf.debian
@@ -2,18 +2,16 @@
# (c) 2017 Pi-hole, LLC (https://pi-hole.net)
# Network-wide ad blocking via your own hardware.
#
-# lighttpd config for Pi-hole
+# Lighttpd config for Pi-hole
#
# This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license.
-
-
###############################################################################
# FILE AUTOMATICALLY OVERWRITTEN BY PI-HOLE INSTALL/UPDATE PROCEDURE. #
# ANY CHANGES MADE TO THIS FILE AFTER INSTALL WILL BE LOST ON THE NEXT UPDATE #
# #
-# CHANGES SHOULD BE MADE IN A SEPERATE CONFIG FILE: #
+# CHANGES SHOULD BE MADE IN A SEPARATE CONFIG FILE: #
# /etc/lighttpd/external.conf #
###############################################################################
@@ -39,9 +37,8 @@ server.port = 80
accesslog.filename = "/var/log/lighttpd/access.log"
accesslog.format = "%{%s}t|%V|%r|%s|%b"
-
index-file.names = ( "index.php", "index.html", "index.lighttpd.html" )
-url.access-deny = ( "~", ".inc" )
+url.access-deny = ( "~", ".inc", ".md", ".yml", ".ini" )
static-file.exclude-extensions = ( ".php", ".pl", ".fcgi" )
compress.cache-dir = "/var/cache/lighttpd/compress/"
@@ -50,31 +47,28 @@ compress.filetype = ( "application/javascript", "text/css", "text/html
# default listening port for IPv6 falls back to the IPv4 port
include_shell "/usr/share/lighttpd/use-ipv6.pl " + server.port
include_shell "/usr/share/lighttpd/create-mime.assign.pl"
-include_shell "/usr/share/lighttpd/include-conf-enabled.pl"
+
+# Prevent Lighttpd from enabling Let's Encrypt SSL for every blocked domain
+#include_shell "/usr/share/lighttpd/include-conf-enabled.pl"
+include_shell "find /etc/lighttpd/conf-enabled -name '*.conf' -a ! -name 'letsencrypt.conf' -printf 'include \"%p\"\n' 2>/dev/null"
# If the URL starts with /admin, it is the Web interface
$HTTP["url"] =~ "^/admin/" {
- # Create a response header for debugging using curl -I
+ # Create a response header for debugging using curl -I
setenv.add-response-header = (
"X-Pi-hole" => "The Pi-hole Web interface is working!",
"X-Frame-Options" => "DENY"
)
-}
-
-# Rewite js requests, must be out of $HTTP block due to bug #2526
-url.rewrite = ( "^(?!/admin/).*\.js$" => "pihole/index.js" )
-# If the URL does not start with /admin, then it is a query for an ad domain
-$HTTP["url"] =~ "^(?!/admin)/.*" {
- # Create a response header for debugging using curl -I
- setenv.add-response-header = ( "X-Pi-hole" => "A black hole for Internet advertisements." )
+ $HTTP["url"] =~ ".ttf$" {
+ # Allow Block Page access to local fonts
+ setenv.add-response-header = ( "Access-Control-Allow-Origin" => "*" )
+ }
}
-# Entering just "pi.hole" into a browser redirects to "pi.hole/admin/"
-$HTTP["host"] == "pi.hole" {
- $HTTP["url"] == "/" {
- url.redirect = ( "" => "/admin/" )
- }
+# Block . files from being served, such as .git, .github, .gitignore
+$HTTP["url"] =~ "^/admin/\.(.*)" {
+ url.access-deny = ("")
}
# Add user chosen options held in external file
diff --git a/advanced/lighttpd.conf.fedora b/advanced/lighttpd.conf.fedora
index fd856fbb..43d94d84 100644
--- a/advanced/lighttpd.conf.fedora
+++ b/advanced/lighttpd.conf.fedora
@@ -7,13 +7,11 @@
# This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license.
-
-
###############################################################################
# FILE AUTOMATICALLY OVERWRITTEN BY PI-HOLE INSTALL/UPDATE PROCEDURE. #
# ANY CHANGES MADE TO THIS FILE AFTER INSTALL WILL BE LOST ON THE NEXT UPDATE #
# #
-# CHANGES SHOULD BE MADE IN A SEPERATE CONFIG FILE: #
+# CHANGES SHOULD BE MADE IN A SEPARATE CONFIG FILE: #
# /etc/lighttpd/external.conf #
###############################################################################
@@ -42,7 +40,7 @@ accesslog.format = "%{%s}t|%V|%r|%s|%b"
index-file.names = ( "index.php", "index.html", "index.lighttpd.html" )
-url.access-deny = ( "~", ".inc" )
+url.access-deny = ( "~", ".inc", ".md", ".yml", ".ini" )
static-file.exclude-extensions = ( ".php", ".pl", ".fcgi" )
compress.cache-dir = "/var/cache/lighttpd/compress/"
@@ -74,24 +72,21 @@ fastcgi.server = ( ".php" =>
# If the URL starts with /admin, it is the Web interface
$HTTP["url"] =~ "^/admin/" {
- # Create a response header for debugging using curl -I
- setenv.add-response-header = ( "X-Pi-hole" => "The Pi-hole Web interface is working!" )
-}
-
-# Rewite js requests, must be out of $HTTP block due to bug #2526
-url.rewrite = ( "^(?!/admin/).*\.js$" => "pihole/index.js" )
-
-# If the URL does not start with /admin, then it is a query for an ad domain
-$HTTP["url"] =~ "^(?!/admin)/.*" {
- # Create a response header for debugging using curl -I
- setenv.add-response-header = ( "X-Pi-hole" => "A black hole for Internet advertisements." )
+ # Create a response header for debugging using curl -I
+ setenv.add-response-header = (
+ "X-Pi-hole" => "The Pi-hole Web interface is working!",
+ "X-Frame-Options" => "DENY"
+ )
+
+ $HTTP["url"] =~ ".ttf$" {
+ # Allow Block Page access to local fonts
+ setenv.add-response-header = ( "Access-Control-Allow-Origin" => "*" )
+ }
}
-# Entering just "pi.hole" into a browser redirects to "pi.hole/admin/"
-$HTTP["host"] == "pi.hole" {
- $HTTP["url"] == "/" {
- url.redirect = ( "" => "/admin/" )
- }
+# Block . files from being served, such as .git, .github, .gitignore
+$HTTP["url"] =~ "^/admin/\.(.*)" {
+ url.access-deny = ("")
}
# Add user chosen options held in external file
diff --git a/advanced/pihole.cron b/advanced/pihole.cron
index f1beb08c..2273358b 100644
--- a/advanced/pihole.cron
+++ b/advanced/pihole.cron
@@ -14,8 +14,8 @@
# is updated or re-installed. Please make any changes to the appropriate crontab
# or other cron file snippets.
-# Pi-hole: Update the ad sources once a week on Sunday at 01:59
-# Download any updates from the adlists
+# Pi-hole: Update the ad sources once a week on Sunday at a random time in the
+# early morning. Download any updates from the adlists
59 1 * * 7 root PATH="$PATH:/usr/local/bin/" pihole updateGravity
# Pi-hole: Update Pi-hole! Uncomment to enable auto update
@@ -28,3 +28,6 @@
00 00 * * * root PATH="$PATH:/usr/local/bin/" pihole flush once quiet
@reboot root /usr/sbin/logrotate /etc/pihole/logrotate
+
+# Pi-hole: Grab remote version and branch every 10 minutes
+*/10 * * * * root PATH="$PATH:/usr/local/bin/" pihole updatechecker
diff --git a/automated install/basic-install.sh b/automated install/basic-install.sh
index 2cf2c61d..79754872 100755
--- a/automated install/basic-install.sh
+++ b/automated install/basic-install.sh
@@ -1,43 +1,65 @@
#!/usr/bin/env bash
+# shellcheck disable=SC1090
+
# Pi-hole: A black hole for Internet advertisements
# (c) 2017 Pi-hole, LLC (https://pi-hole.net)
# Network-wide ad blocking via your own hardware.
#
-# Installs Pi-hole
+# Installs and Updates Pi-hole
#
# This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license.
-
-
# pi-hole.net/donate
#
-# Install with this command (from your Pi):
+# Install with this command (from your Linux machine):
#
# curl -L install.pi-hole.net | bash
+# -e option instructs bash to immediately exit if any command [1] has a non-zero exit status
+# We do not want users to end up with a partially working install, so we exit the script
+# instead of continuing the installation with something broken
set -e
+
######## VARIABLES #########
+# For better maintainability, we store as much information that can change in variables
+# This allows us to make a change in one place that can propogate to all instances of the variable
+# These variables should all be GLOBAL variables, written in CAPS
+# Local variables will be in lowercase and will exist only within functions
+# It's still a work in progress, so you may see some variance in this guideline until it is complete
+
+# We write to a temporary file before moving the log to the pihole folder
tmpLog=/tmp/pihole-install.log
instalLogLoc=/etc/pihole/install.log
+# This is an important file as it contains information specific to the machine it's being installed on
setupVars=/etc/pihole/setupVars.conf
+# Pi-hole uses lighttpd as a Web server, and this is the config file for it
+# shellcheck disable=SC2034
lighttpdConfig=/etc/lighttpd/lighttpd.conf
+# This is a file used for the colorized output
+coltable=/opt/pihole/COL_TABLE
+# We store several other folders and
webInterfaceGitUrl="https://github.com/pi-hole/AdminLTE.git"
webInterfaceDir="/var/www/html/admin"
piholeGitUrl="https://github.com/pi-hole/pi-hole.git"
PI_HOLE_LOCAL_REPO="/etc/.pihole"
+# These are the names of piholes files, stored in an array
PI_HOLE_FILES=(chronometer list piholeDebug piholeLogFlush setupLCD update version gravity uninstall webpage)
+# This folder is where the Pi-hole scripts will be installed
PI_HOLE_INSTALL_DIR="/opt/pihole"
useUpdateVars=false
+# Pi-hole needs an IP address; to begin, these variables are empty since we don't know what the IP is until
+# this script can run
IPV4_ADDRESS=""
IPV6_ADDRESS=""
+# By default, query logging is enabled and the dashboard is set to be installed
QUERY_LOGGING=true
INSTALL_WEB=true
-# Find the rows and columns will default to 80x24 is it can not be detected
+# Find the rows and columns will default to 80x24 if it can not be detected
screen_size=$(stty size 2>/dev/null || echo 24 80)
rows=$(echo "${screen_size}" | awk '{print $1}')
columns=$(echo "${screen_size}" | awk '{print $2}')
@@ -50,19 +72,41 @@ r=$(( r < 20 ? 20 : r ))
c=$(( c < 70 ? 70 : c ))
######## Undocumented Flags. Shhh ########
+# These are undocumented flags; some of which we can use when repairing an installation
+# The runUnattended flag is one example of this
skipSpaceCheck=false
reconfigure=false
runUnattended=false
+# If the color table file exists,
+if [[ -f "${coltable}" ]]; then
+ # source it
+ source ${coltable}
+# Othwerise,
+else
+ # Set these values so the installer can still run in color
+ COL_NC='\e[0m' # No Color
+ COL_LIGHT_GREEN='\e[1;32m'
+ COL_LIGHT_RED='\e[1;31m'
+ TICK="[${COL_LIGHT_GREEN}✓${COL_NC}]"
+ CROSS="[${COL_LIGHT_RED}✗${COL_NC}]"
+ INFO="[i]"
+ # shellcheck disable=SC2034
+ DONE="${COL_LIGHT_GREEN} done!${COL_NC}"
+ OVER="\\r\\033[K"
+fi
+
+# A simple function that just echoes out our logo in ASCII format
+# This lets users know that it is a Pi-hole, LLC product
show_ascii_berry() {
- echo "
- .;;,.
+ echo -e "
+ ${COL_LIGHT_GREEN}.;;,.
.ccccc:,.
:cccclll:. ..,,
:ccccclll. ;ooodc
'ccll:;ll .oooodc
.;cll.;;looo:.
- .. ','.
+ ${COL_LIGHT_RED}.. ','.
.',,,,,,'.
.',,,,,,,,,,.
.',,,,,,,,,,,,....
@@ -75,70 +119,97 @@ show_ascii_berry() {
....',,,,,,,,,,,,.
.',,,,,,,,,'.
.',,,,,,'.
- ..'''.
+ ..'''.${COL_NC}
"
}
-
# Compatibility
distro_check() {
+# If apt-get is installed, then we know it's part of the Debian family
if command -v apt-get &> /dev/null; then
- #Debian Family
- #############################################
+ # Set some global variables here
+ # We don't set them earlier since the family might be Red Hat, so these values would be different
PKG_MANAGER="apt-get"
+ # A variable to store the command used to update the package cache
UPDATE_PKG_CACHE="${PKG_MANAGER} update"
+ # An array for something...
PKG_INSTALL=(${PKG_MANAGER} --yes --no-install-recommends install)
# grep -c will return 1 retVal on 0 matches, block this throwing the set -e with an OR TRUE
PKG_COUNT="${PKG_MANAGER} -s -o Debug::NoLocking=true upgrade | grep -c ^Inst || true"
- # #########################################
- # fixes for dependency differences
- # Debian 7 doesn't have iproute2 use iproute
+ # Some distros vary slightly so these fixes for dependencies may apply
+ # Debian 7 doesn't have iproute2 so if the dry run install is successful,
if ${PKG_MANAGER} install --dry-run iproute2 > /dev/null 2>&1; then
+ # we can install it
iproute_pkg="iproute2"
+ # Otherwise,
else
+ # use iproute
iproute_pkg="iproute"
fi
- # Prefer the php metapackage if it's there, fall back on the php5 packages
+ # We prefer the php metapackage if it's there
if ${PKG_MANAGER} install --dry-run php > /dev/null 2>&1; then
phpVer="php"
+ # If not,
else
+ # fall back on the php5 packages
phpVer="php5"
fi
- # #########################################
+ # We also need the correct version for `php-sqlite` (which differs across distros)
+ if ${PKG_MANAGER} install --dry-run ${phpVer}-sqlite3 > /dev/null 2>&1; then
+ phpSqlite="sqlite3"
+ else
+ phpSqlite="sqlite"
+ fi
+ # Since our install script is so large, we need several other programs to successfuly get a machine provisioned
+ # These programs are stored in an array so they can be looped through later
INSTALLER_DEPS=(apt-utils dialog debconf dhcpcd5 git ${iproute_pkg} whiptail)
- PIHOLE_DEPS=(bc cron curl dnsmasq dnsutils iputils-ping lsof netcat sudo unzip wget)
- PIHOLE_WEB_DEPS=(lighttpd ${phpVer}-common ${phpVer}-cgi)
+ # Pi-hole itself has several dependencies that also need to be installed
+ PIHOLE_DEPS=(bc cron curl dnsmasq dnsutils iputils-ping lsof netcat sudo unzip wget idn2)
+ # The Web dashboard has some that also need to be installed
+ # It's useful to separate the two since our repos are also setup as "Core" code and "Web" code
+ PIHOLE_WEB_DEPS=(lighttpd ${phpVer}-common ${phpVer}-cgi ${phpVer}-${phpSqlite})
+ # The Web server user,
LIGHTTPD_USER="www-data"
+ # group,
LIGHTTPD_GROUP="www-data"
+ # and config file
LIGHTTPD_CFG="lighttpd.conf.debian"
+ # The DNS server user
DNSMASQ_USER="dnsmasq"
+ # A function to check...
test_dpkg_lock() {
- i=0
- while fuser /var/lib/dpkg/lock >/dev/null 2>&1 ; do
- sleep 0.5
- ((i=i+1))
- done
- # Always return success, since we only return if there is no
- # lock (anymore)
- return 0
- }
+ # An iterator used for counting loop iterations
+ i=0
+ # fuser is a program to show which processes use the named files, sockets, or filesystems
+ # So while the command is true
+ while fuser /var/lib/dpkg/lock >/dev/null 2>&1 ; do
+ # Wait half a second
+ sleep 0.5
+ # and increase the iterator
+ ((i=i+1))
+ done
+ # Always return success, since we only return if there is no
+ # lock (anymore)
+ return 0
+ }
+# If apt-get is not found, check for rpm to see if it's a Red Hat family OS
elif command -v rpm &> /dev/null; then
- # Fedora Family
+ # Then check if dnf or yum is the package manager
if command -v dnf &> /dev/null; then
PKG_MANAGER="dnf"
else
PKG_MANAGER="yum"
fi
-# Fedora and family update cache on every PKG_INSTALL call, no need for a separate update.
+ # Fedora and family update cache on every PKG_INSTALL call, no need for a separate update.
UPDATE_PKG_CACHE=":"
PKG_INSTALL=(${PKG_MANAGER} install -y)
PKG_COUNT="${PKG_MANAGER} check-update | egrep '(.i686|.x86|.noarch|.arm|.src)' | wc -l"
INSTALLER_DEPS=(dialog git iproute net-tools newt procps-ng)
- PIHOLE_DEPS=(bc bind-utils cronie curl dnsmasq findutils nmap-ncat sudo unzip wget)
- PIHOLE_WEB_DEPS=(lighttpd lighttpd-fastcgi php php-common php-cli)
+ PIHOLE_DEPS=(bc bind-utils cronie curl dnsmasq findutils nmap-ncat sudo unzip wget libidn2 psmisc)
+ PIHOLE_WEB_DEPS=(lighttpd lighttpd-fastcgi php php-common php-cli php-pdo)
if ! grep -q 'Fedora' /etc/redhat-release; then
INSTALLER_DEPS=("${INSTALLER_DEPS[@]}" "epel-release");
fi
@@ -147,149 +218,233 @@ elif command -v rpm &> /dev/null; then
LIGHTTPD_CFG="lighttpd.conf.fedora"
DNSMASQ_USER="nobody"
+# If neither apt-get or rmp/dnf are found
else
- echo "OS distribution not supported"
+ # it's not an OS we can support,
+ echo -e " ${CROSS} OS distribution not supported"
+ # so exit the installer
exit
fi
}
+# A function for checking if a folder is a git repository
is_repo() {
- # Use git to check if directory is currently under VCS, return the value 128
- # if directory is not a repo. Return 1 if directory does not exist.
+ # Use a named, local variable instead of the vague $1, which is the first arguement passed to this function
+ # These local variables should always be lowercase
local directory="${1}"
+ # A local variable for the current directory
local curdir
+ # A variable to store the return code
local rc
-
+ # Assign the current directory variable by using pwd
curdir="${PWD}"
+ # If the first argument passed to this function is a directory,
if [[ -d "${directory}" ]]; then
- # git -C is not used here to support git versions older than 1.8.4
+ # move into the directory
cd "${directory}"
+ # Use git to check if the folder is a repo
+ # git -C is not used here to support git versions older than 1.8.4
git status --short &> /dev/null || rc=$?
+ # If the command was not successful,
else
- # non-zero return code if directory does not exist
+ # Set a non-zero return code if directory does not exist
rc=1
fi
+ # Move back into the directory the user started in
cd "${curdir}"
+ # Return the code; if one is not set, return 0
return "${rc:-0}"
}
+# A function to clone a repo
make_repo() {
+ # Set named variables for better readability
local directory="${1}"
local remoteRepo="${2}"
-
- echo -n "::: Cloning ${remoteRepo} into ${directory}..."
- # Clean out the directory if it exists for git to clone into
+ # The message to display when this function is running
+ str="Clone ${remoteRepo} into ${directory}"
+ # Display the message and use the color table to preface the message with an "info" indicator
+ echo -ne " ${INFO} ${str}..."
+ # If the directory exists,
if [[ -d "${directory}" ]]; then
+ # delete everything in it so git can clone into it
rm -rf "${directory}"
fi
+ # Clone the repo and return the return code from this command
git clone -q --depth 1 "${remoteRepo}" "${directory}" &> /dev/null || return $?
- echo " done!"
+ # Show a colored message showing it's status
+ echo -e "${OVER} ${TICK} ${str}"
+ # Always return 0? Not sure this is correct
return 0
}
+# We need to make sure the repos are up-to-date so we can effectively install Clean out the directory if it exists for git to clone into
update_repo() {
+ # Use named, local variables
+ # As you can see, these are the same variable names used in the last function,
+ # but since they are local, their scope does not go beyond this function
+ # This helps prevent the wrong value from being assigned if you were to set the variable as a GLOBAL one
local directory="${1}"
local curdir
+ # A variable to store the message we want to display;
+ # Again, it's useful to store these in variables in case we need to reuse or change the message;
+ # we only need to make one change here
+ local str="Update repo in ${1}"
+
+ # Make sure we know what directory we are in so we can move back into it
curdir="${PWD}"
+ # Move into the directory that was passed as an argument
cd "${directory}" &> /dev/null || return 1
- # Pull the latest commits
- echo -n "::: Updating repo in ${1}..."
+ # Let the user know what's happening
+ echo -ne " ${INFO} ${str}..."
+ # Stash any local commits as they conflict with our working code
git stash --all --quiet &> /dev/null || true # Okay for stash failure
- git clean --force -d || true # Okay for already clean directory
+ git clean --quiet --force -d || true # Okay for already clean directory
+ # Pull the latest commits
git pull --quiet &> /dev/null || return $?
- echo " done!"
+ # Show a completion message
+ echo -e "${OVER} ${TICK} ${str}"
+ # Move back into the oiginal directory
cd "${curdir}" &> /dev/null || return 1
return 0
}
+# A function that combines the functions previously made
getGitFiles() {
- # Setup git repos for directory and repository passed
- # as arguments 1 and 2
+ # Setup named variables for the git repos
+ # We need the directory
local directory="${1}"
+ # as well as the repo URL
local remoteRepo="${2}"
- echo ":::"
- echo "::: Checking for existing repository..."
+ # A local varible containing the message to be displayed
+ local str="Check for existing repository in ${1}"
+ # Show the message
+ echo -ne " ${INFO} ${str}..."
+ # Check if the directory is a repository
if is_repo "${directory}"; then
- update_repo "${directory}" || { echo "*** Error: Could not update local repository. Contact support."; exit 1; }
- echo " done!"
+ # Show that we're checking it
+ echo -e "${OVER} ${TICK} ${str}"
+ # Update the repo, returning an error message on failure
+ update_repo "${directory}" || { echo -e "\\n ${COL_LIGHT_RED}Error: Could not update local repository. Contact support.${COL_NC}"; exit 1; }
+ # If it's not a .git repo,
else
- make_repo "${directory}" "${remoteRepo}" || { echo "Unable to clone repository, please contact support"; exit 1; }
- echo " done!"
+ # Show an error
+ echo -e "${OVER} ${CROSS} ${str}"
+ # Attempt to make the repository, showing an error on falure
+ make_repo "${directory}" "${remoteRepo}" || { echo -e "\\n ${COL_LIGHT_RED}Error: Could not update local repository. Contact support.${COL_NC}"; exit 1; }
fi
+ # echo a blank line
+ echo ""
+ # and return success?
return 0
}
+# Reset a repo to get rid of any local changed
resetRepo() {
+ # Use named varibles for arguments
local directory="${1}"
-
+ # Move into the directory
cd "${directory}" &> /dev/null || return 1
- echo -n "::: Resetting repo in ${1}..."
+ # Store the message in a varible
+ str="Resetting repository within ${1}..."
+ # Show the message
+ echo -ne " ${INFO} ${str}"
+ # Use git to remove the local changes
git reset --hard &> /dev/null || return $?
- echo " done!"
+ # And show the status
+ echo -e "${OVER} ${TICK} ${str}"
+ # Returning success anyway?
return 0
}
+# We need to know the IPv4 information so we can effectively setup the DNS server
+# Without this information, we won't know where to Pi-hole will be found
find_IPv4_information() {
+ # Named, local variables
local route
- # Find IP used to route to outside world
+ # Find IP used to route to outside world by checking the the route to Google's public DNS server
route=$(ip route get 8.8.8.8)
+ # Use awk to strip out just the interface device as it is used in future commands
IPv4dev=$(awk '{for (i=1; i<=NF; i++) if ($i~/dev/) print $(i+1)}' <<< "${route}")
+ # Get just the IP address
IPv4bare=$(awk '{print $7}' <<< "${route}")
+ # Append the CIDR notation to the IP address
IPV4_ADDRESS=$(ip -o -f inet addr show | grep "${IPv4bare}" | awk '{print $4}' | awk 'END {print}')
+ # Get the default gateway (the way to reach the Internet)
IPv4gw=$(awk '{print $3}' <<< "${route}")
}
+# Get available interfaces that are UP
get_available_interfaces() {
- # Get available UP interfaces.
+ # There may be more than one so it's all stored in a variable
availableInterfaces=$(ip --oneline link show up | grep -v "lo" | awk '{print $2}' | cut -d':' -f1 | cut -d'@' -f1)
}
+# A function for displaying the dialogs the user sees when first running the installer
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 using an approriately sized window via the calculation conducted earlier in the script
+ 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}
+ # Request that users donate if they enjoy the software since we all work on it in our free time
+ 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.
+ 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}
}
+# We need to make sure there is enough space before installing, so there is a function to check this
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 str="Disk space check"
+ # Reqired space in KB
local required_free_kilobytes=51200
- local existing_free_kilobytes=$(df -Pk | grep -m1 '\/$' | awk '{print $4}')
+ # Calculate existing free space on this machine
+ local existing_free_kilobytes
+ existing_free_kilobytes=$(df -Pk | grep -m1 '\/$' | awk '{print $4}')
- # - Unknown free disk space , not a integer
+ # If the existing space is not an 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"
+ # show an error that we can't determine the free space
+ echo -e " ${CROSS} ${str}
+ Unknown free disk space!
+ We were unable to determine available free disk space on this system.
+ You may override this check, however, it is not recommended
+ The option '${COL_LIGHT_RED}--i_do_not_follow_recommendations${COL_NC}' can override this
+ e.g: curl -L https://install.pi-hole.net | bash /dev/stdin ${COL_LIGHT_RED}<option>${COL_NC}"
+ # exit with an error code
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..."
+ # If there is insufficient free disk space,
+ elif [[ "${existing_free_kilobytes}" -lt "${required_free_kilobytes}" ]]; then
+ # show an error message
+ echo -e " ${CROSS} ${str}
+ Your system disk appears to only have ${existing_free_kilobytes} KB free
+ It is recommended to have a minimum of ${required_free_kilobytes} KB to run the Pi-hole"
+ # if the vcgencmd command exists,
+ if command -v vcgencmd &> /dev/null; then
+ # it's probably a Raspbian install, so show a message about expanding the filesystem
+ echo " If this is a new install you may need to expand your disk
+ Run 'sudo raspi-config', and choose the 'expand file system' option
+ After rebooting, run this installation again
+ e.g: curl -L https://install.pi-hole.net | bash"
+ fi
+ # Show there is not enough free space
+ echo -e "\\n ${COL_LIGHT_RED}Insufficient free space, exiting...${COL_NC}"
+ # and exit with an error
exit 1
+ # Otherwise,
+ else
+ # Show that we're running a disk space check
+ echo -e " ${TICK} ${str}"
fi
}
-
+# A function that let's the user pick an interface to use with Pi-hole
chooseInterface() {
# Turn the available interfaces into an array so it can be used with a whiptail dialog
local interfacesArray=()
@@ -305,101 +460,162 @@ chooseInterface() {
# Find out how many interfaces are available to choose from
interfaceCount=$(echo "${availableInterfaces}" | wc -l)
- if [[ ${interfaceCount} -eq 1 ]]; then
+ # If there is one interface,
+ if [[ "${interfaceCount}" -eq 1 ]]; then
+ # Set it as the interface to use since there is no other option
PIHOLE_INTERFACE="${availableInterfaces}"
+ # Otherwise,
else
+ # While reading through the available interfaces
while read -r line; do
+ # use a variable to set the option as OFF to begin with
mode="OFF"
- if [[ ${firstLoop} -eq 1 ]]; then
+ # If it's the first loop,
+ if [[ "${firstLoop}" -eq 1 ]]; then
+ # set this as the interface to use (ON)
firstLoop=0
mode="ON"
fi
+ # Put all these interfaces into an array
interfacesArray+=("${line}" "available" "${mode}")
+ # Feed the available interfaces into this while loop
done <<< "${availableInterfaces}"
-
+ # The whiptail command that will be run, stored in a variable
chooseInterfaceCmd=(whiptail --separate-output --radiolist "Choose An Interface (press space to select)" ${r} ${c} ${interfaceCount})
+ # Now run the command using the interfaces saved into the array
chooseInterfaceOptions=$("${chooseInterfaceCmd[@]}" "${interfacesArray[@]}" 2>&1 >/dev/tty) || \
- { echo "::: Cancel selected. Exiting"; exit 1; }
+ # If the user chooses Canel, exit
+ { echo -e " ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; }
+ # For each interface
for desiredInterface in ${chooseInterfaceOptions}; do
+ # Set the one the user selected as the interface to use
PIHOLE_INTERFACE=${desiredInterface}
- echo "::: Using interface: $PIHOLE_INTERFACE"
+ # and show this information to the user
+ echo -e " ${INFO} Using interface: $PIHOLE_INTERFACE"
done
fi
}
+# This lets us prefer ULA addresses over GUA
+# This caused problems for some users when their ISP changed their IPv6 addresses
# See https://github.com/pi-hole/pi-hole/issues/1473#issuecomment-301745953
testIPv6() {
+ # first will contain fda2 (ULA)
first="$(cut -f1 -d":" <<< "$1")"
- value1=$(((0x$first)/256))
- value2=$(((0x$first)%256))
- ((($value1&254)==252)) && echo "ULA" || true
- ((($value1&112)==32)) && echo "GUA" || true
- ((($value1==254) && (($value2&192)==128))) && echo "Link-local" || true
+ # value1 will contain 253 which is the decimal value corresponding to 0xfd
+ value1=$(( (0x$first)/256 ))
+ # will contain 162 which is the decimal value corresponding to 0xa2
+ value2=$(( (0x$first)%256 ))
+ # the ULA test is testing for fc00::/7 according to RFC 4193
+ if (( (value1&254)==252 )); then
+ echo "ULA"
+ fi
+ # the GUA test is testing for 2000::/3 according to RFC 4291
+ if (( (value1&112)==32 )); then
+ echo "GUA"
+ fi
+ # the LL test is testing for fe80::/10 according to RFC 4193
+ if (( (value1)==254 )) && (( (value2&192)==128 )); then
+ echo "Link-local"
+ fi
}
+# A dialog for showing the user about IPv6 blocking
useIPv6dialog() {
# Determine the IPv6 address used for blocking
IPV6_ADDRESSES=($(ip -6 address | grep 'scope global' | awk '{print $2}'))
- # Determine type of found IPv6 addresses
+ # For each address in the array above, determine the type of IPv6 address it is
for i in "${IPV6_ADDRESSES[@]}"; do
+ # Check if it's ULA, GUA, or LL by using the function created earlier
result=$(testIPv6 "$i")
+ # If it's a ULA address, use it and store it as a global variable
[[ "${result}" == "ULA" ]] && ULA_ADDRESS="${i%/*}"
+ # If it's a GUA address, we can still use it si store it as a global variable
[[ "${result}" == "GUA" ]] && GUA_ADDRESS="${i%/*}"
done
# Determine which address to be used: Prefer ULA over GUA or don't use any if none found
+ # If the ULA_ADDRESS contains a value,
if [[ ! -z "${ULA_ADDRESS}" ]]; then
+ # set the IPv6 address to the ULA address
IPV6_ADDRESS="${ULA_ADDRESS}"
- echo "::: Found IPv6 ULA address, using it for blocking IPv6 ads"
+ # Show this info to the user
+ echo -e " ${INFO} Found IPv6 ULA address, using it for blocking IPv6 ads"
+ # Otherwise, if the GUA_ADDRESS has a value,
elif [[ ! -z "${GUA_ADDRESS}" ]]; then
- echo "::: Found IPv6 GUA address, using it for blocking IPv6 ads"
+ # Let the user know
+ echo -e " ${INFO} Found IPv6 GUA address, using it for blocking IPv6 ads"
+ # And assign it to the global variable
IPV6_ADDRESS="${GUA_ADDRESS}"
+ # If none of those work,
else
- echo "::: Found neither IPv6 ULA nor GUA address, blocking IPv6 ads will not be enabled"
+ # explain that IPv6 blocking will not be used
+ echo -e " ${INFO} Unable to find IPv6 ULA/GUA address, IPv6 adblocking will not be enabled"
+ # So set the variable to be empty
IPV6_ADDRESS=""
fi
+ # If the IPV6_ADDRESS contains a value
if [[ ! -z "${IPV6_ADDRESS}" ]]; then
+ # Display that IPv6 is supported and will be used
whiptail --msgbox --backtitle "IPv6..." --title "IPv6 Supported" "$IPV6_ADDRESS will be used to block ads." ${r} ${c}
fi
}
+# A function to check if we should use IPv4 and/or IPv6 for blocking ads
use4andor6() {
+ # Named local variables
local useIPv4
local useIPv6
- # Let use select IPv4 and/or IPv6
+ # Let use select IPv4 and/or IPv6 via a checklist
cmd=(whiptail --separate-output --checklist "Select Protocols (press space to select)" ${r} ${c} 2)
+ # In an array, show the options available:
+ # IPv4 (on by default)
options=(IPv4 "Block ads over IPv4" on
+ # or IPv6 (on by default if available)
IPv6 "Block ads over IPv6" on)
- choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) || { echo "::: Cancel selected. Exiting"; exit 1; }
+ # In a variable, show the choices available; exit if Cancel is selected
+ choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) || { echo -e " ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; }
+ # For each choice available,
for choice in ${choices}
do
+ # Set the values to true
case ${choice} in
IPv4 ) useIPv4=true;;
IPv6 ) useIPv6=true;;
esac
done
- if [[ ${useIPv4} ]]; then
+ # If IPv4 is to be used,
+ if [[ "${useIPv4}" ]]; then
+ # Run our function to get the information we need
find_IPv4_information
getStaticIPv4Settings
setStaticIPv4
fi
- if [[ ${useIPv6} ]]; then
+ # If IPv6 is to be used,
+ if [[ "${useIPv6}" ]]; then
+ # Run our function to get this information
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"
+ # Echo the information to the user
+ echo -e " ${INFO} IPv4 address: ${IPV4_ADDRESS}"
+ echo -e " ${INFO} IPv6 address: ${IPV6_ADDRESS}"
+ # If neither protocol is selected,
+ if [[ ! "${useIPv4}" ]] && [[ ! "${useIPv6}" ]]; then
+ # Show an error in red
+ echo -e " ${COL_LIGHT_RED}Error: Neither IPv4 or IPv6 selected${COL_NC}"
+ # and exit with an error
exit 1
fi
}
+#
getStaticIPv4Settings() {
+ # Local, named variables
local ipSettingsCorrect
# Ask if the user wants to use DHCP settings as their static IP
+ # This is useful for users that are using DHCP reservations; then we can just use the information gathered via our functions
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
@@ -407,29 +623,29 @@ getStaticIPv4Settings() {
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
+ # 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
+ 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) || \
# Cancelling IPv4 settings window
- { ipSettingsCorrect=False; echo "::: Cancel selected. Exiting..."; exit 1; }
- echo "::: Your static IPv4 address: ${IPV4_ADDRESS}"
+ { ipSettingsCorrect=False; echo -e " ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; }
+ echo -e " ${INFO} 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) || \
# Cancelling gateway settings window
- { ipSettingsCorrect=False; echo "::: Cancel selected. Exiting..."; exit 1; }
- echo "::: Your static IPv4 gateway: ${IPv4gw}"
+ { ipSettingsCorrect=False; echo -e " ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; }
+ echo -e " ${INFO} 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
+ IP address: ${IPV4_ADDRESS}
+ Gateway: ${IPv4gw}" ${r} ${c}; then
# After that's done, the loop ends and we move on
ipSettingsCorrect=True
else
@@ -441,8 +657,9 @@ It is also possible to use a DHCP reservation, but if you are going to do that,
fi
}
+# dhcpcd is very annoying,
setDHCPCD() {
- # Append these lines to dhcpcd.conf to enable a static IP
+ # but we can append these lines to dhcpcd.conf to enable a static IP
echo "interface ${PIHOLE_INTERFACE}
static ip_address=${IPV4_ADDRESS}
static routers=${IPv4gw}
@@ -450,31 +667,40 @@ setDHCPCD() {
}
setStaticIPv4() {
+ # Local, named variables
local IFCFG_FILE
local IPADDR
local CIDR
- if [[ -f /etc/dhcpcd.conf ]]; then
- # Debian Family
+ # For the Debian family, if dhcpcd.conf exists,
+ if [[ -f "/etc/dhcpcd.conf" ]]; then
+ # check if the IP is already in the file
if grep -q "${IPV4_ADDRESS}" /etc/dhcpcd.conf; then
- echo "::: Static IP already configured"
+ echo -e " ${INFO} Static IP already configured"
+ # If it's not,
else
+ # set it using our function
setDHCPCD
+ # Then use the ip command to immediately set the new address
ip addr replace dev "${PIHOLE_INTERFACE}" "${IPV4_ADDRESS}"
- echo ":::"
- echo "::: Setting IP to ${IPV4_ADDRESS}. You may need to restart after the install is complete."
- echo ":::"
+ # Also give a warning that the user may need to reboot their system
+ echo -e " ${TICK} Set IP address to ${IPV4_ADDRESS%/*}
+ You may need to restart after the install is complete"
fi
- elif [[ -f /etc/sysconfig/network-scripts/ifcfg-${PIHOLE_INTERFACE} ]];then
- # Fedora Family
+ # If it's not Debian, check if it's the Fedora family by checking for the file below
+ elif [[ -f "/etc/sysconfig/network-scripts/ifcfg-${PIHOLE_INTERFACE}" ]];then
+ # If it exists,
IFCFG_FILE=/etc/sysconfig/network-scripts/ifcfg-${PIHOLE_INTERFACE}
+ # check if the desired IP is already set
if grep -q "${IPV4_ADDRESS}" "${IFCFG_FILE}"; then
- echo "::: Static IP already configured"
+ echo -e " ${INFO} Static IP already configured"
+ # Otherwise,
else
+ # Put the IP in variables without the CIDR notation
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:
+ # Build Interface configuration file using the GLOBAL variables we have
{
echo "# Configured via Pi-hole installer"
echo "DEVICE=$PIHOLE_INTERFACE"
@@ -487,120 +713,172 @@ setStaticIPv4() {
echo "DNS2=$PIHOLE_DNS_2"
echo "USERCTL=no"
}> "${IFCFG_FILE}"
+ # Use ip to immediately set the new address
ip addr replace dev "${PIHOLE_INTERFACE}" "${IPV4_ADDRESS}"
+ # If NetworkMangler command line interface exists,
if command -v nmcli &> /dev/null;then
- # Tell NetworkManager to read our new sysconfig file
+ # Tell NetworkManagler 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 ":::"
+ # Show a warning that the user may need to restart
+ echo -e " ${TICK} Set IP address to ${IPV4_ADDRESS%/*}
+ You may need to restart after the install is complete"
fi
+ # If all that fails,
else
- echo "::: Warning: Unable to locate configuration file to set static IPv4 address!"
+ # show an error and exit
+ echo -e " ${INFO} Warning: Unable to locate configuration file to set static IPv4 address"
exit 1
fi
}
+# Check an IP address to see if it is a valid one
valid_ip() {
+ # Local, named variables
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
+ # If the IP matches the format xxx.xxx.xxx.xxx,
+ if [[ "${ip}" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
+ # Save the old Interfal Field Separator in a variable
OIFS=$IFS
+ # and set the new one to a dot (period)
IFS='.'
+ # Put the IP into an array
ip=(${ip})
+ # Restore the IFS to what it was
IFS=${OIFS}
- [[ ${ip[0]} -le 255 && ${ip[1]} -le 255 \
- && ${ip[2]} -le 255 && ${ip[3]} -le 255 ]]
+ ## Evaluate each octet by checking if it's less than or equal to 255 (the max for each octet)
+ [[ "${ip[0]}" -le 255 && "${ip[1]}" -le 255 \
+ && "${ip[2]}" -le 255 && "${ip[3]}" -le 255 ]]
+ # Save the exit code
stat=$?
fi
+ # Return the exit code
return ${stat}
}
+# A function to choose the upstream DNS provider(s)
setDNS() {
+ # Local, named variables
local DNSSettingsCorrect
+ # In an array, list the available upstream providers
DNSChooseOptions=(Google ""
OpenDNS ""
Level3 ""
Norton ""
Comodo ""
DNSWatch ""
+ Quad9 ""
Custom "")
- DNSchoices=$(whiptail --separate-output --menu "Select Upstream DNS Provider. To use your own, select Custom." ${r} ${c} 6 \
+ # In a whiptail dialog, show the options
+ DNSchoices=$(whiptail --separate-output --menu "Select Upstream DNS Provider. To use your own, select Custom." ${r} ${c} 7 \
"${DNSChooseOptions[@]}" 2>&1 >/dev/tty) || \
- { echo "::: Cancel selected. Exiting"; exit 1; }
+ # exit if Cancel is selected
+ { echo -e " ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; }
+
+ # Display the selection
+ echo -ne " ${INFO} Using "
+ # Depending on the user's choice, set the GLOBAl variables to the IP of the respective provider
case ${DNSchoices} in
Google)
- echo "::: Using Google DNS servers."
+ echo "Google DNS servers"
PIHOLE_DNS_1="8.8.8.8"
PIHOLE_DNS_2="8.8.4.4"
;;
OpenDNS)
- echo "::: Using OpenDNS servers."
+ echo "OpenDNS servers"
PIHOLE_DNS_1="208.67.222.222"
PIHOLE_DNS_2="208.67.220.220"
;;
Level3)
- echo "::: Using Level3 servers."
+ echo "Level3 servers"
PIHOLE_DNS_1="4.2.2.1"
PIHOLE_DNS_2="4.2.2.2"
;;
Norton)
- echo "::: Using Norton ConnectSafe servers."
+ echo "Norton ConnectSafe servers"
PIHOLE_DNS_1="199.85.126.10"
PIHOLE_DNS_2="199.85.127.10"
;;
Comodo)
- echo "::: Using Comodo Secure servers."
+ echo "Comodo Secure servers"
PIHOLE_DNS_1="8.26.56.26"
PIHOLE_DNS_2="8.20.247.20"
;;
DNSWatch)
- echo "::: Using DNS.WATCH servers."
+ echo "DNS.WATCH servers"
PIHOLE_DNS_1="84.200.69.80"
PIHOLE_DNS_2="84.200.70.40"
;;
+ Quad9)
+ echo "Quad9 servers"
+ PIHOLE_DNS_1="9.9.9.9"
+ ;;
Custom)
- until [[ ${DNSSettingsCorrect} = True ]]; do
+ # Until the DNS settings are selected,
+ until [[ "${DNSSettingsCorrect}" = True ]]; do
+ #
strInvalid="Invalid"
- if [ ! ${PIHOLE_DNS_1} ]; then
- if [ ! ${PIHOLE_DNS_2} ]; then
+ # If the first
+ if [[ ! "${PIHOLE_DNS_1}" ]]; then
+ # and second upstream servers do not exist
+ if [[ ! "${PIHOLE_DNS_2}" ]]; then
+ #
prePopulate=""
+ # Otherwise,
else
+ #
prePopulate=", ${PIHOLE_DNS_2}"
fi
- elif [ ${PIHOLE_DNS_1} ] && [ ! ${PIHOLE_DNS_2} ]; then
+ #
+ elif [[ "${PIHOLE_DNS_1}" ]] && [[ ! "${PIHOLE_DNS_2}" ]]; then
+ #
prePopulate="${PIHOLE_DNS_1}"
- elif [ ${PIHOLE_DNS_1} ] && [ ${PIHOLE_DNS_2} ]; then
+ #
+ 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) || \
- { echo "::: Cancel selected. Exiting"; exit 1; }
+ # Dialog for the user to enter custom upstream servers
+ 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) || \
+ { echo -e " ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; }
+ #
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
+ # If the IP is valid,
+ if ! valid_ip "${PIHOLE_DNS_1}" || [[ ! "${PIHOLE_DNS_1}" ]]; then
+ # store it in the variable so we can use it
PIHOLE_DNS_1=${strInvalid}
fi
- if ! valid_ip "${PIHOLE_DNS_2}" && [ "${PIHOLE_DNS_2}" ]; then
+ # Do the same for the secondary server
+ if ! valid_ip "${PIHOLE_DNS_2}" && [[ "${PIHOLE_DNS_2}" ]]; then
PIHOLE_DNS_2=${strInvalid}
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
+ # If either of the DNS servers are invalid,
+ if [[ "${PIHOLE_DNS_1}" == "${strInvalid}" ]] || [[ "${PIHOLE_DNS_2}" == "${strInvalid}" ]]; then
+ # explain this to the user
+ 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}
+ # and set the variables back to nothing
+ if [[ "${PIHOLE_DNS_1}" == "${strInvalid}" ]]; then
PIHOLE_DNS_1=""
fi
- if [[ ${PIHOLE_DNS_2} == "${strInvalid}" ]]; then
+ if [[ "${PIHOLE_DNS_2}" == "${strInvalid}" ]]; then
PIHOLE_DNS_2=""
fi
+ # Since the settings will not work, stay in the loop
DNSSettingsCorrect=False
+ # Othwerise,
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
+ # Show the settings
+ 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
+ # and break from the loop since the servers are vaid
DNSSettingsCorrect=True
+ # Otherwise,
else
- # If the settings are wrong, the loop continues
+ # If the settings are wrong, the loop continues
DNSSettingsCorrect=False
fi
fi
@@ -609,51 +887,67 @@ setDNS() {
esac
}
+# Allow the user to enable/disable logging
setLogging() {
+ # Local, named variables
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)
+ # Ask if the user wants to log queries
+ LogToggleCommand=(whiptail --separate-output --radiolist "Do you want to log queries?\\n (Disabling will render graphs on the Admin page useless):" ${r} ${c} 6)
+ # The default selection is on
LogChooseOptions=("On (Recommended)" "" on
Off "" off)
- LogChoices=$("${LogToggleCommand[@]}" "${LogChooseOptions[@]}" 2>&1 >/dev/tty) || (echo "::: Cancel selected. Exiting..." && exit 1)
+ # Get the user's choice
+ LogChoices=$("${LogToggleCommand[@]}" "${LogChooseOptions[@]}" 2>&1 >/dev/tty) || (echo -e " ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}" && exit 1)
case ${LogChoices} in
+ # If it's on
"On (Recommended)")
- echo "::: Logging On."
+ echo -e " ${INFO} Logging On."
+ # Set the GLOBAL variable to true so we know what they selected
QUERY_LOGGING=true
;;
+ # Othwerise, it's off,
Off)
- echo "::: Logging Off."
+ echo -e " ${INFO} Logging Off."
+ # So set it to false
QUERY_LOGGING=false
;;
esac
}
+# Funtion to ask the user if they want to install the dashboard
setAdminFlag() {
+ # Local, named variables
local WebToggleCommand
local WebChooseOptions
local WebChoices
+ # Similar to the logging function, ask what the user wants
WebToggleCommand=(whiptail --separate-output --radiolist "Do you wish to install the web admin interface?" ${r} ${c} 6)
+ # with the default being enabled
WebChooseOptions=("On (Recommended)" "" on
Off "" off)
- WebChoices=$("${WebToggleCommand[@]}" "${WebChooseOptions[@]}" 2>&1 >/dev/tty) || (echo "::: Cancel selected. Exiting..." && exit 1)
+ WebChoices=$("${WebToggleCommand[@]}" "${WebChooseOptions[@]}" 2>&1 >/dev/tty) || (echo -e " ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}" && exit 1)
+ # Depending on their choice
case ${WebChoices} in
"On (Recommended)")
- echo "::: Web Interface On."
+ echo -e " ${INFO} Web Interface On"
+ # Set it to true
INSTALL_WEB=true
;;
Off)
- echo "::: Web Interface off."
+ echo -e " ${INFO} Web Interface Off"
+ # or false
INSTALL_WEB=false
;;
esac
}
-
+# Check if /etc/dnsmasq.conf is from pihole. If so replace with an original and install new in .d directory
version_check_dnsmasq() {
- # Check if /etc/dnsmasq.conf is from pihole. If so replace with an original and install new in .d directory
+ # Local, named variables
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"
@@ -661,103 +955,149 @@ version_check_dnsmasq() {
local dnsmasq_pihole_01_snippet="${PI_HOLE_LOCAL_REPO}/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 the dnsmasq config file exists
+ if [[ -f "${dnsmasq_conf}" ]]; then
+ echo -ne " ${INFO} Existing dnsmasq.conf found..."
+ # If gravity.list is found within this file, we presume it's from older versions on Pi-hole,
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..."
+ echo -ne " ${INFO} Backing up dnsmasq.conf to dnsmasq.conf.orig..."
+ # so backup the original file
mv -f ${dnsmasq_conf} ${dnsmasq_conf_orig}
- echo " done."
- echo -n "::: Restoring default dnsmasq.conf..."
+ echo -e "${OVER} ${TICK} Backing up dnsmasq.conf to dnsmasq.conf.orig..."
+ echo -ne " ${INFO} Restoring default dnsmasq.conf..."
+ # and replace it with the default
cp ${dnsmasq_original_config} ${dnsmasq_conf}
- echo " done."
+ echo -e "${OVER} ${TICK} Restoring default dnsmasq.conf..."
+ # Otherwise,
else
+ # Don't to anything
echo " it is not a Pi-hole file, leaving alone!"
fi
else
- echo -n "::: No dnsmasq.conf found.. restoring default dnsmasq.conf..."
+ # If a file cannot be found,
+ echo -ne " ${INFO} No dnsmasq.conf found... restoring default dnsmasq.conf..."
+ # restore the default one
cp ${dnsmasq_original_config} ${dnsmasq_conf}
- echo " done."
+ echo -e "${OVER} ${TICK} No dnsmasq.conf found... restoring default dnsmasq.conf..."
fi
- echo -n "::: Copying 01-pihole.conf to /etc/dnsmasq.d/01-pihole.conf..."
+ echo -en " ${INFO} Copying 01-pihole.conf to /etc/dnsmasq.d/01-pihole.conf..."
+ # Copy the new Pi-hole DNS config file into the dnsmasq.d directory
cp ${dnsmasq_pihole_01_snippet} ${dnsmasq_pihole_01_location}
- echo " done."
+ echo -e "${OVER} ${TICK} Copying 01-pihole.conf to /etc/dnsmasq.d/01-pihole.conf"
+ # Replace our placeholder values with the GLOBAL DNS variables that we populated earlier
+ # First, swap in the interface to listen on
sed -i "s/@INT@/$PIHOLE_INTERFACE/" ${dnsmasq_pihole_01_location}
if [[ "${PIHOLE_DNS_1}" != "" ]]; then
+ # Then swap in the primary DNS server
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
+ # Then swap in the primary DNS server
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/^#conf-dir=\/etc\/dnsmasq.d$/conf-dir=\/etc\/dnsmasq.d/' ${dnsmasq_conf}
+ # If the user does not want to enable logging,
if [[ "${QUERY_LOGGING}" == false ]] ; then
- #Disable Logging
+ # Disable it by commenting out the directive in the DNS config file
sed -i 's/^log-queries/#log-queries/' ${dnsmasq_pihole_01_location}
+ # Otherwise,
else
- #Enable Logging
+ # enable it by uncommenting the directive in the DNS config file
sed -i 's/^#log-queries/log-queries/' ${dnsmasq_pihole_01_location}
fi
}
+# Clean an existing installation to prepare for upgrade/reinstall
clean_existing() {
- # Clean an exiting installation to prepare for upgrade/reinstall
- # ${1} Directory to clean; ${2} Array of files to remove
+ # Local, named variables
+ # ${1} Directory to clean
local clean_directory="${1}"
+ # Make ${2} the new one?
shift
+ # ${2} Array of files to remove
local old_files=( "$@" )
+ # For each script found in the old files array
for script in "${old_files[@]}"; do
+ # Remove them
rm -f "${clean_directory}/${script}.sh"
done
}
+# Install the scripts from repository to their various locations
installScripts() {
- # Install the scripts from repository to their various locations
-
- echo ":::"
- echo -n "::: Installing scripts from ${PI_HOLE_LOCAL_REPO}..."
+ # Local, named variables
+ local str="Installing scripts from ${PI_HOLE_LOCAL_REPO}"
+ echo -ne " ${INFO} ${str}..."
# Clear out script files from Pi-hole scripts directory.
clean_existing "${PI_HOLE_INSTALL_DIR}" "${PI_HOLE_FILES[@]}"
# Install files from local core repository
if is_repo "${PI_HOLE_LOCAL_REPO}"; then
+ # move into the directory
cd "${PI_HOLE_LOCAL_REPO}"
+ # Install the scripts by:
+ # -o setting the owner to the user
+ # -Dm755 create all leading components of destiantion except the last, then copy the source to the destiantion and setting the permissions to 755
+ #
+ # This first one is the directory
install -o "${USER}" -Dm755 -d "${PI_HOLE_INSTALL_DIR}"
+ # The rest are the scripts Pi-hole needs
install -o "${USER}" -Dm755 -t "${PI_HOLE_INSTALL_DIR}" gravity.sh
install -o "${USER}" -Dm755 -t "${PI_HOLE_INSTALL_DIR}" ./advanced/Scripts/*.sh
install -o "${USER}" -Dm755 -t "${PI_HOLE_INSTALL_DIR}" ./automated\ install/uninstall.sh
+ install -o "${USER}" -Dm755 -t "${PI_HOLE_INSTALL_DIR}" ./advanced/Scripts/COL_TABLE
install -o "${USER}" -Dm755 -t /usr/local/bin/ pihole
install -Dm644 ./advanced/bash-completion/pihole /etc/bash_completion.d/pihole
- echo " done."
+ echo -e "${OVER} ${TICK} ${str}"
+ # Otherwise,
else
- echo " *** ERROR: Local repo ${PI_HOLE_LOCAL_REPO} not found, exiting."
+ # Show an error and exit
+ echo -e "${OVER} ${CROSS} ${str}
+ ${COL_LIGHT_RED}Error: Local repo ${PI_HOLE_LOCAL_REPO} not found, exiting installer${COL_NC}"
exit 1
fi
}
+# Install the configs from PI_HOLE_LOCAL_REPO to their various locations
installConfigs() {
- # Install the configs from PI_HOLE_LOCAL_REPO to their various locations
- echo ":::"
- echo "::: Installing configs from ${PI_HOLE_LOCAL_REPO}..."
+ echo ""
+ echo -e " ${INFO} Installing configs from ${PI_HOLE_LOCAL_REPO}..."
+ # Make sure Pi-hole's config files are in place
version_check_dnsmasq
- #Only mess with lighttpd configs if user has chosen to install web interface
- if [[ ${INSTALL_WEB} == true ]]; then
- if [ ! -d "/etc/lighttpd" ]; then
+ # If the user chose to install the dashboard,
+ if [[ "${INSTALL_WEB}" == true ]]; then
+ # and if the Web server conf directory does not exist,
+ if [[ ! -d "/etc/lighttpd" ]]; then
+ # make it
mkdir /etc/lighttpd
+ # and set the owners
chown "${USER}":root /etc/lighttpd
- elif [ -f "/etc/lighttpd/lighttpd.conf" ]; then
+ # Otherwise, if the config file already exists
+ elif [[ -f "/etc/lighttpd/lighttpd.conf" ]]; then
+ # back up the original
mv /etc/lighttpd/lighttpd.conf /etc/lighttpd/lighttpd.conf.orig
fi
+ # and copy in the config file Pi-hole needs
cp ${PI_HOLE_LOCAL_REPO}/advanced/${LIGHTTPD_CFG} /etc/lighttpd/lighttpd.conf
+ # if there is a custom block page in the html/pihole directory, replace 404 handler in lighttpd config
+ if [[ -f "/var/www/html/pihole/custom.php" ]]; then
+ sed -i 's/^\(server\.error-handler-404\s*=\s*\).*$/\1"pihole\/custom\.php"/' /etc/lighttpd/lighttpd.conf
+ fi
+ # Make the directories if they do not exist and set the owners
mkdir -p /var/run/lighttpd
chown ${LIGHTTPD_USER}:${LIGHTTPD_GROUP} /var/run/lighttpd
mkdir -p /var/cache/lighttpd/compress
@@ -770,80 +1110,119 @@ installConfigs() {
stop_service() {
# Stop service passed in as argument.
# Can softfail, as process may not be installed when this is called
- echo ":::"
- echo -n "::: Stopping ${1} service..."
+ local str="Stopping ${1} service"
+ echo ""
+ echo -ne " ${INFO} ${str}..."
if command -v systemctl &> /dev/null; then
systemctl stop "${1}" &> /dev/null || true
else
service "${1}" stop &> /dev/null || true
fi
- echo " done."
+ echo -e "${OVER} ${TICK} ${str}..."
}
+# Start/Restart service passed in as argument
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..."
+ # Local, named variables
+ local str="Starting ${1} service"
+ echo ""
+ echo -ne " ${INFO} ${str}..."
+ # If systemctl exists,
if command -v systemctl &> /dev/null; then
+ # use that to restart the service
systemctl restart "${1}" &> /dev/null
+ # Otherwise,
else
+ # fall back to the service command
service "${1}" restart &> /dev/null
fi
- echo " done."
+ echo -e "${OVER} ${TICK} ${str}"
}
+# Enable service so that it will start with next reboot
enable_service() {
- # Enable service so that it will start with next reboot
- echo ":::"
- echo -n "::: Enabling ${1} service to start on reboot..."
+ # Local, named variables
+ local str="Enabling ${1} service to start on reboot"
+ echo ""
+ echo -ne " ${INFO} ${str}..."
+ # If systemctl exists,
if command -v systemctl &> /dev/null; then
+ # use that to enable the service
systemctl enable "${1}" &> /dev/null
+ # Othwerwise,
else
+ # use update-rc.d to accomplish this
update-rc.d "${1}" defaults &> /dev/null
fi
- echo " done."
+ echo -e "${OVER} ${TICK} ${str}"
}
update_package_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)
- #Update package cache on apt based OSes. Do this every time since
- #it's quick and packages can be updated at any time.
+ # Update package cache on apt based OSes. Do this every time since
+ # it's quick and packages can be updated at any time.
- echo ":::"
- echo -n "::: Updating local cache of available packages..."
+ # Local, named variables
+ local str="Update local cache of available packages"
+ echo ""
+ echo -ne " ${INFO} ${str}..."
+ # Create a command from the package cache variable
if eval "${UPDATE_PKG_CACHE}" &> /dev/null; then
- echo " done!"
+ echo -e "${OVER} ${TICK} ${str}"
+ # Otherwise,
else
- echo -en "\n!!! ERROR - Unable to update package cache. Please try \"${UPDATE_PKG_CACHE}\""
+ # show an error and exit
+ echo -e "${OVER} ${CROSS} ${str}"
+ echo -ne " ${COL_LIGHT_RED}Error: Unable to update package cache. Please try \"${UPDATE_PKG_CACHE}\"${COL_NC}"
return 1
fi
}
+# Let user know if they have outdated packages on their system and
+# advise them to run a package update at soonest possible.
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...."
+ # Local, named variables
+ local str="Checking ${PKG_MANAGER} for upgraded packages"
+ echo -ne "\\n ${INFO} ${str}..."
+ # Store the list of packages in a variable
updatesToInstall=$(eval "${PKG_COUNT}")
- echo " done!"
- echo ":::"
+
if [[ -d "/lib/modules/$(uname -r)" ]]; then
- if [[ ${updatesToInstall} -eq "0" ]]; then
- echo "::: Your system is up to date! Continuing with Pi-hole installation..."
+ #
+ if [[ "${updatesToInstall}" -eq 0 ]]; then
+ #
+ echo -e "${OVER} ${TICK} ${str}... up to date!"
+ echo ""
else
- echo "::: There are ${updatesToInstall} updates available for your system!"
- echo "::: We recommend you update your OS after installing Pi-hole! "
- echo ":::"
+ #
+ echo -e "${OVER} ${TICK} ${str}... ${updatesToInstall} updates available"
+ echo -e " ${INFO} ${COL_LIGHT_GREEN}It is recommended to update your OS after installing the Pi-hole! ${COL_NC}"
+ echo ""
fi
else
- echo "::: Kernel update detected, please reboot your system and try again if your installation fails."
+ echo -e "${OVER} ${CROSS} ${str}
+ Kernel update detected. If the install fails, please reboot and try again\\n"
fi
}
+# What's this doing outside of a function in the middle of nowhere?
+counter=0
+
install_dependent_packages() {
+ # Local, named variables should be used here, especially for an iterator
+ # Add one to the counter
+ counter=$((counter+1))
+ # If it equals 1,
+ if [[ "${counter}" == 1 ]]; then
+ #
+ echo -e " ${INFO} Installer Dependency checks..."
+ else
+ #
+ echo -e " ${INFO} Main Dependency checks..."
+ fi
+
# Install packages passed in via argument array
# No spinner - conflicts with set -e
declare -a argArray1=("${!1}")
@@ -855,155 +1234,196 @@ install_dependent_packages() {
# NOTE: We may be able to use this installArray in the future to create a list of package that were
# installed by us, and remove only the installed packages, and not the entire list.
if command -v debconf-apt-progress &> /dev/null; then
+ # For each package,
for i in "${argArray1[@]}"; do
- echo -n "::: Checking for $i..."
+ echo -ne " ${INFO} Checking for $i..."
+ #
if dpkg-query -W -f='${Status}' "${i}" 2>/dev/null | grep "ok installed" &> /dev/null; then
- echo " installed!"
+ #
+ echo -e "${OVER} ${TICK} Checking for $i"
else
- echo " added to install list!"
+ #
+ echo -e "${OVER} ${CROSS} Checking for $i (will be installed)"
+ #
installArray+=("${i}")
fi
done
- if [[ ${#installArray[@]} -gt 0 ]]; then
+ #
+ if [[ "${#installArray[@]}" -gt 0 ]]; then
+ #
test_dpkg_lock
+ #
debconf-apt-progress -- "${PKG_INSTALL[@]}" "${installArray[@]}"
return
fi
+ echo ""
+ #
return 0
fi
- #Fedora/CentOS
+ # Install Fedora/CentOS packages
for i in "${argArray1[@]}"; do
- echo -n "::: Checking for $i..."
+ echo -ne " ${INFO} Checking for $i..."
+ #
if ${PKG_MANAGER} -q list installed "${i}" &> /dev/null; then
- echo " installed!"
+ echo -e "${OVER} ${TICK} Checking for $i"
else
- echo " added to install list!"
+ echo -e "${OVER} ${CROSS} Checking for $i (will be installed)"
+ #
installArray+=("${i}")
fi
done
- if [[ ${#installArray[@]} -gt 0 ]]; then
- "${PKG_INSTALL[@]}" "${installArray[@]}" &> /dev/null
- return
- fi
- return 0
+ #
+ if [[ "${#installArray[@]}" -gt 0 ]]; then
+ #
+ "${PKG_INSTALL[@]}" "${installArray[@]}" &> /dev/null
+ return
+ fi
+ echo ""
+ return 0
}
+# Create logfiles if necessary
CreateLogFile() {
- # Create logfiles if necessary
- echo ":::"
- echo -n "::: Creating log file and changing owner to dnsmasq..."
- if [ ! -f /var/log/pihole.log ]; then
+ local str="Creating log and changing owner to dnsmasq"
+ echo ""
+ echo -ne " ${INFO} ${str}..."
+ # If the pihole log does not exist,
+ if [[ ! -f "/var/log/pihole.log" ]]; then
+ # Make it,
touch /var/log/pihole.log
+ # set the permissions,
chmod 644 /var/log/pihole.log
+ # and owners
chown "${DNSMASQ_USER}":root /var/log/pihole.log
- echo " done!"
+ echo -e "${OVER} ${TICK} ${str}"
+ # Otherwise,
else
- echo " already exists!"
+ # the file should already exist
+ echo -e " ${COL_LIGHT_GREEN}log already exists!${COL_NC}"
fi
}
+# Install the Web interface dashboard
installPiholeWeb() {
- # 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.php" ]; then
- echo "::: Existing index.php detected, not overwriting"
- else
- echo -n "::: index.php missing, replacing... "
- cp ${PI_HOLE_LOCAL_REPO}/advanced/index.php /var/www/html/pihole/
- echo " done!"
- fi
+ echo ""
+ echo " ${INFO} Installing blocking page..."
+
+ local str="Creating directory for blocking page, and copying files"
+ echo -ne " ${INFO} ${str}..."
+ # Install the directory
+ install -d /var/www/html/pihole
+ # and the blockpage
+ install -D ${PI_HOLE_LOCAL_REPO}/advanced/{index,blockingpage}.* /var/www/html/pihole/
+
+ # Remove superseded file
+ if [[ -e "/var/www/html/pihole/index.js" ]]; then
+ rm "/var/www/html/pihole/index.js"
+ 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 ${PI_HOLE_LOCAL_REPO}/advanced/index.js /var/www/html/pihole/
- echo " done!"
- fi
-
- if [ -f "/var/www/html/pihole/blockingpage.css" ]; then
- echo "::: Existing blockingpage.css detected, not overwriting"
- else
- echo -n "::: blockingpage.css missing, replacing... "
- cp ${PI_HOLE_LOCAL_REPO}/advanced/blockingpage.css /var/www/html/pihole
- echo " done!"
- fi
+ echo -e "${OVER} ${TICK} ${str}"
+ local str="Backing up index.lighttpd.html"
+ echo -ne " ${INFO} ${str}..."
+ # If the default index file exists,
+ if [[ -f "/var/www/html/index.lighttpd.html" ]]; then
+ # back it up
+ mv /var/www/html/index.lighttpd.html /var/www/html/index.lighttpd.orig
+ echo -e "${OVER} ${TICK} ${str}"
+ # Othwerwise,
else
- echo "::: Creating directory for blocking page"
- install -d /var/www/html/pihole
- install -D ${PI_HOLE_LOCAL_REPO}/advanced/{index,blockingpage}.* /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
- echo " done!"
+ # don't do anything
+ echo -e "${OVER} ${CROSS} ${str}
+ No default index.lighttpd.html file found... not backing up"
fi
- # Install Sudoer file
- echo ":::"
- echo -n "::: Installing sudoer file..."
+ # Install Sudoers file
+ echo ""
+ local str="Installing sudoer file"
+ echo -ne " ${INFO} ${str}..."
+ # Make the .d directory if it doesn't exist
mkdir -p /etc/sudoers.d/
+ # and copy in the pihole sudoers file
cp ${PI_HOLE_LOCAL_REPO}/advanced/pihole.sudo /etc/sudoers.d/pihole
# Add lighttpd user (OS dependent) to sudoers file
echo "${LIGHTTPD_USER} ALL=NOPASSWD: /usr/local/bin/pihole" >> /etc/sudoers.d/pihole
+ # If the Web server user is lighttpd,
if [[ "$LIGHTTPD_USER" == "lighttpd" ]]; then
# Allow executing pihole via sudo with Fedora
# Usually /usr/local/bin is not permitted as directory for sudoable programms
echo "Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin" >> /etc/sudoers.d/pihole
fi
-
+ # Set the strict permissions on the file
chmod 0440 /etc/sudoers.d/pihole
- echo " done!"
+ echo -e "${OVER} ${TICK} ${str}"
}
+# Installs a cron file
installCron() {
# Install the cron job
- echo ":::"
- echo -n "::: Installing latest Cron script..."
+ local str="Installing latest Cron script"
+ echo ""
+ echo -ne " ${INFO} ${str}..."
+ # Copy the cron file over from the local repo
cp ${PI_HOLE_LOCAL_REPO}/advanced/pihole.cron /etc/cron.d/pihole
- echo " done!"
+ # Randomize gravity update time
+ sed -i "s/59 1/$((1 + RANDOM % 58)) $((3 + RANDOM % 2))/" /etc/cron.d/pihole
+ echo -e "${OVER} ${TICK} ${str}"
}
+# Gravity is a very important script as it aggregates all of the domains into a single HOSTS formatted list,
+# which is what Pi-hole needs to begin blocking ads
runGravity() {
- # Run gravity.sh to build blacklists
- echo ":::"
- echo "::: Preparing to run gravity.sh to refresh hosts..."
+ echo ""
+ echo -e " ${INFO} Preparing to run gravity.sh to refresh hosts..."
+ # If cached lists exist,
if ls /etc/pihole/list* 1> /dev/null 2>&1; then
- echo "::: Cleaning up previous install (preserving whitelist/blacklist)"
+ echo -e " ${INFO} Cleaning up previous install (preserving whitelist/blacklist)"
+ # remove them
rm /etc/pihole/list.*
fi
- # Test if /etc/pihole/adlists.default exists
+ # If the default ad lists file exists,
if [[ ! -e /etc/pihole/adlists.default ]]; then
+ # copy it over from the local repo
cp ${PI_HOLE_LOCAL_REPO}/adlists.default /etc/pihole/adlists.default
fi
- echo "::: Running gravity.sh"
+ echo -e " ${INFO} Running gravity.sh"
+ # Run gravity in the current shell
{ /opt/pihole/gravity.sh; }
}
+# Check if the pihole user exists and create if it does not
create_pihole_user() {
- # Check if user pihole exists and create if not
- echo "::: Checking if user 'pihole' exists..."
+ local str="Checking for user 'pihole'"
+ echo -ne " ${INFO} ${str}..."
+ # If the user pihole exists,
if id -u pihole &> /dev/null; then
- echo "::: User 'pihole' already exists"
+ # just show a success
+ echo -ne "${OVER} ${TICK} ${str}"
+ # Othwerwise,
else
- echo "::: User 'pihole' doesn't exist. Creating..."
+ echo -ne "${OVER} ${CROSS} ${str}"
+ local str="Creating user 'pihole'"
+ echo -ne " ${INFO} ${str}..."
+ # create her with the useradd command
useradd -r -s /usr/sbin/nologin pihole
+ echo -ne "${OVER} ${TICK} ${str}"
fi
}
+# Allow HTTP and DNS traffic
configureFirewall() {
- # Allow HTTP and DNS traffic
+ echo ""
+ # If a firewall is running,
if firewall-cmd --state &> /dev/null; then
- whiptail --title "Firewall in use" --yesno "We have detected a running firewall\n\nPi-hole currently requires HTTP and DNS port access.\n\n\n\nInstall Pi-hole default firewall rules?" ${r} ${c} || \
- { echo -e ":::\n::: Not installing firewall rulesets."; return 0; }
- echo -e ":::\n:::\n Configuring FirewallD for httpd and dnsmasq."
+ # ask if the user wants to install Pi-hole's default firwall rules
+ whiptail --title "Firewall in use" --yesno "We have detected a running firewall\\n\\nPi-hole currently requires HTTP and DNS port access.\\n\\n\\n\\nInstall Pi-hole default firewall rules?" ${r} ${c} || \
+ { echo -e " ${INFO} Not installing firewall rulesets."; return 0; }
+ echo -e " ${TICK} Configuring FirewallD for httpd and dnsmasq"
+ # Allow HTTP and DNS traffice
firewall-cmd --permanent --add-service=http --add-service=dns
+ # Reload the firewall to apply these changes
firewall-cmd --reload
return 0
# Check for proper kernel modules to prevent failure
@@ -1011,9 +1431,9 @@ configureFirewall() {
# If chain Policy is not ACCEPT or last Rule is not ACCEPT
# then check and insert our Rules above the DROP/REJECT Rule.
if iptables -S INPUT | head -n1 | grep -qv '^-P.*ACCEPT$' || iptables -S INPUT | tail -n1 | grep -qv '^-\(A\|P\).*ACCEPT$'; then
- whiptail --title "Firewall in use" --yesno "We have detected a running firewall\n\nPi-hole currently requires HTTP and DNS port access.\n\n\n\nInstall Pi-hole default firewall rules?" ${r} ${c} || \
- { echo -e ":::\n::: Not installing firewall rulesets."; return 0; }
- echo -e ":::\n::: Installing new IPTables firewall rulesets."
+ whiptail --title "Firewall in use" --yesno "We have detected a running firewall\\n\\nPi-hole currently requires HTTP and DNS port access.\\n\\n\\n\\nInstall Pi-hole default firewall rules?" ${r} ${c} || \
+ { echo -e " ${INFO} Not installing firewall rulesets."; return 0; }
+ echo -e " ${TICK} Installing new IPTables firewall rulesets"
# Check chain first, otherwise a new rule will duplicate old ones
iptables -C INPUT -p tcp -m tcp --dport 80 -j ACCEPT &> /dev/null || iptables -I INPUT 1 -p tcp -m tcp --dport 80 -j ACCEPT
iptables -C INPUT -p tcp -m tcp --dport 53 -j ACCEPT &> /dev/null || iptables -I INPUT 1 -p tcp -m tcp --dport 53 -j ACCEPT
@@ -1021,29 +1441,37 @@ configureFirewall() {
iptables -C INPUT -p tcp -m tcp --dport 4711:4720 -i lo -j ACCEPT &> /dev/null || iptables -I INPUT 1 -p tcp -m tcp --dport 4711:4720 -i lo -j ACCEPT
return 0
fi
+ # Othwerwise,
else
- echo -e ":::\n::: No active firewall detected.. skipping firewall configuration."
+ # no firewall is running
+ echo -e " ${INFO} No active firewall detected.. skipping firewall configuration"
+ # so just exit
return 0
fi
- echo -e ":::\n::: Skipping firewall configuration."
+ echo -e " ${INFO} Skipping firewall configuration"
}
+#
finalExports() {
-
- if [[ ${INSTALL_WEB} == false ]]; then
- #No web interface installed, and therefore no block page set IPV4/6 to 0.0.0.0 and ::/0
- if [ ${IPV4_ADDRESS} ]; then
+ # If the Web interface is not set to be installed,
+ if [[ "${INSTALL_WEB}" == false ]]; then
+ # and if there is not an IPv4 address,
+ if [[ "${IPV4_ADDRESS}" ]]; then
+ # there is no block page, so set IPv4 to 0.0.0.0 (all IP addresses)
IPV4_ADDRESS="0.0.0.0"
fi
- if [ ${IPV6_ADDRESS} ]; then
+ if [[ "${IPV6_ADDRESS}" ]]; then
+ # and IPv6 to ::/0
IPV6_ADDRESS="::/0"
fi
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;/INSTALL_WEB/d;' "${setupVars}"
+ # If the setup variable file exists,
+ if [[ -e "${setupVars}" ]]; then
+ # update the variables in the file
+ sed -i.update.bak '/PIHOLE_INTERFACE/d;/IPV4_ADDRESS/d;/IPV6_ADDRESS/d;/PIHOLE_DNS_1/d;/PIHOLE_DNS_2/d;/QUERY_LOGGING/d;/INSTALL_WEB/d;/LIGHTTPD_ENABLED/d;' "${setupVars}"
fi
+ # echo the information to the user
{
echo "PIHOLE_INTERFACE=${PIHOLE_INTERFACE}"
echo "IPV4_ADDRESS=${IPV4_ADDRESS}"
@@ -1052,25 +1480,27 @@ finalExports() {
echo "PIHOLE_DNS_2=${PIHOLE_DNS_2}"
echo "QUERY_LOGGING=${QUERY_LOGGING}"
echo "INSTALL_WEB=${INSTALL_WEB}"
+ echo "LIGHTTPD_ENABLED=${LIGHTTPD_ENABLED}"
}>> "${setupVars}"
- # Look for DNS server settings which would have to be reapplied
+ # Bring in the current settings and the functions to manipulate them
source "${setupVars}"
source "${PI_HOLE_LOCAL_REPO}/advanced/Scripts/webpage.sh"
- if [[ "${DNS_FQDN_REQUIRED}" != "" ]] ; then
- ProcessDNSSettings
- fi
+ # Look for DNS server settings which would have to be reapplied
+ ProcessDNSSettings
- if [[ "${DHCP_ACTIVE}" != "" ]] ; then
- ProcessDHCPSettings
- fi
+ # Look for DHCP server settings which would have to be reapplied
+ ProcessDHCPSettings
}
+# Install the logrotate script
installLogrotate() {
- # Install the logrotate script
- echo ":::"
- echo -n "::: Installing latest logrotate script..."
+
+ local str="Installing latest logrotate script"
+ echo ""
+ echo -ne " ${INFO} ${str}..."
+ # Copy the file over from the local repo
cp ${PI_HOLE_LOCAL_REPO}/advanced/logrotate /etc/pihole/logrotate
# Different operating systems have different user / group
# settings for logrotate that makes it impossible to create
@@ -1079,48 +1509,67 @@ installLogrotate() {
# customize the logrotate script here in order to reflect
# the local properties of the /var/log directory
logusergroup="$(stat -c '%U %G' /var/log)"
- if [[ ! -z $logusergroup ]]; then
+ # If the variable has a value,
+ if [[ ! -z "${logusergroup}" ]]; then
+ #
sed -i "s/# su #/su ${logusergroup}/g;" /etc/pihole/logrotate
fi
- echo " done!"
+ echo -e "${OVER} ${TICK} ${str}"
}
+# Install base files and web interface
installPihole() {
- # Install base files and web interface
+ # Create the pihole user
create_pihole_user
- if [[ ${INSTALL_WEB} == true ]]; then
- if [ ! -d "/var/www/html" ]; then
+ # If the user wants to install the Web interface,
+ if [[ "${INSTALL_WEB}" == true ]]; then
+ if [[ ! -d "/var/www/html" ]]; then
+ # make the Web directory if necessary
mkdir -p /var/www/html
fi
+ # Set the owner and permissions
chown ${LIGHTTPD_USER}:${LIGHTTPD_GROUP} /var/www/html
chmod 775 /var/www/html
+ # Give pihole access to the Web server group
usermod -a -G ${LIGHTTPD_GROUP} pihole
- if [ -x "$(command -v lighty-enable-mod)" ]; then
+ # If the lighttpd command is executable,
+ if [[ -x "$(command -v lighty-enable-mod)" ]]; then
+ # enable fastcgi and fastcgi-php
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"
+ # Othweise, show info about installing them
+ echo -e " ${INFO} Warning: 'lighty-enable-mod' utility not found
+ Please ensure fastcgi is enabled if you experience issues\\n"
fi
fi
+ # Install scripts,
installScripts
+ # configs,
installConfigs
+ # and create the log file
CreateLogFile
- if [[ ${INSTALL_WEB} == true ]]; then
+ # If the user wants to install the dashboard,
+ if [[ "${INSTALL_WEB}" == true ]]; then
+ # do so
installPiholeWeb
fi
+ # Install the cron file
installCron
+ # Install the logrotate file
installLogrotate
- FTLdetect || echo "::: FTL Engine not installed."
+ # Check if FTL is installed
+ FTLdetect || echo -e " ${CROSS} FTL Engine not installed"
+ # Configure the firewall
configureFirewall
+
+ #update setupvars.conf with any variables that may or may not have been changed during the install
finalExports
- #runGravity
}
+# 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.
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}
@@ -1128,54 +1577,69 @@ accountForRefactor() {
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() {
accountForRefactor
# Install base files and web interface
installScripts
+ # Install config files
installConfigs
+ # Create the log file
CreateLogFile
- if [[ ${INSTALL_WEB} == true ]]; then
+ # If the user wants to install the dasboard,
+ if [[ "${INSTALL_WEB}" == true ]]; then
+ # do so
installPiholeWeb
fi
+ # Install the cron file
installCron
+ # Install logrotate
installLogrotate
- FTLdetect || echo "::: FTL Engine not installed."
- finalExports #re-export setupVars.conf to account for any new vars added in new versions
- #runGravity
-}
+ # Detect if FTL is installed
+ FTLdetect || echo -e " ${CROSS} FTL Engine not installed."
+ #update setupvars.conf with any variables that may or may not have been changed during the install
+ finalExports
+
+}
+# SELinux
checkSelinux() {
+ # If the getenforce command exists,
if command -v getenforce &> /dev/null; then
- echo ":::"
- echo -n "::: SELinux Support Detected... Mode: "
+ # Store the current mode in a variable
enforceMode=$(getenforce)
- echo "${enforceMode}"
+ echo -e "\\n ${INFO} SELinux mode detected: ${enforceMode}"
+
+ # If it's enforcing,
if [[ "${enforceMode}" == "Enforcing" ]]; then
- whiptail --title "SELinux Enforcing Detected" --yesno "SELinux is being Enforced on your system!\n\nPi-hole currently does not support SELinux, but you may still continue with the installation.\n\nNote: Admin UI Will not function fully without setting your policies correctly\n\nContinue installing Pi-hole?" ${r} ${c} || \
- { echo ":::"; echo "::: Not continuing install after SELinux Enforcing detected."; exit 1; }
- echo ":::"
- echo "::: Continuing installation with SELinux Enforcing."
- echo "::: Please refer to official SELinux documentation to create a custom policy."
+ # Explain Pi-hole does not support it yet
+ whiptail --defaultno --title "SELinux Enforcing Detected" --yesno "SELinux is being ENFORCED on your system! \\n\\nPi-hole currently does not support SELinux, but you may still continue with the installation.\\n\\nNote: Web Admin will not be fully functional unless you set your policies correctly\\n\\nContinue installing Pi-hole?" ${r} ${c} || \
+ { echo -e "\\n ${COL_LIGHT_RED}SELinux Enforcing detected, exiting installer${COL_NC}"; exit 1; }
+ echo -e " ${INFO} Continuing installation with SELinux Enforcing
+ ${INFO} Please refer to official SELinux documentation to create a custom policy"
fi
fi
}
+# Installation complete message with instructions for the user
displayFinalMessage() {
-
- if [[ ${#1} -gt 0 ]] ; then
+ # If
+ if [[ "${#1}" -gt 0 ]] ; then
pwstring="$1"
+ # else, if the dashboard password in the setup variables exists,
elif [[ $(grep 'WEBPASSWORD' -c /etc/pihole/setupVars.conf) -gt 0 ]]; then
+ # set a variable for evaluation later
pwstring="unchanged"
else
+ # set a variable for evaluation later
pwstring="NOT SET"
fi
-
- if [[ ${INSTALL_WEB} == true ]]; then
+ # If the user wants to install the dashboard,
+ if [[ "${INSTALL_WEB}" == true ]]; then
+ # Store a message in a variable and display it
additional="View the web interface at http://pi.hole/admin or http://${IPV4_ADDRESS%/*}/admin
Your Admin Webpage login password is ${pwstring}"
@@ -1195,12 +1659,15 @@ ${additional}" ${r} ${c}
}
update_dialogs() {
- # reconfigure
- if [ "${reconfigure}" = true ]; then
+ # If pihole -r "reconfigure" option was selected,
+ if [[ "${reconfigure}" = true ]]; then
+ # set some variables that will be used
opt1a="Repair"
opt1b="This will retain existing settings"
strAdd="You will remain on the same version"
+ # Othweise,
else
+ # set some variables with different values
opt1a="Update"
opt1b="This will retain existing settings."
strAdd="You will be updated to the latest version."
@@ -1208,161 +1675,257 @@ update_dialogs() {
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 \
+ # Display the information to the user
+ 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) || \
- { echo "::: Cancel selected. Exiting"; exit 1; }
+ { echo -e " ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; }
+ # Set the variable based on if the user chooses
case ${UpdateCmd} in
+ # repair, or
${opt1a})
- echo "::: ${opt1a} option selected."
+ echo -e " ${INFO} ${opt1a} option selected"
useUpdateVars=true
;;
+ # reconfigure,
${opt2a})
- echo "::: ${opt2a} option selected"
+ echo -e " ${INFO} ${opt2a} option selected"
useUpdateVars=false
;;
esac
}
clone_or_update_repos() {
+ # If the user wants to reconfigure,
if [[ "${reconfigure}" == true ]]; then
- echo "::: --reconfigure passed to install script. Resetting changes to local repos"
+ echo " ${INFO} Performing reconfiguration, skipping download of local repos"
+ # Reset the Core repo
resetRepo ${PI_HOLE_LOCAL_REPO} || \
- { echo "!!! Unable to reset ${PI_HOLE_LOCAL_REPO}, unable to continue."; \
+ { echo -e " ${COL_LIGHT_RED}Unable to reset ${PI_HOLE_LOCAL_REPO}, exiting installer${COL_NC}"; \
exit 1; \
}
- if [[ ${INSTALL_WEB} == true ]]; then
+ # If the Web interface was installed,
+ if [[ "${INSTALL_WEB}" == true ]]; then
+ # reset it's repo
resetRepo ${webInterfaceDir} || \
- { echo "!!! Unable to reset ${webInterfaceDir}, unable to continue."; \
+ { echo -e " ${COL_LIGHT_RED}Unable to reset ${webInterfaceDir}, exiting installer${COL_NC}"; \
exit 1; \
}
fi
+ # Otherwise, a repair is happening
else
- # Get Git files for Core and Admin
+ # so get git files for Core
getGitFiles ${PI_HOLE_LOCAL_REPO} ${piholeGitUrl} || \
- { echo "!!! Unable to clone ${piholeGitUrl} into ${PI_HOLE_LOCAL_REPO}, unable to continue."; \
- exit 1; \
- }
-
- if [[ ${INSTALL_WEB} == true ]]; then
- getGitFiles ${webInterfaceDir} ${webInterfaceGitUrl} || \
- { echo "!!! Unable to clone ${webInterfaceGitUrl} into ${webInterfaceDir}, unable to continue."; \
+ { echo -e " ${COL_LIGHT_RED}Unable to clone ${piholeGitUrl} into ${PI_HOLE_LOCAL_REPO}, unable to continue${COL_NC}"; \
exit 1; \
}
- fi
+ # If the Web interface was installed,
+ if [[ "${INSTALL_WEB}" == true ]]; then
+ # get the Web git files
+ getGitFiles ${webInterfaceDir} ${webInterfaceGitUrl} || \
+ { echo -e " ${COL_LIGHT_RED}Unable to clone ${webInterfaceGitUrl} into ${webInterfaceDir}, exiting installer${COL_NC}"; \
+ exit 1; \
+ }
+ fi
fi
}
+# Download and install FTL binary
FTLinstall() {
- # Download and install FTL binary
+ # Local, named variables
local binary="${1}"
local latesttag
local orig_dir
- echo -n "::: Installing FTL... "
+ local str="Downloading and Installing FTL"
+ echo -ne " ${INFO} ${str}..."
+ # Get the current working directory
orig_dir="${PWD}"
+ # Find the latest version tag for FTL
latesttag=$(curl -sI https://github.com/pi-hole/FTL/releases/latest | grep "Location" | awk -F '/' '{print $NF}')
# Tags should always start with v, check for that.
if [[ ! "${latesttag}" == v* ]]; then
- echo "failed (error in getting latest release location from GitHub)"
+ echo -e "${OVER} ${CROSS} ${str}"
+ echo -e " ${COL_LIGHT_RED}Error: Unable to get latest release location from GitHub${COL_NC}"
return 1
fi
+
+ # If the download worked,
if curl -sSL --fail "https://github.com/pi-hole/FTL/releases/download/${latesttag%$'\r'}/${binary}" -o "/tmp/${binary}"; then
- # Get sha1 of the binary we just downloaded for verification.
+ # get sha1 of the binary we just downloaded for verification.
curl -sSL --fail "https://github.com/pi-hole/FTL/releases/download/${latesttag%$'\r'}/${binary}.sha1" -o "/tmp/${binary}.sha1"
- # Check if we just downloaded text, or a binary file.
+
+ # Move into the temp directory
cd /tmp
+ # If we downloaded binary file (as opposed to text),
if sha1sum --status --quiet -c "${binary}".sha1; then
echo -n "transferred... "
+ # Stop FTL
stop_service pihole-FTL &> /dev/null
+ # Install the new version with the correct permissions
install -T -m 0755 /tmp/${binary} /usr/bin/pihole-FTL
+ # Remove the tempoary file
rm /tmp/${binary} /tmp/${binary}.sha1
+ # Move back into the original directory the user was in
cd "${orig_dir}"
+ # Install the FTL service
install -T -m 0755 "${PI_HOLE_LOCAL_REPO}/advanced/pihole-FTL.service" "/etc/init.d/pihole-FTL"
- echo "done."
+ echo -e "${OVER} ${TICK} ${str}"
return 0
+ # Otherise,
else
- echo "failed (download of binary from Github failed)"
+ echo -e "${OVER} ${CROSS} ${str}"
+ echo -e " ${COL_LIGHT_RED}Error: Download of binary from Github failed${COL_NC}"
+ # the download failed, so just go back to the original directory
cd "${orig_dir}"
return 1
fi
+ # Otherwise,
else
cd "${orig_dir}"
- echo "failed (URL not found.)"
+ echo -e "${OVER} ${CROSS} ${str}"
+ # The URL could not be found
+ echo -e " ${COL_LIGHT_RED}Error: URL not found${COL_NC}"
fi
}
+# Detect suitable FTL binary platform
FTLdetect() {
- # Detect suitable FTL binary platform
- echo ":::"
- echo "::: Downloading latest version of FTL..."
+ echo ""
+ echo -e " ${INFO} FTL Checks..."
+ # Local, named variables
local machine
local binary
+ # Store architecture in a variable
machine=$(uname -m)
- if [[ $machine == arm* || $machine == *aarch* ]]; then
+ local str="Detecting architecture"
+ echo -ne " ${INFO} ${str}..."
+ # If the machine is arm or aarch
+ if [[ "${machine}" == "arm"* || "${machine}" == *"aarch"* ]]; then
# ARM
- local rev=$(uname -m | sed "s/[^0-9]//g;")
- local lib=$(ldd /bin/ls | grep -E '^\s*/lib' | awk '{ print $1 }')
- if [[ "$lib" == "/lib/ld-linux-aarch64.so.1" ]]; then
- echo "::: Detected ARM-aarch64 architecture"
+ #
+ local rev
+ rev=$(uname -m | sed "s/[^0-9]//g;")
+ #
+ local lib
+ lib=$(ldd /bin/ls | grep -E '^\s*/lib' | awk '{ print $1 }')
+ #
+ if [[ "${lib}" == "/lib/ld-linux-aarch64.so.1" ]]; then
+ echo -e "${OVER} ${TICK} Detected ARM-aarch64 architecture"
+ # set the binary to be used
binary="pihole-FTL-aarch64-linux-gnu"
- elif [[ "$lib" == "/lib/ld-linux-armhf.so.3" ]]; then
- if [ "$rev" -gt "6" ]; then
- echo "::: Detected ARM-hf architecture (armv7+)"
+ #
+ elif [[ "${lib}" == "/lib/ld-linux-armhf.so.3" ]]; then
+ #
+ if [[ "${rev}" -gt 6 ]]; then
+ echo -e "${OVER} ${TICK} Detected ARM-hf architecture (armv7+)"
+ # set the binary to be used
binary="pihole-FTL-arm-linux-gnueabihf"
+ # Otherwise,
else
- echo "::: Detected ARM-hf architecture (armv6 or lower)"
- echo "::: Using ARM binary"
+ echo -e "${OVER} ${TICK} Detected ARM-hf architecture (armv6 or lower) Using ARM binary"
+ # set the binary to be used
binary="pihole-FTL-arm-linux-gnueabi"
fi
else
- echo "::: Detected ARM architecture"
+ echo -e "${OVER} ${TICK} Detected ARM architecture"
+ # set the binary to be used
binary="pihole-FTL-arm-linux-gnueabi"
fi
- elif [[ $machine == x86_64 ]]; then
+ elif [[ "${machine}" == "ppc" ]]; then
+ # PowerPC
+ echo -e "${OVER} ${TICK} Detected PowerPC architecture"
+ # set the binary to be used
+ binary="pihole-FTL-powerpc-linux-gnu"
+ elif [[ "${machine}" == "x86_64" ]]; then
# 64bit
- echo "::: Detected x86_64 architecture"
+ echo -e "${OVER} ${TICK} Detected x86_64 architecture"
+ # set the binary to be used
binary="pihole-FTL-linux-x86_64"
else
# Something else - we try to use 32bit executable and warn the user
- if [[ ! $machine == i686 ]]; then
- echo "::: Not able to detect architecture (unknown: ${machine}), trying 32bit executable"
- echo "::: Contact Pi-hole support if you experience problems (like FTL not running)"
+ if [[ ! "${machine}" == "i686" ]]; then
+ echo -e "${OVER} ${CROSS} ${str}...
+ ${COL_LIGHT_RED}Not able to detect architecture (unknown: ${machine}), trying 32bit executable${COL_NC}
+ Contact Pi-hole Support if you experience issues (e.g: FTL not running)"
else
- echo "::: Detected 32bit (i686) architecture"
+ echo -e "${OVER} ${TICK} Detected 32bit (i686) architecture"
fi
binary="pihole-FTL-linux-x86_32"
fi
- FTLinstall "${binary}" || return 1
+ #In the next section we check to see if FTL is already installed (in case of pihole -r).
+ #If the installed version matches the latest version, then check the installed sha1sum of the binary vs the remote sha1sum. If they do not match, then download
+ echo -e " ${INFO} Checking for existing FTL binary..."
+
+ local ftlLoc=$(which pihole-FTL 2>/dev/null)
+
+ if [[ ${ftlLoc} ]]; then
+ local FTLversion=$(/usr/bin/pihole-FTL tag)
+ local FTLlatesttag=$(curl -sI https://github.com/pi-hole/FTL/releases/latest | grep 'Location' | awk -F '/' '{print $NF}' | tr -d '\r\n')
+
+ if [[ "${FTLversion}" != "${FTLlatesttag}" ]]; then
+ # Install FTL
+ FTLinstall "${binary}" || return 1
+ else
+ echo -e " ${INFO} Latest FTL Binary already installed (${FTLlatesttag}). Confirming Checksum..."
+
+ local remoteSha1=$(curl -sSL --fail "https://github.com/pi-hole/FTL/releases/download/${FTLversion%$'\r'}/${binary}.sha1" | cut -d ' ' -f 1)
+ local localSha1=$(sha1sum "$(which pihole-FTL)" | cut -d ' ' -f 1)
+
+ if [[ "${remoteSha1}" != "${localSha1}" ]]; then
+ echo -e " ${INFO} Corruption detected..."
+ FTLinstall "${binary}" || return 1
+ else
+ echo -e " ${INFO} Checksum correct. No need to download!"
+ fi
+ fi
+ else
+ # Install FTL
+ FTLinstall "${binary}" || return 1
+ fi
+
}
main() {
-
######## FIRST CHECK ########
- # Must be root to install
+ # Show the Pi-hole logo so people know it's genuine since the logo and name are trademarked
show_ascii_berry
- echo ":::"
- if [[ ${EUID} -eq 0 ]]; then
- echo "::: You are root."
+ # Must be root to install
+ local str="Root user check"
+ echo ""
+
+ # If the user's id is zero,
+ if [[ "${EUID}" -eq 0 ]]; then
+ # they are root and all is good
+ echo -e " ${TICK} ${str}"
+ # Otherwise,
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..."
-
+ # They do not have enough privileges, so let the user know
+ echo -e " ${CROSS} ${str}
+ ${COL_LIGHT_RED}Script called with non-root privileges${COL_NC}
+ The Pi-hole requires elevated privleges to install and run
+ Please check the installer for any concerns regarding this requirement
+ Make sure to download this script from a trusted source\\n"
+ echo -ne " ${INFO} Sudo utility check"
+
+ # If the sudo command exists,
if command -v sudo &> /dev/null; then
- echo "::: Utility sudo located."
+ echo -e "${OVER} ${TICK} Sudo utility check"
+ # Download the install script and run it with admin rights
exec curl -sSL https://raw.githubusercontent.com/pi-hole/pi-hole/master/automated%20install/basic-install.sh | sudo bash "$@"
exit $?
+ # Otherwise,
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."
+ # Let them know they need to run it as root
+ echo -e "${OVER} ${CROSS} Sudo utility check
+ Sudo is needed for the Web Interface to run pihole commands\\n
+ ${COL_LIGHT_RED}Please re-run this installer as root${COL_NC}"
exit 1
fi
fi
@@ -1373,17 +1936,22 @@ 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;;
+ "--reconfigure" ) reconfigure=true;;
+ "--i_do_not_follow_recommendations" ) skipSpaceCheck=false;;
+ "--unattended" ) runUnattended=true;;
esac
done
- if [[ -f ${setupVars} ]]; then
+ # If the setup variable file exists,
+ if [[ -f "${setupVars}" ]]; then
+ # if it's running unattended,
if [[ "${runUnattended}" == true ]]; then
- echo "::: --unattended passed to install script, no whiptail dialogs will be displayed"
+ echo -e " ${INFO} Performing unattended setup, no whiptail dialogs will be displayed"
+ # Use the setup variables
useUpdateVars=true
+ # Otherwise,
else
+ # show the available options (repair/reconfigure)
update_dialogs
fi
fi
@@ -1391,7 +1959,7 @@ main() {
# 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!"
+ echo -e " ${INFO} Skipping free disk space verification"
else
verifyFreeDiskSpace
fi
@@ -1408,15 +1976,14 @@ main() {
# Check if SELinux is Enforcing
checkSelinux
-
- if [[ ${useUpdateVars} == false ]]; then
+ if [[ "${useUpdateVars}" == false ]]; then
# Display welcome dialogs
welcomeDialogs
# Create directory for Pi-hole storage
mkdir -p /etc/pihole/
stop_service dnsmasq
- if [[ ${INSTALL_WEB} == true ]]; then
+ if [[ "${INSTALL_WEB}" == true ]]; then
stop_service lighttpd
fi
# Determine available interfaces
@@ -1434,14 +2001,31 @@ main() {
# Clone/Update the repos
clone_or_update_repos
- # Install packages used by the Pi-hole
- if [[ ${INSTALL_WEB} == true ]]; then
+ # Install packages used by the Pi-hole
+ if [[ "${INSTALL_WEB}" == true ]]; then
+ # Install the Web dependencies
DEPS=("${PIHOLE_DEPS[@]}" "${PIHOLE_WEB_DEPS[@]}")
+ # Otherwise,
else
+ # just install the Core dependencies
DEPS=("${PIHOLE_DEPS[@]}")
fi
+
install_dependent_packages DEPS[@]
+ # On some systems, lighttpd is not enabled on first install. We need to enable it here if the user
+ # has chosen to install the web interface, else the `LIGHTTPD_ENABLED` check will fail
+ if [[ "${INSTALL_WEB}" == true ]]; then
+ enable_service lighttpd
+ fi
+
+ if [[ -x "$(command -v systemctl)" ]]; then
+ # Value will either be 1, if true, or 0
+ LIGHTTPD_ENABLED=$(systemctl is-enabled lighttpd | grep -c 'enabled' || true)
+ else
+ # Value will either be 1, if true, or 0
+ LIGHTTPD_ENABLED=$(service lighttpd status | awk '/Loaded:/ {print $0}' | grep -c 'enabled' || true)
+ fi
# Install and log everything to a file
installPihole | tee ${tmpLog}
@@ -1449,84 +2033,114 @@ main() {
# Clone/Update the repos
clone_or_update_repos
- # Source ${setupVars} for use in the rest of the functions.
+ # Source ${setupVars} for use in the rest of the functions
source ${setupVars}
# Install packages used by the Pi-hole
- if [[ ${INSTALL_WEB} == true ]]; then
+ if [[ "${INSTALL_WEB}" == true ]]; then
+ # Install the Web dependencies
DEPS=("${PIHOLE_DEPS[@]}" "${PIHOLE_WEB_DEPS[@]}")
+ # Otherwise,
else
+ # just install the Core dependencies
DEPS=("${PIHOLE_DEPS[@]}")
fi
install_dependent_packages DEPS[@]
+ if [[ -x "$(command -v systemctl)" ]]; then
+ # Value will either be 1, if true, or 0
+ LIGHTTPD_ENABLED=$(systemctl is-enabled lighttpd | grep -c 'enabled' || true)
+ else
+ # Value will either be 1, if true, or 0
+ LIGHTTPD_ENABLED=$(service lighttpd status | awk '/Loaded:/ {print $0}' | grep -c 'enabled' || true)
+ fi
+
updatePihole | tee ${tmpLog}
fi
# Move the log file into /etc/pihole for storage
mv ${tmpLog} ${instalLogLoc}
- if [[ ${INSTALL_WEB} == true ]]; then
+ if [[ "${INSTALL_WEB}" == true ]]; then
# Add password to web UI if there is none
pw=""
+ # If no password is set,
if [[ $(grep 'WEBPASSWORD' -c /etc/pihole/setupVars.conf) == 0 ]] ; then
+ # generate a random password
pw=$(tr -dc _A-Z-a-z-0-9 < /dev/urandom | head -c 8)
+ # shellcheck disable=SC1091
. /opt/pihole/webpage.sh
echo "WEBPASSWORD=$(HashPassword ${pw})" >> ${setupVars}
fi
fi
- echo "::: Restarting services..."
+ echo -e " ${INFO} Restarting services..."
# Start services
start_service dnsmasq
enable_service dnsmasq
- if [[ ${INSTALL_WEB} == true ]]; then
- start_service lighttpd
- enable_service lighttpd
- fi
+ # If the Web server was installed,
+ if [[ "${INSTALL_WEB}" == true ]]; then
- runGravity
+ if [[ "${LIGHTTPD_ENABLED}" == "1" ]]; then
+ start_service lighttpd
+ enable_service lighttpd
+ else
+ echo -e " ${INFO} Lighttpd is disabled, skipping service restart"
+ fi
+ fi
+ # Enable FTL
start_service pihole-FTL
enable_service pihole-FTL
- echo "::: done."
+ # Download and compile the aggregated block list
+ runGravity
+ # Force an update of the updatechecker
+ . /opt/pihole/updatecheck.sh
+
+ #
if [[ "${useUpdateVars}" == false ]]; then
displayFinalMessage "${pw}"
fi
- 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."
- if [[ ${INSTALL_WEB} == true ]]; then
- echo "::: View the web interface at http://pi.hole/admin or http://${IPV4_ADDRESS%/*}/admin"
+ # If the Web interface was installed,
+ if [[ "${INSTALL_WEB}" == true ]]; then
+ # If there is a password,
+ if (( ${#pw} > 0 )) ; then
+ # display the password
+ echo -e " ${INFO} Web Interface password: ${COL_LIGHT_GREEN}${pw}${COL_NC}
+ This can be changed using 'pihole -a -p'\\n"
fi
- else
- echo "::: Update complete!"
fi
- if [[ ${INSTALL_WEB} == true ]]; then
- 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"
+
+ #
+ if [[ "${useUpdateVars}" == false ]]; then
+ # If the Web interface was installed,
+ if [[ "${INSTALL_WEB}" == true ]]; then
+ echo -e " View the web interface at http://pi.hole/admin or http://${IPV4_ADDRESS%/*}/admin"
+ echo ""
fi
+ # Explain to the user how to use Pi-hole as their DNS server
+ echo " You may now configure your devices to use the Pi-hole as their DNS server"
+ [[ -n "${IPV4_ADDRESS%/*}" ]] && echo -e " ${INFO} Pi-hole DNS (IPv4): ${IPV4_ADDRESS%/*}"
+ [[ -n "${IPV6_ADDRESS}" ]] && echo -e " ${INFO} Pi-hole DNS (IPv6): ${IPV6_ADDRESS}"
+ echo -e " If you set a new IP address, please restart the server running the Pi-hole"
+ #
+ INSTALL_TYPE="Installation"
+ else
+ #
+ INSTALL_TYPE="Update"
fi
- echo ":::"
- echo "::: The install log is located at: /etc/pihole/install.log
- "
+ # Display where the log file is
+ echo -e "\\n ${INFO} The install log is located at: /etc/pihole/install.log
+ ${COL_LIGHT_GREEN}${INSTALL_TYPE} Complete! ${COL_NC}"
+
}
+#
if [[ "${PH_TEST}" != true ]] ; then
main "$@"
fi
diff --git a/automated install/uninstall.sh b/automated install/uninstall.sh
index ed768c38..2f4f4f9f 100755
--- a/automated install/uninstall.sh
+++ b/automated install/uninstall.sh
@@ -8,33 +8,57 @@
# This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license.
+source "/opt/pihole/COL_TABLE"
+while true; do
+ read -rp " ${QST} Are you sure you would like to remove ${COL_WHITE}Pi-hole${COL_NC}? [y/N] " yn
+ case ${yn} in
+ [Yy]* ) break;;
+ [Nn]* ) echo -e "\n ${COL_LIGHT_GREEN}Uninstall has been cancelled${COL_NC}"; exit 0;;
+ * ) echo -e "\n ${COL_LIGHT_GREEN}Uninstall has been cancelled${COL_NC}"; exit 0;;
+ esac
+done
# Must be root to uninstall
+str="Root user check"
if [[ ${EUID} -eq 0 ]]; then
- echo "::: You are root."
+ echo -e " ${TICK} ${str}"
else
- echo "::: Sudo will be used for the uninstall."
- # Check if it is actually installed
- # If it isn't, exit because the unnstall cannot complete
+ # Check if sudo is actually installed
+ # If it isn't, exit because the uninstall can not complete
if [ -x "$(command -v sudo)" ]; then
export SUDO="sudo"
else
- echo "::: Please install sudo or run this as root."
+ echo -e " ${CROSS} ${str}
+ Script called with non-root privileges
+ The Pi-hole requires elevated privleges to uninstall"
exit 1
fi
fi
+readonly PI_HOLE_FILES_DIR="/etc/.pihole"
+PH_TEST="true"
+source "${PI_HOLE_FILES_DIR}/automated install/basic-install.sh"
+# setupVars set in basic-install.sh
+source "${setupVars}"
+
+# distro_check() sourced from basic-install.sh
+distro_check
+
+# Install packages used by the Pi-hole
+if [[ "${INSTALL_WEB}" == true ]]; then
+ # Install the Web dependencies
+ DEPS=("${INSTALLER_DEPS[@]}" "${PIHOLE_DEPS[@]}" "${PIHOLE_WEB_DEPS[@]}")
+# Otherwise,
+else
+ # just install the Core dependencies
+ DEPS=("${INSTALLER_DEPS[@]}" "${PIHOLE_DEPS[@]}")
+fi
+
# Compatability
if [ -x "$(command -v rpm)" ]; then
# Fedora Family
- if [ -x "$(command -v dnf)" ]; then
- PKG_MANAGER="dnf"
- else
- PKG_MANAGER="yum"
- fi
PKG_REMOVE="${PKG_MANAGER} remove -y"
- PIHOLE_DEPS=( bind-utils bc dnsmasq lighttpd lighttpd-fastcgi php-common git curl unzip wget findutils )
package_check() {
rpm -qa | grep ^$1- > /dev/null
}
@@ -43,9 +67,7 @@ if [ -x "$(command -v rpm)" ]; then
}
elif [ -x "$(command -v apt-get)" ]; then
# Debian Family
- PKG_MANAGER="apt-get"
PKG_REMOVE="${PKG_MANAGER} -y remove --purge"
- PIHOLE_DEPS=( dnsutils bc dnsmasq lighttpd php5-common git curl unzip wget )
package_check() {
dpkg-query -W -f='${Status}' "$1" 2>/dev/null | grep -c "ok installed"
}
@@ -54,62 +76,51 @@ elif [ -x "$(command -v apt-get)" ]; then
${SUDO} ${PKG_MANAGER} -y autoclean
}
else
- echo "OS distribution not supported"
- exit
+ echo -e " ${CROSS} OS distribution not supported"
+ exit 1
fi
-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"
-}
-
removeAndPurge() {
# Purge dependencies
- echo ":::"
- for i in "${PIHOLE_DEPS[@]}"; do
+ echo ""
+ for i in "${DEPS[@]}"; do
package_check ${i} > /dev/null
- if [ $? -eq 0 ]; then
+ if [[ "$?" -eq 0 ]]; then
while true; do
- read -rp "::: Do you wish to remove ${i} from your system? [y/n]: " yn
+ read -rp " ${QST} Do you wish to remove ${COL_WHITE}${i}${COL_NC} 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\n" "${i}"; break;;
- * ) printf "::: You must answer yes or no!\n";;
+ [Yy]* )
+ echo -ne " ${INFO} Removing ${i}...";
+ ${SUDO} ${PKG_REMOVE} "${i}" &> /dev/null;
+ echo -e "${OVER} ${INFO} Removed ${i}";
+ break;;
+ [Nn]* ) echo -e " ${INFO} Skipped ${i}"; break;;
esac
done
else
- printf ":::\tPackage %s not installed... Not removing.\n" "${i}"
+ echo -e " ${INFO} Package ${i} not installed"
fi
done
- # Remove dependency config files
- echo "::: Removing dnsmasq config files..."
- ${SUDO} rm /etc/dnsmasq.conf /etc/dnsmasq.conf.orig /etc/dnsmasq.d/01-pihole.conf &> /dev/null
+ # Remove dnsmasq config files
+ ${SUDO} rm -f /etc/dnsmasq.conf /etc/dnsmasq.conf.orig /etc/dnsmasq.d/01-pihole.conf &> /dev/null
+ echo -e " ${TICK} Removing dnsmasq config files"
# Take care of any additional package cleaning
- printf "::: Auto removing & cleaning remaining dependencies..."
- package_cleanup &> /dev/null & spinner $!; printf "done!\n";
+ echo -ne " ${INFO} Removing & cleaning remaining dependencies..."
+ package_cleanup &> /dev/null
+ echo -e "${OVER} ${TICK} Removed & cleaned up remaining dependencies"
- # Call removeNoPurge to remove PiHole specific files
+ # Call removeNoPurge to remove Pi-hole specific files
removeNoPurge
}
removeNoPurge() {
- echo ":::"
- # Only web directories/files that are created by pihole should be removed.
- echo "::: Removing the Pi-hole Web server files..."
+ # Only web directories/files that are created by Pi-hole should be removed
+ echo -ne " ${INFO} Removing Web Interface..."
${SUDO} rm -rf /var/www/html/admin &> /dev/null
${SUDO} rm -rf /var/www/html/pihole &> /dev/null
- ${SUDO} rm /var/www/html/index.lighttpd.orig &> /dev/null
+ ${SUDO} rm -f /var/www/html/index.lighttpd.orig &> /dev/null
# If the web directory is empty after removing these files, then the parent html folder can be removed.
if [ -d "/var/www/html" ]; then
@@ -117,65 +128,96 @@ removeNoPurge() {
${SUDO} rm -rf /var/www/html &> /dev/null
fi
fi
+ echo -e "${OVER} ${TICK} Removed Web Interface"
# Attempt to preserve backwards compatibility with older versions
# to guarantee no additional changes were made to /etc/crontab after
# the installation of pihole, /etc/crontab.pihole should be permanently
# preserved.
if [[ -f /etc/crontab.orig ]]; then
- echo "::: Initial Pi-hole cron detected. Restoring the default system cron..."
${SUDO} mv /etc/crontab /etc/crontab.pihole
${SUDO} mv /etc/crontab.orig /etc/crontab
${SUDO} service cron restart
+ echo -e " ${TICK} Restored the default system cron"
fi
# Attempt to preserve backwards compatibility with older versions
if [[ -f /etc/cron.d/pihole ]];then
- echo "::: Removing cron.d/pihole..."
- ${SUDO} rm /etc/cron.d/pihole &> /dev/null
+ ${SUDO} rm -f /etc/cron.d/pihole &> /dev/null
+ echo -e " ${TICK} Removed /etc/cron.d/pihole"
fi
- echo "::: Removing config files and scripts..."
package_check lighttpd > /dev/null
- if [ $? -eq 1 ]; then
+ if [[ $? -eq 1 ]]; then
${SUDO} rm -rf /etc/lighttpd/ &> /dev/null
+ echo -e " ${TICK} Removed lighttpd"
else
if [ -f /etc/lighttpd/lighttpd.conf.orig ]; then
${SUDO} mv /etc/lighttpd/lighttpd.conf.orig /etc/lighttpd/lighttpd.conf
fi
fi
- ${SUDO} rm /etc/dnsmasq.d/adList.conf &> /dev/null
- ${SUDO} rm /etc/dnsmasq.d/01-pihole.conf &> /dev/null
+ ${SUDO} rm -f /etc/dnsmasq.d/adList.conf &> /dev/null
+ ${SUDO} rm -f /etc/dnsmasq.d/01-pihole.conf &> /dev/null
${SUDO} rm -rf /var/log/*pihole* &> /dev/null
${SUDO} rm -rf /etc/pihole/ &> /dev/null
${SUDO} rm -rf /etc/.pihole/ &> /dev/null
${SUDO} rm -rf /opt/pihole/ &> /dev/null
- ${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
+ ${SUDO} rm -f /usr/local/bin/pihole &> /dev/null
+ ${SUDO} rm -f /etc/bash_completion.d/pihole &> /dev/null
+ ${SUDO} rm -f /etc/sudoers.d/pihole &> /dev/null
+ echo -e " ${TICK} Removed config files"
+
+ # Remove FTL
+ if command -v pihole-FTL &> /dev/null; then
+ echo -ne " ${INFO} Removing pihole-FTL..."
+
+ if [[ -x "$(command -v systemctl)" ]]; then
+ systemctl stop pihole-FTL
+ else
+ service pihole-FTL stop
+ fi
+
+ ${SUDO} rm -f /etc/init.d/pihole-FTL
+ ${SUDO} rm -f /usr/bin/pihole-FTL
+ echo -e "${OVER} ${TICK} Removed pihole-FTL"
+ fi
# If the pihole user exists, then remove
- if id "pihole" >/dev/null 2>&1; then
- echo "::: Removing pihole user..."
- ${SUDO} userdel -r pihole
+ if id "pihole" &> /dev/null; then
+ ${SUDO} userdel -r pihole 2> /dev/null
+ if [[ "$?" -eq 0 ]]; then
+ echo -e " ${TICK} Removed 'pihole' user"
+ else
+ echo -e " ${CROSS} Unable to remove 'pihole' user"
+ fi
fi
- echo ":::"
- printf "::: Finished removing PiHole from your system. Sorry to see you go!\n"
- printf "::: Reach out to us at https://github.com/pi-hole/pi-hole/issues if you need help\n"
- printf "::: Reinstall by simpling running\n:::\n:::\tcurl -sSL https://install.pi-hole.net | bash\n:::\n::: at any time!\n:::\n"
- printf "::: PLEASE RESET YOUR DNS ON YOUR ROUTER/CLIENTS TO RESTORE INTERNET CONNECTIVITY!\n"
+ echo -e "\n We're sorry to see you go, but thanks for checking out Pi-hole!
+ If you need help, reach out to us on Github, Discourse, Reddit or Twitter
+ Reinstall at any time: ${COL_WHITE}curl -sSL https://install.pi-hole.net | bash${COL_NC}
+
+ ${COL_LIGHT_RED}Please reset the DNS on your router/clients to restore internet connectivity
+ ${COL_LIGHT_GREEN}Uninstallation Complete! ${COL_NC}"
}
######### SCRIPT ###########
-echo "::: Preparing to remove packages, be sure that each may be safely removed depending on your operating system."
-echo "::: (SAFE TO REMOVE ALL ON RASPBIAN)"
+if command -v vcgencmd &> /dev/null; then
+ echo -e " ${INFO} All dependencies are safe to remove on Raspbian"
+else
+ echo -e " ${INFO} Be sure to confirm if any dependencies should not be removed"
+fi
while true; do
- read -rp "::: Do you wish to purge PiHole's dependencies from your OS? (You will be prompted for each package) [y/n]: " yn
+ echo -e " ${INFO} ${COL_YELLOW}The following dependencies may have been added by the Pi-hole install:"
+ echo -n " "
+ for i in "${DEPS[@]}"; do
+ echo -n "${i} "
+ done
+ echo "${COL_NC}"
+ read -rp " ${QST} Do you wish to go through each dependency for removal? (Choosing No will leave all dependencies installed) [Y/n] " yn
case ${yn} in
[Yy]* ) removeAndPurge; break;;
-
[Nn]* ) removeNoPurge; break;;
+ * ) removeAndPurge; break;;
esac
done
diff --git a/gravity.sh b/gravity.sh
index 41e3c68a..f4b5fc36 100755
--- a/gravity.sh
+++ b/gravity.sh
@@ -1,459 +1,665 @@
#!/usr/bin/env bash
+# shellcheck disable=SC1090
+
# Pi-hole: A black hole for Internet advertisements
# (c) 2017 Pi-hole, LLC (https://pi-hole.net)
# Network-wide ad blocking via your own hardware.
#
+# Usage: "pihole -g"
# Compiles a list of ad-serving domains by downloading them from multiple sources
#
# This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license.
+coltable="/opt/pihole/COL_TABLE"
+source "${coltable}"
+basename="pihole"
+PIHOLE_COMMAND="/usr/local/bin/${basename}"
-# Run this script as root or under sudo
-echo ":::"
+piholeDir="/etc/${basename}"
+piholeRepo="/etc/.${basename}"
-helpFunc() {
- cat << EOM
-::: Pull in domains from adlists
-:::
-::: Usage: pihole -g
-:::
-::: Options:
-::: -f, --force Force lists to be downloaded, even if they don't need updating.
-::: -h, --help Show this help dialog
-EOM
- exit 0
-}
+adListFile="${piholeDir}/adlists.list"
+adListDefault="${piholeDir}/adlists.default"
+adListRepoDefault="${piholeRepo}/adlists.default"
+
+whitelistFile="${piholeDir}/whitelist.txt"
+blacklistFile="${piholeDir}/blacklist.txt"
+wildcardFile="/etc/dnsmasq.d/03-pihole-wildcard.conf"
+
+adList="${piholeDir}/gravity.list"
+blackList="${piholeDir}/black.list"
+localList="${piholeDir}/local.list"
+VPNList="/etc/openvpn/ipp.txt"
+
+domainsExtension="domains"
+matterAndLight="${basename}.0.matterandlight.txt"
+parsedMatter="${basename}.1.parsedmatter.txt"
+whitelistMatter="${basename}.2.whitelistmatter.txt"
+accretionDisc="${basename}.3.accretionDisc.txt"
+preEventHorizon="list.preEventHorizon"
+
+skipDownload="false"
-PIHOLE_COMMAND="/usr/local/bin/pihole"
+# Source setupVars from install script
+setupVars="${piholeDir}/setupVars.conf"
+if [[ -f "${setupVars}" ]];then
+ source "${setupVars}"
-adListFile=/etc/pihole/adlists.list
-adListDefault=/etc/pihole/adlists.default #being deprecated
-adListRepoDefault=/etc/.pihole/adlists.default
-whitelistScript="${PIHOLE_COMMAND} -w"
-whitelistFile=/etc/pihole/whitelist.txt
-blacklistFile=/etc/pihole/blacklist.txt
-readonly wildcardlist="/etc/dnsmasq.d/03-pihole-wildcard.conf"
+ # Remove CIDR mask from IPv4/6 addresses
+ IPV4_ADDRESS="${IPV4_ADDRESS%/*}"
+ IPV6_ADDRESS="${IPV6_ADDRESS%/*}"
-#Source the setupVars from install script for the IP
-setupVars=/etc/pihole/setupVars.conf
-if [[ -f ${setupVars} ]];then
- . /etc/pihole/setupVars.conf
+ # Determine if IPv4/6 addresses exist
+ if [[ -z "${IPV4_ADDRESS}" ]] && [[ -z "${IPV6_ADDRESS}" ]]; then
+ echo -e " ${COL_LIGHT_RED}No IP addresses found! Please run 'pihole -r' to reconfigure${COL_NC}"
+ exit 1
+ fi
else
- echo "::: WARNING: /etc/pihole/setupVars.conf missing. Possible installation failure."
- echo "::: Please run 'pihole -r', and choose the 'reconfigure' option to reconfigure."
- exit 1
+ echo -e " ${COL_LIGHT_RED}Installation Failure: ${setupVars} does not exist! ${COL_NC}
+ Please run 'pihole -r', and choose the 'reconfigure' option to fix."
+ exit 1
fi
-#Remove the /* from the end of the IP addresses
-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
-blackList=${piholeDir}/black.list
-localList=${piholeDir}/local.list
-justDomainsExtension=domains
-matterAndLight=${basename}.0.matterandlight.txt
-supernova=${basename}.1.supernova.txt
-preEventHorizon=list.preEventHorizon
-eventHorizon=${basename}.2.supernova.txt
-accretionDisc=${basename}.3.accretionDisc.txt
-
-skipDownload=false
-
-# Warn users still using pihole.conf that it no longer has any effect (I imagine about 2 people use it)
-if [[ -r ${piholeDir}/pihole.conf ]]; then
- echo "::: pihole.conf file no longer supported. Over-rides in this file are ignored."
+# Determine if superseded pihole.conf exists
+if [[ -r "${piholeDir}/pihole.conf" ]]; then
+ echo -e " ${COL_LIGHT_RED}Ignoring overrides specified within pihole.conf! ${COL_NC}"
fi
-###########################
-# collapse - begin formation of pihole
-gravity_collapse() {
+# Determine if DNS resolution is available before proceeding
+gravity_DNSLookup() {
+ local lookupDomain="pi.hole" plural=""
- #New Logic:
- # Does /etc/pihole/adlists.list exist? If so leave it alone
- # If not, cp /etc/.pihole/adlists.default /etc/pihole/adlists.list
- # Read from adlists.list
+ # Determine if $localList does not exist
+ if [[ ! -e "${localList}" ]]; then
+ lookupDomain="raw.githubusercontent.com"
+ fi
- #The following two blocks will sort out any missing adlists in the /etc/pihole directory, and remove legacy adlists.default
- if [ -f ${adListDefault} ] && [ -f ${adListFile} ]; then
- rm ${adListDefault}
+ # Determine if $lookupDomain is resolvable
+ if timeout 1 getent hosts "${lookupDomain}" &> /dev/null; then
+ # Print confirmation of resolvability if it had previously failed
+ if [[ -n "${secs:-}" ]]; then
+ echo -e "${OVER} ${TICK} DNS resolution is now available\\n"
+ fi
+ return 0
+ elif [[ -n "${secs:-}" ]]; then
+ echo -e "${OVER} ${CROSS} DNS resolution is not available"
+ exit 1
fi
- if [ ! -f ${adListFile} ]; then
- cp ${adListRepoDefault} ${adListFile}
+ # Determine error output message
+ if pidof dnsmasq &> /dev/null; then
+ echo -e " ${CROSS} DNS resolution is currently unavailable"
+ else
+ echo -e " ${CROSS} DNS service is not running"
+ "${PIHOLE_COMMAND}" restartdns
fi
- echo "::: Neutrino emissions detected..."
- echo ":::"
- echo -n "::: Pulling source lists into range..."
- sources=()
- while IFS= read -r line || [[ -n "$line" ]]; do
- #Do not read commented out or blank lines
- if [[ ${line} = \#* ]] || [[ ! ${line} ]]; then
- echo "" > /dev/null
- else
- sources+=(${line})
- fi
- done < ${adListFile}
- echo " done!"
+ # Ensure DNS server is given time to be resolvable
+ secs="120"
+ echo -ne " ${INFO} Waiting up to ${secs} seconds before continuing..."
+ until timeout 1 getent hosts "${lookupDomain}" &> /dev/null; do
+ [[ "${secs:-}" -eq 0 ]] && break
+ [[ "${secs:-}" -ne 1 ]] && plural="s"
+ echo -ne "${OVER} ${INFO} Waiting up to ${secs} second${plural} before continuing..."
+ : $((secs--))
+ sleep 1
+ done
+
+ # Try again
+ gravity_DNSLookup
}
-# patternCheck - check to see if curl downloaded any new files.
-gravity_patternCheck() {
- patternBuffer=$1
- success=$2
- error=$3
- if [ $success = true ]; then
- # check if download was successful but list has not been modified
- if [ "${error}" == "304" ]; then
- echo "::: No changes detected, transport skipped!"
- # check if the patternbuffer is a non-zero length file
- elif [[ -s "${patternBuffer}" ]]; then
- # Some of the blocklists are copyright, they need to be downloaded
- # and stored as is. They can be processed for content after they
- # have been saved.
- mv "${patternBuffer}" "${saveLocation}"
- echo "::: List updated, transport successful!"
- else
- # Empty file -> use previously downloaded list
- echo "::: Received empty file, using cached one (list not updated!)"
- fi
- else
- # check if cached list exists
- if [[ -r "${saveLocation}" ]]; then
- echo "::: List download failed, using cached list (list not updated!)"
- else
- echo "::: Download failed and no cached list available (list will not be considered)"
- fi
- fi
-}
+# Retrieve blocklist URLs and parse domains from adlists.list
+gravity_Collapse() {
+ echo -e " ${INFO} ${COL_BOLD}Neutrino emissions detected${COL_NC}..."
+
+ # Determine if adlists file needs handling
+ if [[ ! -f "${adListFile}" ]]; then
+ # Create "adlists.list" by copying "adlists.default" from internal core repo
+ cp "${adListRepoDefault}" "${adListFile}" 2> /dev/null || \
+ echo -e " ${CROSS} Unable to copy ${adListFile##*/} from ${piholeRepo}"
+ elif [[ -f "${adListDefault}" ]] && [[ -f "${adListFile}" ]]; then
+ # Remove superceded $adListDefault file
+ rm "${adListDefault}" 2> /dev/null || \
+ echo -e " ${CROSS} Unable to remove ${adListDefault}"
+ fi
-# transport - curl the specified url with any needed command extentions
-gravity_transport() {
- url=$1
- cmd_ext=$2
- agent=$3
-
- # tmp file, so we don't have to store the (long!) lists in RAM
- patternBuffer=$(mktemp)
- heisenbergCompensator=""
- if [[ -r ${saveLocation} ]]; then
- # if domain has been saved, add file for date check to only download newer
- heisenbergCompensator="-z ${saveLocation}"
- fi
-
- # Silently curl url
- err=$(curl -s -L ${cmd_ext} ${heisenbergCompensator} -w %{http_code} -A "${agent}" ${url} -o ${patternBuffer})
-
- echo " done"
- # Analyze http response
- echo -n "::: Status: "
- case "$err" in
- "200" ) echo "Success (OK)"; success=true;;
- "304" ) echo "Not modified"; success=true;;
- "403" ) echo "Forbidden"; success=false;;
- "404" ) echo "Not found"; success=false;;
- "408" ) echo "Time-out"; success=false;;
- "451" ) echo "Unavailable For Legal Reasons"; success=false;;
- "521" ) echo "Web Server Is Down (Cloudflare)"; success=false;;
- "522" ) echo "Connection Timed Out (Cloudflare)"; success=false;;
- "500" ) echo "Internal Server Error"; success=false;;
- * ) echo "Status $err"; success=false;;
- esac
-
- # Process result
- gravity_patternCheck "${patternBuffer}" ${success} "${err}"
-
- # Delete temp file if it hasn't been moved
- if [[ -f "${patternBuffer}" ]]; then
- rm "${patternBuffer}"
- fi
+ local str="Pulling blocklist source list into range"
+ echo -ne " ${INFO} ${str}..."
+
+ # Retrieve source URLs from $adListFile
+ # Logic: Remove comments and empty lines
+ mapfile -t sources <<< "$(grep -v -E "^(#|$)" "${adListFile}" 2> /dev/null)"
+
+ # Parse source domains from $sources
+ mapfile -t sourceDomains <<< "$(
+ # Logic: Split by folder/port
+ awk -F '[/:]' '{
+ # Remove URL protocol & optional username:password@
+ gsub(/(.*:\/\/|.*:.*@)/, "", $0)
+ print $1
+ }' <<< "$(printf '%s\n' "${sources[@]}")" 2> /dev/null
+ )"
+
+ if [[ -n "${sources[*]}" ]] && [[ -n "${sourceDomains[*]}" ]]; then
+ echo -e "${OVER} ${TICK} ${str}"
+ else
+ echo -e "${OVER} ${CROSS} ${str}"
+ gravity_Cleanup "error"
+ fi
}
-# spinup - main gravity function
-gravity_spinup() {
- echo ":::"
- # Loop through domain list. Download each one and remove commented lines (lines beginning with '# 'or '/') and # blank lines
- for ((i = 0; i < "${#sources[@]}"; i++)); do
- url=${sources[$i]}
- # Get just the domain from the URL
- domain=$(echo "${url}" | cut -d'/' -f3)
-
- # Save the file as list.#.domain
- saveLocation=${piholeDir}/list.${i}.${domain}.${justDomainsExtension}
- activeDomains[$i]=${saveLocation}
-
- agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36"
-
- # Use a case statement to download lists that need special cURL commands
- # to complete properly and reset the user agent when required
- case "${domain}" in
- "adblock.mahakala.is")
- agent='Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36'
- cmd_ext="-e http://forum.xda-developers.com/"
- ;;
-
- "adaway.org")
- agent='Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36'
- ;;
-
- "pgl.yoyo.org")
- cmd_ext="-d mimetype=plaintext -d hostformat=hosts"
- ;;
-
- # Default is a simple request
- *) cmd_ext=""
- esac
- if [[ "${skipDownload}" == false ]]; then
- echo -n "::: Getting $domain list..."
- gravity_transport "$url" "$cmd_ext" "$agent"
- fi
- done
+# Define options for when retrieving blocklists
+gravity_Supernova() {
+ local url domain agent cmd_ext str
+
+ echo ""
+
+ # Loop through $sources and download each one
+ for ((i = 0; i < "${#sources[@]}"; i++)); do
+ url="${sources[$i]}"
+ domain="${sourceDomains[$i]}"
+
+ # Save the file as list.#.domain
+ saveLocation="${piholeDir}/list.${i}.${domain}.${domainsExtension}"
+ activeDomains[$i]="${saveLocation}"
+
+ # Default user-agent (for Cloudflare's Browser Integrity Check: https://support.cloudflare.com/hc/en-us/articles/200170086-What-does-the-Browser-Integrity-Check-do-)
+ agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36"
+
+ # Provide special commands for blocklists which may need them
+ case "${domain}" in
+ "pgl.yoyo.org") cmd_ext="-d mimetype=plaintext -d hostformat=hosts";;
+ *) cmd_ext="";;
+ esac
+
+ if [[ "${skipDownload}" == false ]]; then
+ echo -e " ${INFO} Target: ${domain} (${url##*/})"
+ gravity_Pull "${url}" "${cmd_ext}" "${agent}"
+ echo ""
+ fi
+ done
+ gravity_Blackbody=true
}
-# Schwarzchild - aggregate domains to one list and add blacklisted domains
-gravity_Schwarzchild() {
- echo "::: "
- # Find all active domains and compile them into one file and remove CRs
- echo -n "::: Aggregating list of domains..."
- truncate -s 0 ${piholeDir}/${matterAndLight}
- for i in "${activeDomains[@]}"; do
- # Only assimilate list if it is available (download might have faild permanently)
- if [[ -r "${i}" ]]; then
- cat "${i}" | tr -d '\r' >> ${piholeDir}/${matterAndLight}
- fi
- done
- echo " done!"
+# Download specified URL and perform checks on HTTP status and file content
+gravity_Pull() {
+ local url="${1}" cmd_ext="${2}" agent="${3}" heisenbergCompensator="" patternBuffer str httpCode success=""
+
+ # Create temp file to store content on disk instead of RAM
+ patternBuffer=$(mktemp -p "/tmp" --suffix=".phgpb")
+
+ # Determine if $saveLocation has read permission
+ if [[ -r "${saveLocation}" ]]; then
+ # Have curl determine if a remote file has been modified since last retrieval
+ # Uses "Last-Modified" header, which certain web servers do not provide (e.g: raw github urls)
+ heisenbergCompensator="-z ${saveLocation}"
+ fi
+
+ str="Status:"
+ echo -ne " ${INFO} ${str} Pending..."
+ # shellcheck disable=SC2086
+ httpCode=$(curl -s -L ${cmd_ext} ${heisenbergCompensator} -w "%{http_code}" -A "${agent}" "${url}" -o "${patternBuffer}" 2> /dev/null)
+
+ # Determine "Status:" output based on HTTP response
+ case "${httpCode}" in
+ "200") echo -e "${OVER} ${TICK} ${str} Retrieval successful"; success=true;;
+ "304") echo -e "${OVER} ${TICK} ${str} No changes detected"; success=true;;
+ "000") echo -e "${OVER} ${CROSS} ${str} Connection Refused";;
+ "403") echo -e "${OVER} ${CROSS} ${str} Forbidden";;
+ "404") echo -e "${OVER} ${CROSS} ${str} Not found";;
+ "408") echo -e "${OVER} ${CROSS} ${str} Time-out";;
+ "451") echo -e "${OVER} ${CROSS} ${str} Unavailable For Legal Reasons";;
+ "500") echo -e "${OVER} ${CROSS} ${str} Internal Server Error";;
+ "504") echo -e "${OVER} ${CROSS} ${str} Connection Timed Out (Gateway)";;
+ "521") echo -e "${OVER} ${CROSS} ${str} Web Server Is Down (Cloudflare)";;
+ "522") echo -e "${OVER} ${CROSS} ${str} Connection Timed Out (Cloudflare)";;
+ * ) echo -e "${OVER} ${CROSS} ${str} ${httpCode}";;
+ esac
+
+ # Determine if the blocklist was downloaded and saved correctly
+ if [[ "${success}" == true ]]; then
+ if [[ "${httpCode}" == "304" ]]; then
+ : # Do not attempt to re-parse file
+ # Check if $patternbuffer is a non-zero length file
+ elif [[ -s "${patternBuffer}" ]]; then
+ # Determine if blocklist is non-standard and parse as appropriate
+ gravity_ParseFileIntoDomains "${patternBuffer}" "${saveLocation}"
+ else
+ # Fall back to previously cached list if $patternBuffer is empty
+ echo -e " ${INFO} Received empty file: ${COL_LIGHT_GREEN}using previously cached list${COL_NC}"
+ fi
+ else
+ # Determine if cached list has read permission
+ if [[ -r "${saveLocation}" ]]; then
+ echo -e " ${CROSS} List download failed: ${COL_LIGHT_GREEN}using previously cached list${COL_NC}"
+ else
+ echo -e " ${CROSS} List download failed: ${COL_LIGHT_RED}no cached list available${COL_NC}"
+ fi
+ fi
}
-gravity_Blacklist() {
- # Append blacklist entries to eventHorizon if they exist
- if [[ -f "${blacklistFile}" ]]; then
- numBlacklisted=$(wc -l < "${blacklistFile}")
- plural=; [[ "$numBlacklisted" != "1" ]] && plural=s
- echo "::: Exact blocked domain${plural}: $numBlacklisted"
- else
- echo "::: Nothing to blacklist!"
- fi
+# Parse source files into domains format
+gravity_ParseFileIntoDomains() {
+ local source="${1}" destination="${2}" commentPattern firstLine abpFilter
+
+ # Determine if we are parsing a consolidated list
+ if [[ "${source}" == "${piholeDir}/${matterAndLight}" ]]; then
+ # Define symbols used as comments: #;@![/
+ commentPattern="[#;@![\\/]"
+
+ # Parse Domains/Hosts files by removing comments & host IPs
+ # Logic: Ignore lines which begin with comments
+ awk '!/^'"${commentPattern}"'/ {
+ # Determine if there are multiple words seperated by a space
+ if(NF>1) {
+ # Remove comments (including prefixed spaces/tabs)
+ if($0 ~ /'"${commentPattern}"'/) { gsub("( |\t)'"${commentPattern}"'.*", "", $0) }
+ # Determine if there are aliased domains
+ if($3) {
+ # Remove IP address
+ $1=""
+ # Remove space which is left in $0 when removing $1
+ gsub("^ ", "", $0)
+ print $0
+ } else if($2) {
+ # Print single domain without IP
+ print $2
+ }
+ # If there are no words seperated by space
+ } else if($1) {
+ print $1
+ }
+ }' "${source}" 2> /dev/null > "${destination}"
+ return 0
+ fi
+
+ # Individual file parsing: Keep comments, while parsing domains from each line
+ # We keep comments to respect the list maintainer's licensing
+ read -r firstLine < "${source}"
+
+ # Determine how to parse individual source file formats
+ if [[ "${firstLine,,}" =~ (adblock|ublock|^!) ]]; then
+ # Compare $firstLine against lower case words found in Adblock lists
+ echo -ne " ${INFO} Format: Adblock"
+
+ # Define symbols used as comments: [!
+ # "||.*^" includes the "Example 2" domains we can extract
+ # https://adblockplus.org/filter-cheatsheet
+ abpFilter="/^(\\[|!)|^(\\|\\|.*\\^)/"
+
+ # Parse Adblock lists by extracting "Example 2" domains
+ # Logic: Ignore lines which do not include comments or domain name anchor
+ awk ''"${abpFilter}"' {
+ # Remove valid adblock type options
+ gsub(/\$?~?(important|third-party|popup|subdocument|websocket),?/, "", $0)
+ # Remove starting domain name anchor "||" and ending seperator "^"
+ gsub(/^(\|\|)|(\^)/, "", $0)
+ # Remove invalid characters (*/,=$)
+ if($0 ~ /[*\/,=\$]/) { $0="" }
+ # Remove lines which are only IPv4 addresses
+ if($0 ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) { $0="" }
+ if($0) { print $0 }
+ }' "${source}" > "${destination}"
+
+ # Determine if there are Adblock exception rules
+ # https://adblockplus.org/filters
+ if grep -q "^@@||" "${source}" &> /dev/null; then
+ # Parse Adblock lists by extracting exception rules
+ # Logic: Ignore lines which do not include exception format "@@||example.com^"
+ awk -F "[|^]" '/^@@\|\|.*\^/ {
+ # Remove valid adblock type options
+ gsub(/\$?~?(third-party)/, "", $0)
+ # Remove invalid characters (*/,=$)
+ if($0 ~ /[*\/,=\$]/) { $0="" }
+ if($3) { print $3 }
+ }' "${source}" > "${destination}.exceptionsFile.tmp"
+
+ # Remove exceptions
+ grep -F -x -v -f "${destination}.exceptionsFile.tmp" "${destination}" > "${source}"
+ mv "${source}" "${destination}"
+ fi
+ echo -e "${OVER} ${TICK} Format: Adblock"
+ elif grep -q "^address=/" "${source}" &> /dev/null; then
+ # Parse Dnsmasq format lists
+ echo -e " ${CROSS} Format: Dnsmasq (list type not supported)"
+ elif grep -q -E "^https?://" "${source}" &> /dev/null; then
+ # Parse URL list if source file contains "http://" or "https://"
+ # Scanning for "^IPv4$" is too slow with large (1M) lists on low-end hardware
+ echo -ne " ${INFO} Format: URL"
+
+ awk '{
+ # Remove URL protocol, optional "username:password@", and ":?/;"
+ if ($0 ~ /[:?\/;]/) { gsub(/(^.*:\/\/(.*:.*@)?|[:?\/;].*)/, "", $0) }
+ # Remove lines which are only IPv4 addresses
+ if ($0 ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) { $0="" }
+ if ($0) { print $0 }
+ }' "${source}" 2> /dev/null > "${destination}"
+
+ echo -e "${OVER} ${TICK} Format: URL"
+ else
+ # Default: Keep hosts/domains file in same format as it was downloaded
+ output=$( { mv "${source}" "${destination}"; } 2>&1 )
+
+ if [[ ! -e "${destination}" ]]; then
+ echo -e "\\n ${CROSS} Unable to move tmp file to ${piholeDir}
+ ${output}"
+ gravity_Cleanup "error"
+ fi
+ fi
}
-gravity_Wildcard() {
- # Return number of wildcards in output - don't actually handle wildcards
- if [[ -f "${wildcardlist}" ]]; then
- numWildcards=$(grep -c ^ "${wildcardlist}")
- if [[ -n "${IPV4_ADDRESS}" && -n "${IPV6_ADDRESS}" ]];then
- let numWildcards/=2
- fi
- plural=; [[ "$numWildcards" != "1" ]] && plural=s
- echo "::: Wildcard blocked domain${plural}: $numWildcards"
- else
- echo "::: No wildcards used!"
- fi
+# Create (unfiltered) "Matter and Light" consolidated list
+gravity_Schwarzschild() {
+ local str lastLine
+
+ str="Consolidating blocklists"
+ echo -ne " ${INFO} ${str}..."
+
+ # Empty $matterAndLight if it already exists, otherwise, create it
+ : > "${piholeDir}/${matterAndLight}"
+
+ # Loop through each *.domains file
+ for i in "${activeDomains[@]}"; do
+ # Determine if file has read permissions, as download might have failed
+ if [[ -r "${i}" ]]; then
+ # Remove windows CRs from file, convert list to lower case, and append into $matterAndLight
+ tr -d '\r' < "${i}" | tr '[:upper:]' '[:lower:]' >> "${piholeDir}/${matterAndLight}"
+ # Ensure that the first line of a new list is on a new line
+ lastLine=$(tail -1 "${piholeDir}/${matterAndLight}")
+ if [[ "${#lastLine}" -gt 0 ]]; then
+ echo "" >> "${piholeDir}/${matterAndLight}"
+ fi
+ fi
+ done
+
+ echo -e "${OVER} ${TICK} ${str}"
}
-gravity_Whitelist() {
- #${piholeDir}/${eventHorizon})
- echo ":::"
- # Prevent our sources from being pulled into the hole
- plural=; [[ "${sources[@]}" != "1" ]] && plural=s
- echo -n "::: Adding adlist source${plural} to the whitelist..."
-
- urls=()
- for url in "${sources[@]}"; do
- tmp=$(echo "${url}" | awk -F '/' '{print $3}')
- urls=("${urls[@]}" ${tmp})
- done
- echo " done!"
-
- # Ensure adlist domains are in whitelist.txt
- ${whitelistScript} -nr -q "${urls[@]}" > /dev/null
-
- # Check whitelist.txt exists.
- if [[ -f "${whitelistFile}" ]]; then
- # Remove anything in whitelist.txt from the Event Horizon
- numWhitelisted=$(wc -l < "${whitelistFile}")
- plural=; [[ "$numWhitelisted" != "1" ]] && plural=s
- echo -n "::: Whitelisting $numWhitelisted domain${plural}..."
- #print everything from preEventHorizon into eventHorizon EXCEPT domains in whitelist.txt
- grep -F -x -v -f ${whitelistFile} ${piholeDir}/${preEventHorizon} > ${piholeDir}/${eventHorizon}
- echo " done!"
- else
- echo "::: Nothing to whitelist!"
- fi
+# Parse consolidated list into (filtered, unique) domains-only format
+gravity_Filter() {
+ local str num
+
+ str="Extracting domains from blocklists"
+ echo -ne " ${INFO} ${str}..."
+
+ # Parse into hosts file
+ gravity_ParseFileIntoDomains "${piholeDir}/${matterAndLight}" "${piholeDir}/${parsedMatter}"
+
+ # Format $parsedMatter line total as currency
+ num=$(printf "%'.0f" "$(wc -l < "${piholeDir}/${parsedMatter}")")
+ echo -e "${OVER} ${TICK} ${str}
+ ${INFO} ${COL_BLUE}${num}${COL_NC} domains being pulled in by gravity"
+
+ str="Removing duplicate domains"
+ echo -ne " ${INFO} ${str}..."
+ sort -u "${piholeDir}/${parsedMatter}" > "${piholeDir}/${preEventHorizon}"
+ echo -e "${OVER} ${TICK} ${str}"
+
+ # Format $preEventHorizon line total as currency
+ num=$(printf "%'.0f" "$(wc -l < "${piholeDir}/${preEventHorizon}")")
+ echo -e " ${INFO} ${COL_BLUE}${num}${COL_NC} unique domains trapped in the Event Horizon"
}
-gravity_unique() {
- # Sort and remove duplicates
- echo -n "::: Removing duplicate domains...."
- sort -u ${piholeDir}/${supernova} > ${piholeDir}/${preEventHorizon}
- echo " done!"
- numberOf=$(wc -l < ${piholeDir}/${preEventHorizon})
- echo "::: $numberOf unique domains trapped in the event horizon."
+# Whitelist unique blocklist domain sources
+gravity_WhitelistBLD() {
+ local uniqDomains plural="" str
+
+ echo ""
+
+ # Create array of unique $sourceDomains
+ mapfile -t uniqDomains <<< "$(awk '{ if(!a[$1]++) { print $1 } }' <<< "$(printf '%s\n' "${sourceDomains[@]}")")"
+ [[ "${#uniqDomains[@]}" -ne 1 ]] && plural="s"
+
+ str="Adding ${#uniqDomains[@]} blocklist source domain${plural} to the whitelist"
+ echo -ne " ${INFO} ${str}..."
+
+ # Whitelist $uniqDomains
+ "${PIHOLE_COMMAND}" -w -nr -q "${uniqDomains[*]}" &> /dev/null
+
+ echo -e "${OVER} ${TICK} ${str}"
}
-gravity_doHostFormat() {
- # Check vars from setupVars.conf to see if we're using IPv4, IPv6, Or both.
- if [[ -n "${IPV4_ADDRESS}" && -n "${IPV6_ADDRESS}" ]];then
- # Both IPv4 and IPv6
- awk -v ipv4addr="$IPV4_ADDRESS" -v ipv6addr="$IPV6_ADDRESS" '{sub(/\r$/,""); print ipv4addr" "$0"\n"ipv6addr" "$0}' >> "${2}" < "${1}"
- elif [[ -n "${IPV4_ADDRESS}" && -z "${IPV6_ADDRESS}" ]];then
- # Only IPv4
- awk -v ipv4addr="$IPV4_ADDRESS" '{sub(/\r$/,""); print ipv4addr" "$0}' >> "${2}" < "${1}"
- elif [[ -z "${IPV4_ADDRESS}" && -n "${IPV6_ADDRESS}" ]];then
- # Only IPv6
- awk -v ipv6addr="$IPV6_ADDRESS" '{sub(/\r$/,""); print ipv6addr" "$0}' >> "${2}" < "${1}"
- 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
+# Whitelist user-defined domains
+gravity_Whitelist() {
+ local num plural="" str
+
+ if [[ ! -f "${whitelistFile}" ]]; then
+ echo -e " ${INFO} Nothing to whitelist!"
+ return 0
fi
+
+ num=$(wc -l < "${whitelistFile}")
+ [[ "${num}" -ne 1 ]] && plural="s"
+ str="Whitelisting ${num} domain${plural}"
+ echo -ne " ${INFO} ${str}..."
+
+ # Print everything from preEventHorizon into whitelistMatter EXCEPT domains in $whitelistFile
+ grep -F -x -v -f "${whitelistFile}" "${piholeDir}/${preEventHorizon}" > "${piholeDir}/${whitelistMatter}"
+
+ echo -e "${OVER} ${TICK} ${str}"
}
-gravity_hostFormatLocal() {
- # Format domain list as "192.168.x.x domain.com"
-
- if [[ -f /etc/hostname ]]; then
- hostname=$(</etc/hostname)
- elif [ -x "$(command -v hostname)" ]; then
- hostname=$(hostname -f)
- else
- echo "::: Error: Unable to determine fully qualified domain name of host"
- fi
-
- echo -e "${hostname}\npi.hole" > "${localList}.tmp"
- # Copy the file over as /etc/pihole/local.list so dnsmasq can use it
- rm "${localList}"
- gravity_doHostFormat "${localList}.tmp" "${localList}"
- rm "${localList}.tmp"
+# Output count of blacklisted domains and wildcards
+gravity_ShowBlockCount() {
+ local num plural
+
+ if [[ -f "${blacklistFile}" ]]; then
+ num=$(printf "%'.0f" "$(wc -l < "${blacklistFile}")")
+ plural=; [[ "${num}" -ne 1 ]] && plural="s"
+ echo -e " ${INFO} Blacklisted ${num} domain${plural}"
+ fi
+
+ if [[ -f "${wildcardFile}" ]]; then
+ num=$(grep -c "^" "${wildcardFile}")
+ # If IPv4 and IPv6 is used, divide total wildcard count by 2
+ if [[ -n "${IPV4_ADDRESS}" ]] && [[ -n "${IPV6_ADDRESS}" ]];then
+ num=$(( num/2 ))
+ fi
+ plural=; [[ "${num}" -ne 1 ]] && plural="s"
+ echo -e " ${INFO} Wildcard blocked ${num} domain${plural}"
+ fi
}
-gravity_hostFormatGravity() {
- # Format domain list as "192.168.x.x domain.com"
- echo "" > "${piholeDir}/${accretionDisc}"
- gravity_doHostFormat "${piholeDir}/${eventHorizon}" "${piholeDir}/${accretionDisc}"
- # Copy the file over as /etc/pihole/gravity.list so dnsmasq can use it
- mv "${piholeDir}/${accretionDisc}" "${adList}"
+# Parse list of domains into hosts format
+gravity_ParseDomainsIntoHosts() {
+ awk -v ipv4="$IPV4_ADDRESS" -v ipv6="$IPV6_ADDRESS" '{
+ # Remove windows CR line endings
+ sub(/\r$/, "")
+ # Parse each line as "ipaddr domain"
+ if(ipv6 && ipv4) {
+ print ipv4" "$0"\n"ipv6" "$0
+ } else if(!ipv6) {
+ print ipv4" "$0
+ } else {
+ print ipv6" "$0
+ }
+ }' >> "${2}" < "${1}"
}
-gravity_hostFormatBlack() {
- if [[ -f "${blacklistFile}" ]]; then
- numBlacklisted=$(wc -l < "${blacklistFile}")
- # Format domain list as "192.168.x.x domain.com"
- gravity_doHostFormat "${blacklistFile}" "${blackList}.tmp"
- # Copy the file over as /etc/pihole/black.list so dnsmasq can use it
- mv "${blackList}.tmp" "${blackList}"
+# Create "localhost" entries into hosts format
+gravity_ParseLocalDomains() {
+ local hostname
+
+ if [[ -s "/etc/hostname" ]]; then
+ hostname=$(< "/etc/hostname")
+ elif command -v hostname &> /dev/null; then
+ hostname=$(hostname -f)
else
- echo "::: Nothing to blacklist!"
+ echo -e " ${CROSS} Unable to determine fully qualified domain name of host"
+ return 0
+ fi
+
+ echo -e "${hostname}\\npi.hole" > "${localList}.tmp"
+
+ # Empty $localList if it already exists, otherwise, create it
+ : > "${localList}"
+
+ gravity_ParseDomainsIntoHosts "${localList}.tmp" "${localList}"
+
+ # Add additional LAN hosts provided by OpenVPN (if available)
+ if [[ -f "${VPNList}" ]]; then
+ awk -F, '{printf $2"\t"$1".vpn\n"}' "${VPNList}" >> "${localList}"
+ fi
+}
+
+# Create primary blacklist entries
+gravity_ParseBlacklistDomains() {
+ local output status
+
+ # Empty $accretionDisc if it already exists, otherwise, create it
+ : > "${piholeDir}/${accretionDisc}"
+
+ gravity_ParseDomainsIntoHosts "${piholeDir}/${whitelistMatter}" "${piholeDir}/${accretionDisc}"
+
+ # Move the file over as /etc/pihole/gravity.list so dnsmasq can use it
+ output=$( { mv "${piholeDir}/${accretionDisc}" "${adList}"; } 2>&1 )
+ status="$?"
+
+ if [[ "${status}" -ne 0 ]]; then
+ echo -e "\\n ${CROSS} Unable to move ${accretionDisc} from ${piholeDir}\\n ${output}"
+ gravity_Cleanup "error"
fi
}
-# blackbody - remove any remnant files from script processes
-gravity_blackbody() {
- # Loop through list files
- for file in ${piholeDir}/*.${justDomainsExtension}; do
- # If list is in active array then leave it (noop) else rm the list
- if [[ " ${activeDomains[@]} " =~ ${file} ]]; then
- :
- else
- rm -f "${file}"
- fi
- done
+# Create user-added blacklist entries
+gravity_ParseUserDomains() {
+ if [[ ! -f "${blacklistFile}" ]]; then
+ return 0
+ fi
+
+ gravity_ParseDomainsIntoHosts "${blacklistFile}" "${blackList}.tmp"
+ # Copy the file over as /etc/pihole/black.list so dnsmasq can use it
+ mv "${blackList}.tmp" "${blackList}" 2> /dev/null || \
+ echo -e "\\n ${CROSS} Unable to move ${blackList##*/}.tmp to ${piholeDir}"
}
-gravity_advanced() {
- # Remove comments and print only the domain name
- # Most of the lists downloaded are already in hosts file format but the spacing/formating is not contigious
- # This helps with that and makes it easier to read
- # It also helps with debugging so each stage of the script can be researched more in depth
- 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')
- #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})
- echo "::: ${numberOf} domains being pulled in by gravity..."
-
- gravity_unique
+# Trap Ctrl-C
+gravity_Trap() {
+ trap '{ echo -e "\\n\\n ${INFO} ${COL_LIGHT_RED}User-abort detected${COL_NC}"; gravity_Cleanup "error"; }' INT
}
-gravity_reload() {
+# Clean up after Gravity upon exit or cancellation
+gravity_Cleanup() {
+ local error="${1:-}"
+
+ str="Cleaning up stray matter"
+ echo -ne " ${INFO} ${str}..."
+
+ # Delete tmp content generated by Gravity
+ rm ${piholeDir}/pihole.*.txt 2> /dev/null
+ rm ${piholeDir}/*.tmp 2> /dev/null
+ rm /tmp/*.phgpb 2> /dev/null
+
+ # Ensure this function only runs when gravity_Supernova() has completed
+ if [[ "${gravity_Blackbody:-}" == true ]]; then
+ # Remove any unused .domains files
+ for file in ${piholeDir}/*.${domainsExtension}; do
+ # If list is not in active array, then remove it
+ if [[ ! "${activeDomains[*]}" == *"${file}"* ]]; then
+ rm -f "${file}" 2> /dev/null || \
+ echo -e " ${CROSS} Failed to remove ${file##*/}"
+ fi
+ done
+ fi
- # Reload hosts file
- echo ":::"
- echo -n "::: Refresh lists in dnsmasq..."
+ echo -e "${OVER} ${TICK} ${str}"
- #ensure /etc/dnsmasq.d/01-pihole.conf is pointing at the correct list!
- #First escape forward slashes in the path:
- adList=${adList//\//\\\/}
- #Now replace the line in dnsmasq file
-# sed -i "s/^addn-hosts.*/addn-hosts=$adList/" /etc/dnsmasq.d/01-pihole.conf
+ # Only restart DNS service if offline
+ if ! pidof dnsmasq &> /dev/null; then
+ "${PIHOLE_COMMAND}" restartdns
+ dnsWasOffline=true
+ fi
- "${PIHOLE_COMMAND}" restartdns
- echo " done!"
+ # Print Pi-hole status if an error occured
+ if [[ -n "${error}" ]]; then
+ "${PIHOLE_COMMAND}" status
+ exit 1
+ fi
+}
+
+helpFunc() {
+ echo "Usage: pihole -g
+Update domains from blocklists specified in adlists.list
+
+Options:
+ -f, --force Force the download of all specified blocklists
+ -h, --help Show this help dialog"
+ exit 0
}
for var in "$@"; do
- case "${var}" in
- "-f" | "--force" ) forceGrav=true;;
- "-h" | "--help" ) helpFunc;;
- "-sd" | "--skip-download" ) skipDownload=true;;
- "-b" | "--blacklist-only" ) blackListOnly=true;;
- esac
+ case "${var}" in
+ "-f" | "--force" ) forceDelete=true;;
+ "-h" | "--help" ) helpFunc;;
+ "-sd" | "--skip-download" ) skipDownload=true;;
+ "-b" | "--blacklist-only" ) listType="blacklist";;
+ "-w" | "--whitelist-only" ) listType="whitelist";;
+ "-wild" | "--wildcard-only" ) listType="wildcard"; dnsRestartType="restart";;
+ esac
done
-if [[ "${forceGrav}" == true ]]; then
- echo -n "::: Deleting exising list cache..."
- rm /etc/pihole/list.*
- echo " done!"
+# Trap Ctrl-C
+gravity_Trap
+
+if [[ "${forceDelete:-}" == true ]]; then
+ str="Deleting existing list cache"
+ echo -ne "${INFO} ${str}..."
+
+ rm /etc/pihole/list.* 2> /dev/null || true
+ echo -e "${OVER} ${TICK} ${str}"
fi
-if [[ ! "${blackListOnly}" == true ]]; then
- gravity_collapse
- gravity_spinup
- if [[ "${skipDownload}" == false ]]; then
- gravity_Schwarzchild
- gravity_advanced
- else
- echo "::: Using cached Event Horizon list..."
- numberOf=$(wc -l < ${piholeDir}/${preEventHorizon})
- echo "::: $numberOf unique domains trapped in the event horizon."
- fi
- gravity_Whitelist
+# Determine which functions to run
+if [[ "${skipDownload}" == false ]]; then
+ # Gravity needs to download blocklists
+ gravity_DNSLookup
+ gravity_Collapse
+ gravity_Supernova
+ gravity_Schwarzschild
+ gravity_Filter
+ gravity_WhitelistBLD
+else
+ # Gravity needs to modify Blacklist/Whitelist/Wildcards
+ echo -e " ${INFO} Using cached Event Horizon list..."
+ numberOf=$(printf "%'.0f" "$(wc -l < "${piholeDir}/${preEventHorizon}")")
+ echo -e " ${INFO} ${COL_BLUE}${numberOf}${COL_NC} unique domains trapped in the Event Horizon"
fi
-gravity_Blacklist
-gravity_Wildcard
-echo -n "::: Formatting domains into a HOSTS file..."
-if [[ ! "${blackListOnly}" == true ]]; then
- gravity_hostFormatLocal
- gravity_hostFormatGravity
+# Perform when downloading blocklists, or modifying the whitelist
+if [[ "${skipDownload}" == false ]] || [[ "${listType}" == "whitelist" ]]; then
+ gravity_Whitelist
fi
-gravity_hostFormatBlack
-echo " done!"
-gravity_blackbody
+gravity_ShowBlockCount
+
+# Perform when downloading blocklists, or modifying the white/blacklist (not wildcards)
+if [[ "${skipDownload}" == false ]] || [[ "${listType}" == *"list" ]]; then
+ str="Parsing domains into hosts format"
+ echo -ne " ${INFO} ${str}..."
-if [[ ! "${blackListOnly}" == true ]]; then
- #Clear no longer needed files...
- echo ":::"
- echo -n "::: Cleaning up un-needed files..."
- rm ${piholeDir}/pihole.*.txt
- echo " done!"
+ gravity_ParseUserDomains
+
+ # Perform when downloading blocklists
+ if [[ ! "${listType:-}" == "blacklist" ]]; then
+ gravity_ParseLocalDomains
+ gravity_ParseBlacklistDomains
+ fi
+
+ echo -e "${OVER} ${TICK} ${str}"
+
+ gravity_Cleanup
fi
-gravity_reload
+echo ""
+
+# Determine if DNS has been restarted by this instance of gravity
+if [[ -z "${dnsWasOffline:-}" ]]; then
+ # Use "force-reload" when restarting dnsmasq for everything but Wildcards
+ "${PIHOLE_COMMAND}" restartdns "${dnsRestartType:-force-reload}"
+fi
"${PIHOLE_COMMAND}" status
diff --git a/pihole b/pihole
index 055d6bce..652f4acb 100755
--- a/pihole
+++ b/pihole
@@ -1,4 +1,5 @@
#!/bin/bash
+
# Pi-hole: A black hole for Internet advertisements
# (c) 2017 Pi-hole, LLC (https://pi-hole.net)
# Network-wide ad blocking via your own hardware.
@@ -10,6 +11,8 @@
readonly PI_HOLE_SCRIPT_DIR="/opt/pihole"
readonly wildcardlist="/etc/dnsmasq.d/03-pihole-wildcard.conf"
+readonly colfile="${PI_HOLE_SCRIPT_DIR}/COL_TABLE"
+source "${colfile}"
# Must be root to use this tool
if [[ ! $EUID -eq 0 ]];then
@@ -17,13 +20,13 @@ if [[ ! $EUID -eq 0 ]];then
exec sudo bash "$0" "$@"
exit $?
else
- echo "::: sudo is needed to run pihole commands. Please run this script as root or install sudo."
+ echo -e " ${CROSS} sudo is needed to run pihole commands. Please run this script as root or install sudo."
exit 1
fi
fi
webpageFunc() {
- source /opt/pihole/webpage.sh
+ source "${PI_HOLE_SCRIPT_DIR}/webpage.sh"
main "$@"
exit 0
}
@@ -80,17 +83,28 @@ updateGravityFunc() {
exit 0
}
+# Scan an array of files for matching strings
scanList(){
- domain="${1}"
- list="${2}"
- method="${3}"
- if [[ ${method} == "-exact" ]] ; then
- grep -i -E "(^|\s)${domain}($|\s)" "${list}"
- else
- grep -i "${domain}" "${list}"
- fi
+ # Escape full stops
+ local domain="${1//./\\.}" lists="${2}" type="${3:-}"
+
+ # Prevent grep from printing file path
+ cd "/etc/pihole" || exit 1
+
+ # Prevent grep -i matching slowly: http://bit.ly/2xFXtUX
+ export LC_CTYPE=C
+
+ # /dev/null forces filename to be printed when only one list has been generated
+ # shellcheck disable=SC2086
+ case "${type}" in
+ "exact" ) grep -i -E -l "(^|\\s)${domain}($|\\s|#)" ${lists} /dev/null;;
+ "wc" ) grep -i -o -m 1 "/${domain}/" ${lists};;
+ * ) grep -i "${domain}" ${lists} /dev/null;;
+ esac
}
+# Print each subdomain
+# e.g: foo.bar.baz.com = "foo.bar.baz.com bar.baz.com baz.com com"
processWildcards() {
IFS="." read -r -a array <<< "${1}"
for (( i=${#array[@]}-1; i>=0; i-- )); do
@@ -107,39 +121,192 @@ processWildcards() {
}
queryFunc() {
- domain="${2}"
- method="${3}"
- lists=( /etc/pihole/list.* /etc/pihole/blacklist.txt)
- for list in ${lists[@]}; do
- if [ -e "${list}" ]; then
- 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 ""
- else
- echo "::: ${list} does not exist"
- echo ""
+ shift
+ local options="$*" adlist="" all="" exact="" blockpage="" matchType="match"
+
+ if [[ "${options}" == "-h" ]] || [[ "${options}" == "--help" ]]; then
+ echo "Usage: pihole -q [option] <domain>
+Example: 'pihole -q -exact domain.com'
+Query the adlists for a specified domain
+
+Options:
+ -adlist Print the name of the block list URL
+ -exact Search the block lists for exact domain matches
+ -all Return all query matches within a block list
+ -h, --help Show this help dialog"
+ exit 0
+ fi
+
+ if [[ ! -e "/etc/pihole/adlists.list" ]]; then
+ echo -e "${COL_LIGHT_RED}The file '/etc/pihole/adlists.list' was not found${COL_NC}"
+ exit 1
+ fi
+
+ # Handle valid options
+ if [[ "${options}" == *"-bp"* ]]; then
+ exact="exact"; blockpage=true
+ else
+ [[ "${options}" == *"-adlist"* ]] && adlist=true
+ [[ "${options}" == *"-all"* ]] && all=true
+ if [[ "${options}" == *"-exact"* ]]; then
+ exact="exact"; matchType="exact ${matchType}"
fi
- done
+ fi
+
+ # Strip valid options, leaving only the domain and invalid options
+ # This allows users to place the options before or after the domain
+ options=$(sed -E 's/ ?-(bp|adlists?|all|exact) ?//g' <<< "${options}")
+
+ # Handle remaining options
+ # If $options contain non ASCII characters, convert to punycode
+ case "${options}" in
+ "" ) str="No domain specified";;
+ *" "* ) str="Unknown query option specified";;
+ *[![:ascii:]]* ) domainQuery=$(idn2 "${options}");;
+ * ) domainQuery="${options}";;
+ esac
+
+ if [[ -n "${str:-}" ]]; then
+ echo -e "${str}${COL_NC}\\nTry 'pihole -q --help' for more information."
+ exit 1
+ fi
+
+ # Scan Whitelist and Blacklist
+ lists="whitelist.txt blacklist.txt"
+ mapfile -t results <<< "$(scanList "${domainQuery}" "${lists}" "${exact}")"
- # Scan for possible wildcard matches
- if [ -e "${wildcardlist}" ]; then
- local wildcards=($(processWildcards "${domain}"))
- for domain in ${wildcards[@]}; do
- result=$(scanList "\/${domain}\/" ${wildcardlist})
- # Remove empty lines before couting number of results
- count=$(sed '/^\s*$/d' <<< "$result" | wc -l)
- if [[ ${count} > 0 ]]; then
- echo "::: Wildcard blocking ${domain} (${count} results)"
- echo "${result}"
- echo ""
+ if [[ -n "${results[*]}" ]]; then
+ wbMatch=true
+
+ # Loop through each result in order to print unique file title once
+ for result in "${results[@]}"; do
+ fileName="${result%%.*}"
+
+ if [[ -n "${blockpage}" ]]; then
+ echo "π ${result}"
+ exit 0
+ elif [[ -n "${exact}" ]]; then
+ echo " ${matchType^} found in ${COL_BOLD}${fileName^}${COL_NC}"
+ else
+ # Only print filename title once per file
+ if [[ ! "${fileName}" == "${fileName_prev:-}" ]]; then
+ echo " ${matchType^} found in ${COL_BOLD}${fileName^}${COL_NC}"
+ fileName_prev="${fileName}"
+ fi
+ echo " ${result#*:}"
fi
done
fi
+
+ # Scan Wildcards
+ if [[ -e "${wildcardlist}" ]]; then
+ # Determine all subdomains, domain and TLDs
+ mapfile -t wildcards <<< "$(processWildcards "${domainQuery}")"
+
+ for match in "${wildcards[@]}"; do
+ # Search wildcard list for matches
+ mapfile -t results <<< "$(scanList "${match}" "${wildcardlist}" "wc")"
+
+ if [[ -n "${results[*]}" ]]; then
+ if [[ -z "${wcMatch:-}" ]] && [[ -z "${blockpage}" ]]; then
+ wcMatch=true
+ echo " ${matchType^} found in ${COL_BOLD}Wildcards${COL_NC}:"
+ fi
+
+ case "${blockpage}" in
+ true ) echo "π ${wildcardlist##*/}"; exit 0;;
+ * ) echo " *.${match}";;
+ esac
+ fi
+ done
+ fi
+
+ # Get version sorted *.domains filenames (without dir path)
+ lists=("$(cd "/etc/pihole" || exit 0; printf "%s\\n" -- *.domains | sort -V)")
+
+ # Query blocklists for occurences of domain
+ mapfile -t results <<< "$(scanList "${domainQuery}" "${lists[*]}" "${exact}")"
+
+ # Handle notices
+ if [[ -z "${wbMatch:-}" ]] && [[ -z "${wcMatch:-}" ]] && [[ -z "${results[*]}" ]]; then
+ echo -e " ${INFO} No ${exact/t/t }results found for ${COL_BOLD}${domainQuery}${COL_NC} found within block lists"
+ exit 0
+ elif [[ -z "${results[*]}" ]]; then
+ # Result found in WL/BL/Wildcards
+ exit 0
+ elif [[ -z "${all}" ]] && [[ "${#results[*]}" -ge 100 ]]; then
+ echo -e " ${INFO} Over 100 ${exact/t/t }results found for ${COL_BOLD}${domainQuery}${COL_NC}
+ This can be overridden using the -all option"
+ exit 0
+ fi
+
+ # Remove unwanted content from non-exact $results
+ if [[ -z "${exact}" ]]; then
+ # Delete lines starting with #
+ # Remove comments after domain
+ # Remove hosts format IP address
+ mapfile -t results <<< "$(IFS=$'\n'; sed \
+ -e "/:#/d" \
+ -e "s/[ \\t]#.*//g" \
+ -e "s/:.*[ \\t]/:/g" \
+ <<< "${results[*]}")"
+
+ # Exit if result was in a comment
+ [[ -z "${results[*]}" ]] && exit 0
+ fi
+
+ # Get adlist file content as array
+ if [[ -n "${adlist}" ]] || [[ -n "${blockpage}" ]]; then
+ for adlistUrl in $(< "/etc/pihole/adlists.list"); do
+ if [[ "${adlistUrl:0:4}" =~ (http|www.) ]]; then
+ adlists+=("${adlistUrl}")
+ fi
+ done
+ fi
+
+ # Print "Exact matches for" title
+ if [[ -n "${exact}" ]] && [[ -z "${blockpage}" ]]; then
+ plural=""; [[ "${#results[*]}" -gt 1 ]] && plural="es"
+ echo " ${matchType^}${plural} for ${COL_BOLD}${domainQuery}${COL_NC} found in:"
+ fi
+
+ for result in "${results[@]}"; do
+ fileName="${result/:*/}"
+
+ # Determine *.domains URL using filename's number
+ if [[ -n "${adlist}" ]] || [[ -n "${blockpage}" ]]; then
+ fileNum="${fileName/list./}"; fileNum="${fileNum%%.*}"
+ fileName="${adlists[$fileNum]}"
+
+ # Discrepency occurs when adlists has been modified, but Gravity has not been run
+ if [[ -z "${fileName}" ]]; then
+ fileName="${COL_LIGHT_RED}(no associated adlists URL found)${COL_NC}"
+ fi
+ fi
+
+ if [[ -n "${blockpage}" ]]; then
+ echo "${fileNum} ${fileName}"
+ elif [[ -n "${exact}" ]]; then
+ echo " ${fileName}"
+ else
+ if [[ ! "${fileName}" == "${fileName_prev:-}" ]]; then
+ count=""
+ echo " ${matchType^} found in ${COL_BOLD}${fileName}${COL_NC}:"
+ fileName_prev="${fileName}"
+ fi
+ : $((count++))
+
+ # Print matching domain if $max_count has not been reached
+ [[ -z "${all}" ]] && max_count="50"
+ if [[ -z "${all}" ]] && [[ "${count}" -ge "${max_count}" ]]; then
+ [[ "${count}" -gt "${max_count}" ]] && continue
+ echo " ${COL_GRAY}Over ${count} results found, skipping rest of file${COL_NC}"
+ else
+ echo " ${result#*:}"
+ fi
+ fi
+ done
+
exit 0
}
@@ -162,22 +329,39 @@ versionFunc() {
}
restartDNS() {
- dnsmasqPid=$(pidof dnsmasq)
- if [[ "${dnsmasqPid}" ]]; then
- # Service already running - reload config
- if [[ -x "$(command -v systemctl)" ]]; then
- systemctl restart dnsmasq
- else
- service dnsmasq restart
- fi
+ local svcOption svc str output status
+ svcOption="${1:-}"
+
+ # Determine if we should reload or restart dnsmasq
+ if [[ "${svcOption}" =~ "reload" ]]; then
+ # Using SIGHUP will NOT re-read any *.conf files
+ svc="killall -s SIGHUP dnsmasq"
else
- # Service not running, start it up
- if [[ -x "$(command -v systemctl)" ]]; then
- systemctl start dnsmasq
+ # Get PID of dnsmasq to determine if it needs to start or restart
+ if pidof dnsmasq &> /dev/null; then
+ svcOption="restart"
else
- service dnsmasq start
+ svcOption="start"
fi
+ svc="service dnsmasq ${svcOption}"
fi
+
+ # Print output to Terminal, but not to Web Admin
+ str="${svcOption^}ing DNS service"
+ [[ -t 1 ]] && echo -ne " ${INFO} ${str}..."
+
+ output=$( { ${svc}; } 2>&1 )
+ status="$?"
+
+ if [[ "${status}" -eq 0 ]]; then
+ [[ -t 1 ]] && echo -e "${OVER} ${TICK} ${str}"
+ else
+ [[ ! -t 1 ]] && local OVER=""
+ echo -e "${OVER} ${CROSS} ${output}"
+ fi
+
+ # Send signal to FTL to have it re-parse the gravity files
+ killall -s SIGHUP pihole-FTL
}
piholeEnable() {
@@ -190,6 +374,7 @@ Time:
#s Disable Pi-hole functionality for # second(s)
#m Disable Pi-hole functionality for # minute(s)"
exit 0
+
elif [[ "${1}" == "0" ]]; then
# Disable Pi-hole
sed -i 's/^addn-hosts=\/etc\/pihole\/gravity.list/#addn-hosts=\/etc\/pihole\/gravity.list/' /etc/dnsmasq.d/01-pihole.conf
@@ -197,34 +382,57 @@ Time:
if [[ -e "$wildcardlist" ]]; then
mv "$wildcardlist" "/etc/pihole/wildcard.list"
fi
- echo "::: Blocking has been disabled!"
if [[ $# > 1 ]]; then
- if [[ "${2}" == *"s"* ]]; then
+ local error=false
+ 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 &>/dev/null &
- elif [[ "${2}" == *"m"* ]]; then
+ if [[ "${tt}" =~ ^-?[0-9]+$ ]];then
+ local str="Disabling blocking for ${tt} seconds"
+ echo -e " ${INFO} ${str}..."
+ local str="Blocking will be re-enabled in ${tt} seconds"
+ nohup bash -c "sleep ${tt}; pihole enable" </dev/null &>/dev/null &
+ else
+ local error=true
+ fi
+ 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 &>/dev/null &
+ if [[ "${tt}" =~ ^-?[0-9]+$ ]];then
+ local str="Disabling blocking for ${tt} minutes"
+ echo -e " ${INFO} ${str}..."
+ local str="Blocking will be re-enabled in ${tt} minutes"
+ tt=$((${tt}*60))
+ nohup bash -c "sleep ${tt}; pihole enable" </dev/null &>/dev/null &
+ else
+ local error=true
+ fi
+ elif [[ -n "${2}" ]]; then
+ local error=true
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!"
+ echo -e " ${INFO} Disabling blocking"
fi
+
+ if [[ ${error} == true ]];then
+ echo -e " ${COL_LIGHT_RED}Unknown format for delayed reactivation of the blocking!${COL_NC}"
+ echo -e " Try 'pihole disable --help' for more information."
+ exit 1
+ fi
+
+ local str="Pi-hole Disabled"
fi
else
# Enable Pi-hole
- echo "::: Blocking has been enabled!"
+ echo -e " ${INFO} Enabling blocking"
+ local str="Pi-hole Enabled"
+
sed -i 's/^#addn-hosts/addn-hosts/' /etc/dnsmasq.d/01-pihole.conf
if [[ -e "/etc/pihole/wildcard.list" ]]; then
mv "/etc/pihole/wildcard.list" "$wildcardlist"
fi
fi
+
restartDNS
+
+ echo -e "${OVER} ${TICK} ${str}"
}
piholeLogging() {
@@ -243,54 +451,60 @@ Options:
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!"
+ echo -e " ${INFO} Disabling logging..."
+ local str="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!"
+ echo -e " ${INFO} Enabling logging..."
+ local str="Logging has been enabled!"
else
- echo "::: Invalid option passed, please pass 'on' or 'off'"
+ echo -e " ${COL_LIGHT_RED}Invalid option${COL_NC}
+ Try 'pihole logging --help' for more information."
exit 1
fi
restartDNS
+ echo -e "${OVER} ${TICK} ${str}"
}
-piholeStatus() {
- if [[ "$(netstat -plnt | grep -c ':53 ')" -gt "0" ]]; then
+statusFunc() {
+ local addnConfigs
+
+ # Determine if service is running on port 53 (Cr: https://superuser.com/a/806331)
+ if (echo > /dev/tcp/localhost/53) >/dev/null 2>&1; then
if [[ "${1}" != "web" ]]; then
- echo "::: DNS service is running"
+ echo -e " ${TICK} DNS service is running"
fi
else
- if [[ "${1}" == "web" ]]; then
- echo "-1";
- else
- echo "::: DNS service is NOT running"
- fi
- return
+ case "${1}" in
+ "web") echo "-1";;
+ *) echo -e " ${CROSS} DNS service is NOT running";;
+ esac
+ return 0
fi
- 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 [[ "$(grep -i "^addn-hosts=/" /etc/dnsmasq.d/01-pihole.conf)" ]]; then
- # List set
- if [[ "${1}" == "web" ]]; then
- echo 1;
- else
- echo "::: Pi-hole blocking is Enabled";
- fi
+ # Determine if Pi-hole's addn-hosts configs are commented out
+ addnConfigs=$(grep -i "addn-hosts=/" /etc/dnsmasq.d/01-pihole.conf)
+
+ if [[ "${addnConfigs}" =~ "#" ]]; then
+ # A config is commented out
+ case "${1}" in
+ "web") echo 0;;
+ *) echo -e " ${CROSS} Pi-hole blocking is Disabled";;
+ esac
+ elif [[ -n "${addnConfigs}" ]]; then
+ # Configs are set
+ case "${1}" in
+ "web") echo 1;;
+ *) echo -e " ${TICK} Pi-hole blocking is Enabled";;
+ esac
else
- # Addn-host not found
- if [[ "${1}" == "web" ]]; then
- echo 99
- else
- echo "::: No hosts file linked to dnsmasq, adding it in enabled state"
- fi
+ # No configs were found
+ case "${1}" in
+ "web") echo 99;;
+ *) echo -e " ${INFO} No hosts file linked to dnsmasq, adding it in enabled state";;
+ esac
# Add addn-host= to dnsmasq
echo "addn-hosts=/etc/pihole/gravity.list" >> /etc/dnsmasq.d/01-pihole.conf
restartDNS
@@ -298,8 +512,20 @@ piholeStatus() {
}
tailFunc() {
- echo "Press Ctrl-C to exit"
- tail -F /var/log/pihole.log
+ echo -e " ${INFO} Press Ctrl-C to exit"
+
+ # Retrieve IPv4/6 addresses
+ source /etc/pihole/setupVars.conf
+
+ # Strip date from each line
+ # Colour blocklist/blacklist/wildcard entries as red
+ # Colour A/AAAA/DHCP strings as white
+ # Colour everything else as gray
+ tail -f /var/log/pihole.log | sed -E \
+ -e "s,($(date +'%b %d ')| dnsmasq[.*[0-9]]),,g" \
+ -e "s,(.*(gravity.list|black.list| config ).* is (${IPV4_ADDRESS%/*}|${IPV6_ADDRESS:-NULL}).*),${COL_RED}&${COL_NC}," \
+ -e "s,.*(query\\[A|DHCP).*,${COL_NC}&${COL_NC}," \
+ -e "s,.*,${COL_GRAY}&${COL_NC},"
exit 0
}
@@ -312,6 +538,7 @@ Switch Pi-hole subsystems to a different Github branch
Repositories:
core [branch] Change the branch of Pi-hole's core subsystem
web [branch] Change the branch of Admin Console subsystem
+ ftl [branch] Change the branch of Pi-hole's FTL subsystem
Branches:
master Update subsystems to the latest stable release
@@ -326,12 +553,12 @@ Branches:
tricorderFunc() {
if [[ ! -p "/dev/stdin" ]]; then
- echo "Please do not call Tricorder directly."
+ echo -e " ${INFO} Please do not call Tricorder directly"
exit 1
fi
- if ! timeout 2 nc -z tricorder.pi-hole.net 9998 &> /dev/null; then
- echo "Unable to connect to Pi-hole's Tricorder server."
+ if ! (echo > /dev/tcp/tricorder.pi-hole.net/9998) >/dev/null 2>&1; then
+ echo -e " ${CROSS} Unable to connect to Pi-hole's Tricorder server"
exit 1
fi
@@ -339,9 +566,10 @@ tricorderFunc() {
openssl s_client -quiet -connect tricorder.pi-hole.net:9998 2> /dev/null < /dev/stdin
exit "$?"
else
- echo "Your debug log will be transmitted unencrypted via plain-text"
- echo "There is a possibility that this could be intercepted by a third party"
- echo "If you wish to cancel, press Ctrl-C to exit within 10 seconds"
+ echo -e " ${INFO} ${COL_YELLOW}Security Notice${COL_NC}: ${COL_WHITE}openssl${COL_NC} is not installed
+ Your debug log will be transmitted unencrypted via plain-text
+ There is a possibility that this could be intercepted by a third party
+ If you wish to cancel, press Ctrl-C to exit within 10 seconds"
secs="10"
while [[ "$secs" -gt "0" ]]; do
echo -ne "."
@@ -354,6 +582,11 @@ tricorderFunc() {
fi
}
+updateCheckFunc() {
+ "${PI_HOLE_SCRIPT_DIR}"/updatecheck.sh "$@"
+ exit 0
+}
+
helpFunc() {
echo "Usage: pihole [options]
Example: 'pihole -w -h'
@@ -382,7 +615,7 @@ Options:
-l, logging Specify whether the Pi-hole log should be used
Add '-h' for more info on logging usage
-q, query Query the adlists for a specified domain
- Add '-exact' AFTER a specified domain for exact match
+ Add '-h' for more info on query usage
-up, updatePihole Update Pi-hole subsystems
-v, version Show installed versions of Pi-hole, Admin Console & FTL
Add '-h' for more info on version usage
@@ -419,11 +652,12 @@ case "${1}" in
"uninstall" ) uninstallFunc;;
"enable" ) piholeEnable 1;;
"disable" ) piholeEnable 0 "$2";;
- "status" ) piholeStatus "$2";;
- "restartdns" ) restartDNS;;
+ "status" ) statusFunc "$2";;
+ "restartdns" ) restartDNS "$2";;
"-a" | "admin" ) webpageFunc "$@";;
"-t" | "tail" ) tailFunc;;
"checkout" ) piholeCheckoutFunc "$@";;
"tricorder" ) tricorderFunc;;
+ "updatechecker" ) updateCheckFunc;;
* ) helpFunc;;
esac
diff --git a/test/test_automated_install.py b/test/test_automated_install.py
index 60772625..0e961c7f 100644
--- a/test/test_automated_install.py
+++ b/test/test_automated_install.py
@@ -9,6 +9,10 @@ SETUPVARS = {
'PIHOLE_DNS_2' : '4.2.2.2'
}
+tick_box="[\x1b[1;32m\xe2\x9c\x93\x1b[0m]".decode("utf-8")
+cross_box="[\x1b[1;31m\xe2\x9c\x97\x1b[0m]".decode("utf-8")
+info_box="[i]".decode("utf-8")
+
def test_setupVars_are_sourced_to_global_scope(Pihole):
''' currently update_dialogs sources setupVars with a dot,
then various other functions use the variables.
@@ -55,6 +59,8 @@ def test_setupVars_saved_to_file(Pihole):
TERM=xterm
source /opt/pihole/basic-install.sh
{}
+ mkdir -p /etc/dnsmasq.d
+ version_check_dnsmasq
finalExports
cat /etc/pihole/setupVars.conf
'''.format(set_setup_vars))
@@ -74,7 +80,7 @@ def test_configureFirewall_firewalld_running_no_errors(Pihole):
source /opt/pihole/basic-install.sh
configureFirewall
''')
- expected_stdout = 'Configuring FirewallD for httpd and dnsmasq.'
+ 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
@@ -89,7 +95,7 @@ def test_configureFirewall_firewalld_disabled_no_errors(Pihole):
source /opt/pihole/basic-install.sh
configureFirewall
''')
- expected_stdout = 'No active firewall detected.. skipping firewall configuration.'
+ expected_stdout = 'No active firewall detected.. skipping firewall configuration'
assert expected_stdout in configureFirewall.stdout
def test_configureFirewall_firewalld_enabled_declined_no_errors(Pihole):
@@ -173,105 +179,13 @@ def test_installPiholeWeb_fresh_install_no_errors(Pihole):
source /opt/pihole/basic-install.sh
installPiholeWeb
''')
- assert 'Installing pihole custom index page...' in installWeb.stdout
+ assert info_box + ' Installing blocking page...' in installWeb.stdout
+ assert tick_box + ' Creating directory for blocking page, and copying files' in installWeb.stdout
+ assert cross_box + ' Backing up index.lighttpd.html' in installWeb.stdout
assert 'No default index.lighttpd.html file found... not backing up' in installWeb.stdout
+ assert tick_box + ' Installing sudoer file' in installWeb.stdout
web_directory = Pihole.run('ls -r /var/www/html/pihole').stdout
assert 'index.php' in web_directory
- assert 'index.js' in web_directory
- assert 'blockingpage.css' in web_directory
-
-def test_installPiholeWeb_empty_directory_no_errors(Pihole):
- ''' confirms all web page assets from Core repo are installed in an emtpy directory '''
- installWeb = Pihole.run('''
- source /opt/pihole/basic-install.sh
- mkdir -p /var/www/html/pihole
- installPiholeWeb
- ''')
- assert 'Installing pihole custom index page...' in installWeb.stdout
- assert 'No default index.lighttpd.html file found... not backing up' not in installWeb.stdout
- assert 'index.php missing, replacing...' in installWeb.stdout
- assert 'index.js missing, replacing...' in installWeb.stdout
- assert 'blockingpage.css missing, replacing...' in installWeb.stdout
- web_directory = Pihole.run('ls -r /var/www/html/pihole').stdout
- assert 'index.php' in web_directory
- assert 'index.js' in web_directory
- assert 'blockingpage.css' in web_directory
-
-def test_installPiholeWeb_index_php_no_errors(Pihole):
- ''' confirms all web page assets from Core repo are installed when necessary '''
- installWeb = Pihole.run('''
- source /opt/pihole/basic-install.sh
- mkdir -p /var/www/html/pihole
- touch /var/www/html/pihole/index.php
- installPiholeWeb
- ''')
- assert 'Installing pihole custom index page...' in installWeb.stdout
- assert 'No default index.lighttpd.html file found... not backing up' not in installWeb.stdout
- assert 'Existing index.php detected, not overwriting' in installWeb.stdout
- assert 'index.js missing, replacing...' in installWeb.stdout
- assert 'blockingpage.css missing, replacing...' in installWeb.stdout
- web_directory = Pihole.run('ls -r /var/www/html/pihole').stdout
- assert 'index.php' in web_directory
- assert 'index.js' in web_directory
- assert 'blockingpage.css' in web_directory
-
-def test_installPiholeWeb_index_js_no_errors(Pihole):
- ''' confirms all web page assets from Core repo are installed when necessary '''
- installWeb = Pihole.run('''
- source /opt/pihole/basic-install.sh
- mkdir -p /var/www/html/pihole
- touch /var/www/html/pihole/index.js
- installPiholeWeb
- ''')
- assert 'Installing pihole custom index page...' in installWeb.stdout
- assert 'No default index.lighttpd.html file found... not backing up' not in installWeb.stdout
- assert 'index.php missing, replacing...' in installWeb.stdout
- assert 'Existing index.js detected, not overwriting' in installWeb.stdout
- assert 'blockingpage.css missing, replacing...' in installWeb.stdout
- web_directory = Pihole.run('ls -r /var/www/html/pihole').stdout
- assert 'index.php' in web_directory
- assert 'index.js' in web_directory
- assert 'blockingpage.css' in web_directory
-
-def test_installPiholeWeb_blockingpage_css_no_errors(Pihole):
- ''' confirms all web page assets from Core repo are installed when necessary '''
- installWeb = Pihole.run('''
- source /opt/pihole/basic-install.sh
- mkdir -p /var/www/html/pihole
- touch /var/www/html/pihole/blockingpage.css
- installPiholeWeb
- ''')
- assert 'Installing pihole custom index page...' in installWeb.stdout
- assert 'No default index.lighttpd.html file found... not backing up' not in installWeb.stdout
- assert 'index.php missing, replacing...' in installWeb.stdout
- assert 'index.js missing, replacing...' in installWeb.stdout
- assert 'Existing blockingpage.css detected, not overwriting' in installWeb.stdout
- web_directory = Pihole.run('ls -r /var/www/html/pihole').stdout
- assert 'index.php' in web_directory
- assert 'index.js' in web_directory
- assert 'blockingpage.css' in web_directory
-
-def test_installPiholeWeb_already_populated_no_errors(Pihole):
- ''' confirms all web page assets from Core repo are installed when necessary '''
- installWeb = Pihole.run('''
- source /opt/pihole/basic-install.sh
- mkdir -p /var/www/html/pihole
- touch /var/www/html/pihole/index.php
- touch /var/www/html/pihole/index.js
- touch /var/www/html/pihole/blockingpage.css
- installPiholeWeb
- ''')
- assert 'Installing pihole custom index page...' in installWeb.stdout
- assert 'No default index.lighttpd.html file found... not backing up' not in installWeb.stdout
- assert 'Existing index.php detected, not overwriting' in installWeb.stdout
- assert 'index.php missing, replacing...' not in installWeb.stdout
- assert 'Existing index.js detected, not overwriting' in installWeb.stdout
- assert 'index.js missing, replacing...' not in installWeb.stdout
- assert 'Existing blockingpage.css detected, not overwriting' in installWeb.stdout
- assert 'blockingpage.css missing, replacing... ' not in installWeb.stdout
- web_directory = Pihole.run('ls -r /var/www/html/pihole').stdout
- assert 'index.php' in web_directory
- assert 'index.js' in web_directory
assert 'blockingpage.css' in web_directory
def test_update_package_cache_success_no_errors(Pihole):
@@ -281,9 +195,8 @@ def test_update_package_cache_success_no_errors(Pihole):
distro_check
update_package_cache
''')
- assert 'Updating local cache of available packages...' in updateCache.stdout
- assert 'ERROR' not in updateCache.stdout
- assert 'done!' in updateCache.stdout
+ assert tick_box + ' Update local cache of available packages' in updateCache.stdout
+ assert 'Error: Unable to update package cache.' not in updateCache.stdout
def test_update_package_cache_failure_no_errors(Pihole):
''' confirms package cache was not updated'''
@@ -293,9 +206,8 @@ def test_update_package_cache_failure_no_errors(Pihole):
distro_check
update_package_cache
''')
- assert 'Updating local cache of available packages...' in updateCache.stdout
- assert 'ERROR' in updateCache.stdout
- assert 'done!' not in updateCache.stdout
+ assert cross_box + ' Update local cache of available packages' in updateCache.stdout
+ assert 'Error: Unable to update package cache.' in updateCache.stdout
def test_FTL_detect_aarch64_no_errors(Pihole):
''' confirms only aarch64 package is downloaded for FTL engine '''
@@ -307,7 +219,11 @@ def test_FTL_detect_aarch64_no_errors(Pihole):
source /opt/pihole/basic-install.sh
FTLdetect
''')
- expected_stdout = 'Detected ARM-aarch64 architecture'
+ expected_stdout = info_box + ' FTL Checks...'
+ assert expected_stdout in detectPlatform.stdout
+ expected_stdout = tick_box + ' Detected ARM-aarch64 architecture'
+ assert expected_stdout in detectPlatform.stdout
+ expected_stdout = tick_box + ' Downloading and Installing FTL'
assert expected_stdout in detectPlatform.stdout
def test_FTL_detect_armv6l_no_errors(Pihole):
@@ -320,7 +236,11 @@ def test_FTL_detect_armv6l_no_errors(Pihole):
source /opt/pihole/basic-install.sh
FTLdetect
''')
- expected_stdout = 'Detected ARM-hf architecture (armv6 or lower)'
+ expected_stdout = info_box + ' FTL Checks...'
+ assert expected_stdout in detectPlatform.stdout
+ expected_stdout = tick_box + ' Detected ARM-hf architecture (armv6 or lower)'
+ assert expected_stdout in detectPlatform.stdout
+ expected_stdout = tick_box + ' Downloading and Installing FTL'
assert expected_stdout in detectPlatform.stdout
def test_FTL_detect_armv7l_no_errors(Pihole):
@@ -333,7 +253,11 @@ def test_FTL_detect_armv7l_no_errors(Pihole):
source /opt/pihole/basic-install.sh
FTLdetect
''')
- expected_stdout = 'Detected ARM-hf architecture (armv7+)'
+ expected_stdout = info_box + ' FTL Checks...'
+ assert expected_stdout in detectPlatform.stdout
+ expected_stdout = tick_box + ' Detected ARM-hf architecture (armv7+)'
+ assert expected_stdout in detectPlatform.stdout
+ expected_stdout = tick_box + ' Downloading and Installing FTL'
assert expected_stdout in detectPlatform.stdout
def test_FTL_detect_x86_64_no_errors(Pihole):
@@ -342,7 +266,11 @@ def test_FTL_detect_x86_64_no_errors(Pihole):
source /opt/pihole/basic-install.sh
FTLdetect
''')
- expected_stdout = 'Detected x86_64 architecture'
+ expected_stdout = info_box + ' FTL Checks...'
+ assert expected_stdout in detectPlatform.stdout
+ expected_stdout = tick_box + ' Detected x86_64 architecture'
+ assert expected_stdout in detectPlatform.stdout
+ expected_stdout = tick_box + ' Downloading and Installing FTL'
assert expected_stdout in detectPlatform.stdout
def test_FTL_detect_unknown_no_errors(Pihole):
@@ -363,9 +291,12 @@ def test_FTL_download_aarch64_no_errors(Pihole):
source /opt/pihole/basic-install.sh
FTLinstall pihole-FTL-aarch64-linux-gnu
''')
- expected_stdout = 'done'
+ expected_stdout = tick_box + ' Downloading and Installing FTL'
assert expected_stdout in download_binary.stdout
- assert 'failed' not in download_binary.stdout
+ error = 'Error: Download of binary from Github failed'
+ assert error not in download_binary.stdout
+ error = 'Error: URL not found'
+ assert error not in download_binary.stdout
def test_FTL_download_unknown_fails_no_errors(Pihole):
''' confirms unknown binary is not downloaded for FTL engine '''
@@ -374,9 +305,10 @@ def test_FTL_download_unknown_fails_no_errors(Pihole):
source /opt/pihole/basic-install.sh
FTLinstall pihole-FTL-mips
''')
- expected_stdout = 'failed'
+ expected_stdout = cross_box + ' Downloading and Installing FTL'
assert expected_stdout in download_binary.stdout
- assert 'done' not in download_binary.stdout
+ error = 'Error: URL not found'
+ assert error in download_binary.stdout
def test_FTL_binary_installed_and_responsive_no_errors(Pihole):
''' confirms FTL binary is copied and functional in installed location '''
@@ -410,7 +342,7 @@ def test_IPv6_only_link_local(Pihole):
source /opt/pihole/basic-install.sh
useIPv6dialog
''')
- expected_stdout = 'Found neither IPv6 ULA nor GUA address, blocking IPv6 ads will not be enabled'
+ expected_stdout = 'Unable to find IPv6 ULA/GUA address, IPv6 adblocking will not be enabled'
assert expected_stdout in detectPlatform.stdout
def test_IPv6_only_ULA(Pihole):