diff --git a/.editorconfig b/.editorconfig index ee415d1f..a50f2f70 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,4 @@ -# EditorConfig is awesome: http://EditorConfig.org +# EditorConfig is awesome: https://editorconfig.org/ # top-most EditorConfig file root = true diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 4a9c585a..bef9f73c 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -9,11 +9,11 @@ `{Replace this with a number from 1 to 10. 1 being not familiar, and 10 being very familiar}` --- -**Expected behaviour:** +**Expected behavior:** `{A detailed description of what you expect to see}` -**Actual behaviour:** +**Actual behavior:** `{A detailed description and/or screenshots of what you do see}` diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 41564b65..7509e923 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -10,7 +10,7 @@ - [ ] 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)) -Please make sure you [Sign Off](https://github.com/pi-hole/pi-hole/wiki/How-to-signoff-your-commits.) all commits. Pi-hole enforces the [DCO](https://github.com/pi-hole/pi-hole/wiki/Contributing-to-the-project). +Please make sure you [Sign Off](https://docs.pi-hole.net/guides/github/how-to-signoff/) all commits. Pi-hole enforces the [DCO](https://docs.pi-hole.net/guides/github/contributing/). --- **What does this PR aim to accomplish?:** diff --git a/.gitignore b/.gitignore index 1e80dfb8..b7ad1e41 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,7 @@ __pycache__ # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 -# All idea files, with execptions +# All idea files, with exceptions .idea !.idea/codeStyles/* !.idea/codeStyleSettings.xml diff --git a/.stickler.yml b/.stickler.yml index 0eaae8cb..ab98025e 100644 --- a/.stickler.yml +++ b/.stickler.yml @@ -2,5 +2,4 @@ linters: shellcheck: shell: bash phpcs: - csslint: flake8: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e32b500e..6992cce5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,7 +32,7 @@ When requesting or submitting new features, first consider whether it might be u - Submit Pull Requests to the **development branch only**. - Before Submitting your Pull Request, merge `development` with your new branch and fix any conflicts. (Make sure you don't break anything in development!) -- Please use the [Google Style Guide for Shell](https://google.github.io/styleguide/shell.xml) for your code submission styles. +- Please use the [Google Style Guide for Shell](https://google.github.io/styleguide/shell.xml) for your code submission styles. - Commit Unix line endings. - Please use the Pi-hole brand: **Pi-hole** (Take a special look at the capitalized 'P' and a low 'h' with a hyphen) - (Optional fun) keep to the theme of Star Trek/black holes/gravity. diff --git a/README.md b/README.md index 97459442..c91c84bf 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,22 @@ + +

-Pi-hole
-Network-wide ad blocking via your own Linux hardware
+ + Pi-hole + +
+ Network-wide ad blocking via your own Linux hardware

+ -The Pi-hole[®](https://pi-hole.net/trademark-rules-and-brand-guidelines/) is a [DNS sinkhole](https://en.wikipedia.org/wiki/DNS_Sinkhole) that protects your devices from unwanted content, without installing any client-side software. +# + +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. - **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) +- **Lightweight**: runs smoothly with [minimal hardware and software requirements](https://docs.pi-hole.net/main/prerequisites/) - **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 @@ -17,32 +25,35 @@ The Pi-hole[®](https://pi-hole.net/trademark-rules-and-brand-guidelines/) is a - **Free**: open source software which helps ensure _you_ are the sole person in control of your privacy ----- -[![Codacy Badge](https://api.codacy.com/project/badge/Grade/c558a0f8d7124c99b02b84f0f5564238)](https://www.codacy.com/app/Pi-hole/pi-hole?utm_source=github.com&utm_medium=referral&utm_content=pi-hole/pi-hole&utm_campaign=Badge_Grade) -[![Build Status](https://travis-ci.org/pi-hole/pi-hole.svg?branch=development)](https://travis-ci.org/pi-hole/pi-hole) -[![BountySource](https://www.bountysource.com/badge/tracker?tracker_id=3011939)](https://www.bountysource.com/trackers/3011939-pi-hole-pi-hole?utm_source=3011939&utm_medium=shield&utm_campaign=TRACKER_BADGE) + +Master [![Build Status](https://travis-ci.com/pi-hole/pi-hole.svg?branch=master)](https://travis-ci.com/pi-hole/pi-hole) Development [![Build Status](https://travis-ci.com/pi-hole/pi-hole.svg?branch=development)](https://travis-ci.com/pi-hole/pi-hole) ## One-Step Automated Install + Those who want to get started quickly and conveniently may install Pi-hole using the following command: -#### `curl -sSL https://install.pi-hole.net | bash` +### `curl -sSL https://install.pi-hole.net | bash` ## 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: + +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 -``` + +```bash git clone --depth 1 https://github.com/pi-hole/pi-hole.git Pi-hole cd "Pi-hole/automated install/" sudo bash basic-install.sh ``` ### Method 2: Manually download the installer and run -``` + +```bash wget -O basic-install.sh https://install.pi-hole.net sudo bash basic-install.sh ``` -## Post-install: Make your network take advantage of Pi-hole +## [Post-install: Make your network take advantage of Pi-hole](https://docs.pi-hole.net/main/post-install/) 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. @@ -53,161 +64,102 @@ As a last resort, you can always manually set each device to use Pi-hole as thei ----- ## 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. Make no mistake: **your support is absolutely vital to help keep us innovating!** -### Donations -Sending a donation using our links below is **extremely helpful** in offsetting a portion of our monthly expenses: +### [Donations](https://pi-hole.net/donate) -- PP Donate via PayPal
-- BTC [Bitcoin, Bitcoin Cash, Ethereum, Litecoin](https://commerce.coinbase.com/checkout/dd304d04-f324-4a77-931b-0db61c77a41b) +Sending a donation using our Sponsor Button is **extremely helpful** in offsetting a portion of our monthly expenses: ### Alternative support -If you'd rather not [donate](https://pi-hole.net/donate/) (_which is okay!_), there are other ways you can help support us: + +If you'd rather not donate (_which is okay!_), there are other ways you can help support us: + - [Patreon](https://patreon.com/pihole) _Become a patron for rewards_ -- [Digital Ocean](http://www.digitalocean.com/?refcode=344d234950e1) _affiliate link_ +- [Digital Ocean](https://www.digitalocean.com/?refcode=344d234950e1) _affiliate link_ - [Stickermule](https://www.stickermule.com/unlock?ref_id=9127301701&utm_medium=link&utm_source=invite) _earn a $10 credit after your first purchase_ -- [Pi-hole Swag Store](https://pi-hole.net/shop/) _affiliate link_ - [Amazon](http://www.amazon.com/exec/obidos/redirect-home/pihole09-20) _affiliate link_ -- [DNS Made Easy](https://cp.dnsmadeeasy.com/u/133706) _affiliate link_ -- [Vultr](http://www.vultr.com/?ref=7190426) _affiliate link_ - Spreading the word about our software, and how you have benefited from it ### Contributing via GitHub + We welcome _everyone_ to contribute to issue reports, suggest new features, and create pull requests. 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. 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. -### Presentations about Pi-hole -Word-of-mouth continues to help our project grow immensely, and so we are helping make this easier for people. - -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! - ----- ## Getting in touch with us -While we are primarily reachable on our Discourse User Forum, 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. - +While we are primarily reachable on our [Discourse User Forum](https://discourse.pi-hole.net/), 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. + +- [Frequently Asked Questions](https://discourse.pi-hole.net/c/faqs) +- [Feature Requests](https://discourse.pi-hole.net/c/feature-requests?order=votes) +- [Reddit](https://www.reddit.com/r/pihole/) +- [Twitter](https://twitter.com/The_Pi_hole) ----- ## 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 an understanding of `bash`. -Pi-hole Blacklist Demo +### The Command Line Interface + +The [pihole](https://docs.pi-hole.net/core/pihole-command/) 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 an understanding of `bash`. + +![Pi-hole Blacklist Demo](https://pi-hole.github.io/graphics/Screenshots/blacklist-cli.gif) 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! -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. +- [Whitelisting, Blacklisting and Regex](https://docs.pi-hole.net/core/pihole-command/#whitelisting-blacklisting-and-regex) +- [Debugging utility](https://docs.pi-hole.net/core/pihole-command/#debugger) +- [Viewing the live log file](https://docs.pi-hole.net/core/pihole-command/#tail) +- [Updating Ad Lists](https://docs.pi-hole.net/core/pihole-command/#gravity) +- [Querying Ad Lists for blocked domains](https://docs.pi-hole.net/core/pihole-command/#query) +- [Enabling and Disabling Pi-hole](https://docs.pi-hole.net/core/pihole-command/#enable-disable) +- ... and *many* more! + +You can read our [Core Feature Breakdown](https://docs.pi-hole.net/core/pihole-command/#pi-hole-core) for more information. ### 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! -Pi-hole Dashboard +![Pi-hole Dashboard](https://pi-hole.github.io/graphics/Screenshots/pihole-dashboard.png) 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! + +- 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! There are several ways to [access the dashboard](https://discourse.pi-hole.net/t/how-do-i-access-pi-holes-dashboard-admin-interface/3168): -1. `http:///admin/` -2. `http://pi.hole/admin/` (when using Pi-hole as your DNS server) +1. `http://pi.hole/admin/` (when using Pi-hole as your DNS server) +2. `http:///admin/` 3. `http://pi.hole/` (when using Pi-hole as your DNS server) ## Faster-than-light Engine + FTLDNS 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, FTLDNS does this all *very quickly*! 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). +- 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 Origin Of Pi-hole -Pi-hole being an **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) - web server 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 set up 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. - ------ - -## 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/) (February, 2015) -- [MakeUseOf: Adblock Everywhere: The Raspberry Pi-Hole Way](http://www.makeuseof.com/tag/adblock-everywhere-raspberry-pi-hole-way/) (March, 2015) -- [Catchpoint: Ad-Blocking on Apple iOS9: Valuing the End User Experience](http://blog.catchpoint.com/2015/09/14/ad-blocking-apple/) (September, 2015) -- [Security Now Netcast: Pi-hole](https://www.youtube.com/watch?v=p7-osq_y8i8&t=100m26s) (October, 2015) -- [TekThing: Raspberry Pi-Hole Makes Ads Disappear!](https://youtu.be/8Co59HU2gY0?t=2m) (December, 2015) -- [Foolish Tech Show](https://youtu.be/bYyena0I9yc?t=2m4s) (December, 2015) -- [Block Ads on All Home Devices for $53.18](https://medium.com/@robleathern/block-ads-on-all-home-devices-for-53-18-a5f1ec139693#.gj1xpgr5d) (December, 2015) -- [Pi-Hole for Ubuntu 14.04](http://www.boyter.org/2015/12/pi-hole-ubuntu-14-04/) (December, 2015) -- [MacObserver Podcast 585](https://www.macobserver.com/tmo/podcast/macgeekgab-585) (December, 2015) -- [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) (January, 2016) -- [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/) (March, 2016) -- [Digital Trends: 5 Fun, Easy Projects You Can Try With a $35 Raspberry Pi](https://youtu.be/QwrKlyC2kdM?t=1m42s) (March, 2016) -- [Adafruit: Raspberry Pi Quick Look at Pi Hole ad blocking server with Tony D](https://www.youtube.com/watch?v=eg4u2j1HYlI) (June, 2016) -- [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/) (December, 2016) -- [Linux Pro: The Hole Truth](http://www.linuxpromagazine.com/Issues/2017/200/The-sysadmin-s-daily-grind-Pi-hole) (July, 2017) -- [Adafruit: installing Pi-hole on a Pi Zero W](https://learn.adafruit.com/pi-hole-ad-blocker-with-pi-zero-w/install-pi-hole) (August, 2017) -- [CryptoAUSTRALIA: How We Tried 5 Privacy Focused Raspberry Pi Projects](https://blog.cryptoaustralia.org.au/2017/10/05/5-privacy-focused-raspberry-pi-projects/) (October, 2017) -- [CryptoAUSTRALIA: Pi-hole Workshop](https://blog.cryptoaustralia.org.au/2017/11/02/pi-hole-network-wide-ad-blocker/) (November, 2017) -- [Know How 355: Killing ads with a Raspberry Pi-Hole!](https://www.twit.tv/shows/know-how/episodes/355) (November, 2017) -- [Hobohouse: Block Advertising on your Network with Pi-hole and Raspberry Pi](https://hobo.house/2018/02/27/block-advertising-with-pi-hole-and-raspberry-pi/) (March, 2018) -- [Scott Helme: Securing DNS across all of my devices with Pi-Hole + DNS-over-HTTPS + 1.1.1.1](https://scotthelme.co.uk/securing-dns-across-all-of-my-devices-with-pihole-dns-over-https-1-1-1-1/) (April, 2018) -- [Scott Helme: Catching and dealing with naughty devices on my home network](https://scotthelme.co.uk/catching-naughty-devices-on-my-home-network/) (April, 2018) -- [Bloomberg Business Week: Brotherhood of the Ad blockers](https://www.bloomberg.com/news/features/2018-05-10/inside-the-brotherhood-of-pi-hole-ad-blockers) (May, 2018) -- [Software Engineering Daily: Interview with the creator of Pi-hole](https://softwareengineeringdaily.com/2018/05/29/pi-hole-ad-blocker-hardware-with-jacob-salmela/) (May, 2018) -- [Raspberry Pi: Block ads at home using Pi-hole and a Raspberry Pi](https://www.raspberrypi.org/blog/pi-hole-raspberry-pi/) (July, 2018) -- [Troy Hunt: Mmm... Pi-hole...](https://www.troyhunt.com/mmm-pi-hole/) (September, 2018) -- [PEBKAK Podcast: Interview With Jacob Salmela](https://www.jerseystudios.net/2018/10/11/150-pi-hole/) (October, 2018) - ------ - -## Pi-hole Projects -- [The Big Blocklist Collection](https://wally3k.github.io) -- [Pie in the Sky-Hole](https://dlaa.me/blog/post/skyhole) -- [Copernicus: Windows Tray Application](https://github.com/goldbattle/copernicus) -- [Magic Mirror with DNS Filtering](https://zonksec.com/blog/magic-mirror-dns-filtering/#dnssoftware) -- [Windows DNS Swapper](https://github.com/roots84/DNS-Swapper) +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 find out [more details over here](https://discourse.pi-hole.net/t/pi-hole-api/1863). diff --git a/advanced/Scripts/COL_TABLE b/advanced/Scripts/COL_TABLE index 57aab4dd..d76be68c 100644 --- a/advanced/Scripts/COL_TABLE +++ b/advanced/Scripts/COL_TABLE @@ -1,7 +1,7 @@ -# Determine if terminal is capable of showing colours +# Determine if terminal is capable of showing colors 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 + # If something MUST be emphasized, use both COL_BOLD='' COL_ULINE='' diff --git a/advanced/Scripts/chronometer.sh b/advanced/Scripts/chronometer.sh index 1a4ce993..98f43c3f 100755 --- a/advanced/Scripts/chronometer.sh +++ b/advanced/Scripts/chronometer.sh @@ -13,7 +13,7 @@ LC_NUMERIC=C # Retrieve stats from FTL engine pihole-FTL() { - ftl_port=$(cat /var/run/pihole-FTL.port 2> /dev/null) + ftl_port=$(cat /run/pihole-FTL.port 2> /dev/null) if [[ -n "$ftl_port" ]]; then # Open connection to FTL exec 3<>"/dev/tcp/127.0.0.1/$ftl_port" @@ -72,7 +72,7 @@ printFunc() { # Remove excess characters from main text if [[ "$text_main_len" -gt "$text_main_max_len" ]]; then - # Trim text without colours + # Trim text without colors 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}" @@ -88,7 +88,7 @@ printFunc() { [[ "$spc_num" -le 0 ]] && spc_num="0" spc=$(printf "%${spc_num}s") - #spc="${spc// /.}" # Debug: Visualise spaces + #spc="${spc// /.}" # Debug: Visualize spaces printf "%s%s$spc" "$title" "$text_main" @@ -131,7 +131,7 @@ get_init_stats() { printf "%s%02d:%02d:%02d\\n" "$days" "$hrs" "$mins" "$secs" } - # Set Colour Codes + # Set Color Codes coltable="/opt/pihole/COL_TABLE" if [[ -f "${coltable}" ]]; then source ${coltable} @@ -153,7 +153,7 @@ get_init_stats() { sys_throttle_raw=$(vgt=$(sudo vcgencmd get_throttled); echo "${vgt##*x}") - # Active Throttle Notice: http://bit.ly/2gnunOo + # Active Throttle Notice: https://bit.ly/2gnunOo if [[ "$sys_throttle_raw" != "0" ]]; then case "$sys_throttle_raw" in *0001) thr_type="${COL_YELLOW}Under Voltage";; @@ -269,7 +269,7 @@ get_sys_stats() { scr_lines="${scr_size[0]}" scr_cols="${scr_size[1]}" - # Determine Chronometer size behaviour + # Determine Chronometer size behavior if [[ "$scr_cols" -ge 58 ]]; then chrono_width="large" elif [[ "$scr_cols" -gt 40 ]]; then @@ -308,7 +308,7 @@ get_sys_stats() { [[ "${cpu_freq}" == *".0"* ]] && cpu_freq="${cpu_freq/.0/}" fi - # Determine colour for temperature + # Determine color for temperature if [[ -n "$temp_file" ]]; then if [[ "$temp_unit" == "C" ]]; then cpu_temp=$(printf "%.0fc\\n" "$(calcFunc "$(< $temp_file) / 1000")") diff --git a/advanced/Scripts/database_migration/gravity-db.sh b/advanced/Scripts/database_migration/gravity-db.sh index 8a669429..70090a3b 100644 --- a/advanced/Scripts/database_migration/gravity-db.sh +++ b/advanced/Scripts/database_migration/gravity-db.sh @@ -104,4 +104,10 @@ upgrade_gravityDB(){ sqlite3 "${database}" < "${scriptPath}/10_to_11.sql" version=11 fi + if [[ "$version" == "11" ]]; then + # Rename group 0 from "Unassociated" to "Default" + echo -e " ${INFO} Upgrading gravity database from version 11 to 12" + sqlite3 "${database}" < "${scriptPath}/11_to_12.sql" + version=12 + fi } diff --git a/advanced/Scripts/database_migration/gravity/11_to_12.sql b/advanced/Scripts/database_migration/gravity/11_to_12.sql new file mode 100644 index 00000000..45fbc845 --- /dev/null +++ b/advanced/Scripts/database_migration/gravity/11_to_12.sql @@ -0,0 +1,19 @@ +.timeout 30000 + +PRAGMA FOREIGN_KEYS=OFF; + +BEGIN TRANSACTION; + +UPDATE "group" SET name = 'Default' WHERE id = 0; +UPDATE "group" SET description = 'The default group' WHERE id = 0; + +DROP TRIGGER IF EXISTS tr_group_zero; + +CREATE TRIGGER tr_group_zero AFTER DELETE ON "group" + BEGIN + INSERT OR IGNORE INTO "group" (id,enabled,name,description) VALUES (0,1,'Default','The default group'); + END; + +UPDATE info SET value = 12 WHERE property = 'version'; + +COMMIT; \ No newline at end of file diff --git a/advanced/Scripts/list.sh b/advanced/Scripts/list.sh index 4f2e046f..77a5dece 100755 --- a/advanced/Scripts/list.sh +++ b/advanced/Scripts/list.sh @@ -22,6 +22,9 @@ web=false domList=() typeId="" +comment="" +declare -i domaincount +domaincount=0 colfile="/opt/pihole/COL_TABLE" source ${colfile} @@ -97,10 +100,12 @@ ValidateDomain() { fi if [[ -n "${validDomain}" ]]; then - domList=("${domList[@]}" ${validDomain}) + domList=("${domList[@]}" "${validDomain}") else echo -e " ${CROSS} ${domain} is not a valid argument or domain name!" fi + + domaincount=$((domaincount+1)) } ProcessDomainList() { @@ -151,7 +156,12 @@ AddDomain() { reload=true # Insert only the domain here. The enabled and date_added fields will be filled # with their default values (enabled = true, date_added = current timestamp) - sqlite3 "${gravityDBfile}" "INSERT INTO domainlist (domain,type) VALUES ('${domain}',${typeId});" + if [[ -z "${comment}" ]]; then + sqlite3 "${gravityDBfile}" "INSERT INTO domainlist (domain,type) VALUES ('${domain}',${typeId});" + else + # also add comment when variable has been set through the "--comment" option + sqlite3 "${gravityDBfile}" "INSERT INTO domainlist (domain,type,comment) VALUES ('${domain}',${typeId},'${comment}');" + fi } RemoveDomain() { @@ -224,8 +234,16 @@ NukeList() { sqlite3 "${gravityDBfile}" "DELETE FROM domainlist WHERE type = ${typeId};" } -for var in "$@"; do - case "${var}" in +GetComment() { + comment="$1" + if [[ "${comment}" =~ [^a-zA-Z0-9_\#:/\.,\ -] ]]; then + echo " ${CROSS} Found invalid characters in domain comment!" + exit + fi +} + +while (( "$#" )); do + case "${1}" in "-w" | "whitelist" ) typeId=0;; "-b" | "blacklist" ) typeId=1;; "--white-regex" | "white-regex" ) typeId=2;; @@ -239,13 +257,15 @@ for var in "$@"; do "-l" | "--list" ) Displaylist;; "--nuke" ) NukeList;; "--web" ) web=true;; - * ) ValidateDomain "${var}";; + "--comment" ) GetComment "${2}"; shift;; + * ) ValidateDomain "${1}";; esac + shift done shift -if [[ $# = 0 ]]; then +if [[ ${domaincount} == 0 ]]; then helpFunc fi diff --git a/advanced/Scripts/pihole-reenable.sh b/advanced/Scripts/pihole-reenable.sh new file mode 100755 index 00000000..93ec3b95 --- /dev/null +++ b/advanced/Scripts/pihole-reenable.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# Pi-hole: A black hole for Internet advertisements +# (c) 2020 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. +# +# +# The pihole disable command has the option to set a specified time before +# blocking is automatically re-enabled. +# +# Present script is responsible for the sleep & re-enable part of the job and +# is automatically terminated if it is still running when pihole is enabled by +# other means. +# +# This ensures that pihole ends up in the correct state after a sequence of +# commands suchs as: `pihole disable 30s; pihole enable; pihole disable` + +readonly PI_HOLE_BIN_DIR="/usr/local/bin" + +sleep "${1}" +"${PI_HOLE_BIN_DIR}"/pihole enable diff --git a/advanced/Scripts/piholeARPTable.sh b/advanced/Scripts/piholeARPTable.sh index aa45f9ad..b6b552c9 100755 --- a/advanced/Scripts/piholeARPTable.sh +++ b/advanced/Scripts/piholeARPTable.sh @@ -36,13 +36,6 @@ flushARP(){ echo -ne " ${INFO} Flushing network table ..." fi - # Flush ARP cache to avoid re-adding of dead entries - if ! output=$(ip neigh flush all 2>&1); then - echo -e "${OVER} ${CROSS} Failed to clear ARP cache" - echo " Output: ${output}" - return 1 - fi - # Truncate network_addresses table in pihole-FTL.db # This needs to be done before we can truncate the network table due to # foreign key contraints diff --git a/advanced/Scripts/piholeCheckout.sh b/advanced/Scripts/piholeCheckout.sh index 31009dd9..1c1b16a4 100644 --- a/advanced/Scripts/piholeCheckout.sh +++ b/advanced/Scripts/piholeCheckout.sh @@ -3,7 +3,7 @@ # (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. @@ -36,7 +36,7 @@ warning1() { return 0 ;; *) - echo -e "\\n ${INFO} Branch change has been cancelled" + echo -e "\\n ${INFO} Branch change has been canceled" return 1 ;; esac @@ -84,7 +84,7 @@ checkout() { 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; } + fetch_checkout_pull_branch "${PI_HOLE_FILES_DIR}" "development" || { echo " ${CROSS} Unable to pull Core development branch"; exit 1; } if [[ "${INSTALL_WEB_INTERFACE}" == "true" ]]; then echo "" echo -e " ${INFO} Web interface" diff --git a/advanced/Scripts/piholeDebug.sh b/advanced/Scripts/piholeDebug.sh index 28d34ab6..d5da57ae 100755 --- a/advanced/Scripts/piholeDebug.sh +++ b/advanced/Scripts/piholeDebug.sh @@ -87,7 +87,7 @@ 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" +WEB_SERVER_CUSTOM_CONFIG_FILE="${WEB_SERVER_CONFIG_DIRECTORY}/external.conf" PIHOLE_INSTALL_LOG_FILE="${PIHOLE_DIRECTORY}/install.log" PIHOLE_RAW_BLOCKLIST_FILES="${PIHOLE_DIRECTORY}/list.*" @@ -138,7 +138,7 @@ PIHOLE_FTL_LOG="$(get_ftl_conf_value "LOGFILE" "${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 +# An array of operating system "pretty names" that we officially support # We can loop through the array at any time to see if it matches a value #SUPPORTED_OS=("Raspbian" "Ubuntu" "Fedora" "Debian" "CentOS") @@ -166,11 +166,13 @@ REQUIRED_FILES=("${PIHOLE_CRON_FILE}" "${PIHOLE_DHCP_CONFIG_FILE}" "${PIHOLE_WILDCARD_CONFIG_FILE}" "${WEB_SERVER_CONFIG_FILE}" +"${WEB_SERVER_CUSTOM_CONFIG_FILE}" "${PIHOLE_INSTALL_LOG_FILE}" "${PIHOLE_RAW_BLOCKLIST_FILES}" "${PIHOLE_LOCAL_HOSTS_FILE}" "${PIHOLE_LOGROTATE_FILE}" "${PIHOLE_SETUP_VARS_FILE}" +"${PIHOLE_FTL_CONF_FILE}" "${PIHOLE_COMMAND}" "${PIHOLE_COLTABLE_FILE}" "${FTL_PID}" @@ -296,11 +298,15 @@ compare_local_version_to_git_version() { log_write "${INFO} ${pihole_component}: ${COL_YELLOW}${remote_version:-Untagged}${COL_NC} (${FAQ_UPDATE_PI_HOLE})" fi + # Print the repo upstreams + remotes=$(git remote -v) + log_write "${INFO} Remotes: ${remotes//$'\n'/'\n '}" + # 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 + # If it is any other branch, they are in a development 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})" @@ -309,7 +315,7 @@ compare_local_version_to_git_version() { log_write "${INFO} Commit: ${remote_commit}" # if `local_status` is non-null, then the repo is not clean, display details here if [[ ${local_status} ]]; then - #Replace new lines in the status with 12 spaces to make the output cleaner + # Replace new lines in the status with 12 spaces to make the output cleaner log_write "${INFO} Status: ${local_status//$'\n'/'\n '}" local local_diff local_diff=$(git diff) @@ -357,7 +363,7 @@ check_component_versions() { get_program_version() { local program_name="${1}" - # Create a loval variable so this function can be safely reused + # Create a local 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 @@ -387,53 +393,53 @@ check_critical_program_versions() { get_program_version "php" } -is_os_supported() { - local os_to_check="${1}" - # Strip just the base name of the system using sed - # shellcheck disable=SC2001 - 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 -} +os_check() { + # This function gets a list of supported OS versions from a TXT record at versions.pi-hole.net + # and determines whether or not the script is running on one of those systems + local remote_os_domain valid_os valid_version detected_os_pretty detected_os detected_version + remote_os_domain="versions.pi-hole.net" + valid_os=false + valid_version=false -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 - local distro_info - #shellcheck disable=SC2016 - IFS=$'\r\n' command eval 'distro_info=( $(cat /etc/*release) )' + detected_os_pretty=$(cat /etc/*release | grep PRETTY_NAME | cut -d '=' -f2- | tr -d '"') + detected_os="${detected_os_pretty%% *}" + detected_version=$(cat /etc/*release | grep VERSION_ID | cut -d '=' -f2- | tr -d '"') - # 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 - 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 - : + IFS=" " read -r -a supportedOS < <(dig +short -t txt ${remote_os_domain} | tr -d '"') + + for i in "${supportedOS[@]}" + do + os_part=$(echo "$i" | cut -d '=' -f1) + versions_part=$(echo "$i" | cut -d '=' -f2-) + + if [[ "${detected_os}" =~ ${os_part} ]]; then + valid_os=true + IFS="," read -r -a supportedVer <<<"${versions_part}" + for x in "${supportedVer[@]}" + do + if [[ "${detected_version}" =~ $x ]];then + valid_version=true + break + fi + done + break fi done - # Set the IFS back to what it was - IFS="$OLD_IFS" + + # Display findings back to the user + if [ "$valid_os" = true ]; then + log_write "${TICK} Distro: ${COL_GREEN}${detected_os}${COL_NC}" + + if [ "$valid_version" = true ]; then + log_write "${TICK} Version: ${COL_GREEN}${detected_version}${COL_NC}" + else + log_write "${CROSS} Version: ${COL_RED}${detected_version}${COL_NC}" + log_write "${CROSS} Error: ${COL_RED}${detected_os} is supported but version ${detected_version} is currently unsupported (${FAQ_HARDWARE_REQUIREMENTS})${COL_NC}" + fi + else + log_write "${CROSS} Distro: ${COL_RED}${detected_os}${COL_NC}" + log_write "${CROSS} Error: ${COL_RED}${detected_os} is not a supported distro (${FAQ_HARDWARE_REQUIREMENTS})${COL_NC}" + fi } diagnose_operating_system() { @@ -445,7 +451,7 @@ diagnose_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 + os_check else # 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})" @@ -747,7 +753,7 @@ check_x_headers() { # 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 + # Store what the X-Header shoud be in variables for comparison later local block_page_working block_page_working="X-Pi-hole: A black hole for Internet advertisements." local dashboard_working @@ -818,7 +824,7 @@ dig_at() { # 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 + # If it can, show success 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 @@ -969,7 +975,7 @@ check_name_resolution() { # 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 + # Set the first argument passed to this 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}" @@ -987,14 +993,14 @@ dir_check() { } list_files_in_dir() { - # Set the first argument passed to tihs function as a named variable for better readability + # Set the first argument passed to this function as a named variable for better readability local dir_to_parse="${1}" # Store the files found in an array mapfile -t 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 + # If it's a directory, do nothing : elif [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_DEBUG_LOG}" ]] || \ [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_RAW_BLOCKLIST_FILES}" ]] || \ @@ -1107,22 +1113,19 @@ show_db_entries() { } show_groups() { - show_db_entries "Groups" "SELECT id,name,enabled,datetime(date_added,'unixepoch','localtime') date_added,datetime(date_modified,'unixepoch','localtime') date_modified,description FROM \"group\"" "4 50 7 19 19 50" + show_db_entries "Groups" "SELECT id,CASE enabled WHEN '0' THEN ' 0' WHEN '1' THEN ' 1' ELSE enabled END enabled,name,datetime(date_added,'unixepoch','localtime') date_added,datetime(date_modified,'unixepoch','localtime') date_modified,description FROM \"group\"" "4 7 50 19 19 50" } show_adlists() { - show_db_entries "Adlists" "SELECT id,address,enabled,datetime(date_added,'unixepoch','localtime') date_added,datetime(date_modified,'unixepoch','localtime') date_modified,comment FROM adlist" "4 100 7 19 19 50" - show_db_entries "Adlist groups" "SELECT * FROM adlist_by_group" "4 4" + show_db_entries "Adlists" "SELECT id,CASE enabled WHEN '0' THEN ' 0' WHEN '1' THEN ' 1' ELSE enabled END enabled,GROUP_CONCAT(adlist_by_group.group_id) group_ids,address,datetime(date_added,'unixepoch','localtime') date_added,datetime(date_modified,'unixepoch','localtime') date_modified,comment FROM adlist LEFT JOIN adlist_by_group ON adlist.id = adlist_by_group.adlist_id GROUP BY id;" "4 7 12 100 19 19 50" } show_domainlist() { - show_db_entries "Domainlist (0/1 = exact/regex whitelist, 2/3 = exact/regex blacklist)" "SELECT id,type,domain,enabled,datetime(date_added,'unixepoch','localtime') date_added,datetime(date_modified,'unixepoch','localtime') date_modified,comment FROM domainlist" "4 4 100 7 19 19 50" - show_db_entries "Domainlist groups" "SELECT * FROM domainlist_by_group" "10 10" + show_db_entries "Domainlist (0/1 = exact white-/blacklist, 2/3 = regex white-/blacklist)" "SELECT id,CASE type WHEN '0' THEN '0 ' WHEN '1' THEN ' 1 ' WHEN '2' THEN ' 2 ' WHEN '3' THEN ' 3' ELSE type END type,CASE enabled WHEN '0' THEN ' 0' WHEN '1' THEN ' 1' ELSE enabled END enabled,GROUP_CONCAT(domainlist_by_group.group_id) group_ids,domain,datetime(date_added,'unixepoch','localtime') date_added,datetime(date_modified,'unixepoch','localtime') date_modified,comment FROM domainlist LEFT JOIN domainlist_by_group ON domainlist.id = domainlist_by_group.domainlist_id GROUP BY id;" "4 4 7 12 100 19 19 50" } show_clients() { - show_db_entries "Clients" "SELECT id,ip,datetime(date_added,'unixepoch','localtime') date_added,datetime(date_modified,'unixepoch','localtime') date_modified,comment FROM client" "4 100 19 19 50" - show_db_entries "Client groups" "SELECT * FROM client_by_group" "10 10" + show_db_entries "Clients" "SELECT id,GROUP_CONCAT(client_by_group.group_id) group_ids,ip,datetime(date_added,'unixepoch','localtime') date_added,datetime(date_modified,'unixepoch','localtime') date_modified,comment FROM client LEFT JOIN client_by_group ON client.id = client_by_group.client_id GROUP BY id;" "4 12 100 19 19 50" } analyze_gravity_list() { @@ -1190,7 +1193,7 @@ analyze_pihole_log() { # 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 + # so we substitute 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 @@ -1212,6 +1215,11 @@ tricorder_use_nc_or_curl() { log_write " * Using ${COL_GREEN}curl${COL_NC} for transmission." # transmit he log via TLS and store the token returned in a variable tricorder_token=$(curl --silent --upload-file ${PIHOLE_DEBUG_LOG} https://tricorder.pi-hole.net:${TRICORDER_SSL_PORT_NUMBER}) + if [ -z "${tricorder_token}" ]; then + # curl failed, fallback to nc + log_write " * ${COL_GREEN}curl${COL_NC} failed, falling back to ${COL_YELLOW}netcat${COL_NC} for transmission." + tricorder_token=$(< ${PIHOLE_DEBUG_LOG} nc tricorder.pi-hole.net ${TRICORDER_NC_PORT_NUMBER}) + fi # Otherwise, else # use net cat @@ -1238,7 +1246,7 @@ upload_to_tricorder() { 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 pihole -d is running automatically (usually through the dashboard) if [[ "${AUTOMATED}" ]]; then # let the user know log_write "${INFO} Debug script running in automated mode" @@ -1254,7 +1262,7 @@ upload_to_tricorder() { # If they say yes, run our function for uploading the log [yY][eE][sS]|[yY]) tricorder_use_nc_or_curl;; # If they choose no, just exit out of the script - *) log_write " * Log will ${COL_GREEN}NOT${COL_NC} be uploaded to tricorder.";exit; + *) log_write " * Log will ${COL_GREEN}NOT${COL_NC} be uploaded to tricorder.\\n * A local copy of the debug log can be found at: ${COL_CYAN}${PIHOLE_DEBUG_LOG}${COL_NC}\\n";exit; esac fi # Check if tricorder.pi-hole.net is reachable and provide token diff --git a/advanced/Scripts/query.sh b/advanced/Scripts/query.sh index a96129e0..fe9b8ebf 100755 --- a/advanced/Scripts/query.sh +++ b/advanced/Scripts/query.sh @@ -29,19 +29,21 @@ scanList(){ # Prevent grep from printing file path cd "$piholeDir" || exit 1 - # Prevent grep -i matching slowly: http://bit.ly/2xFXtUX + # Prevent grep -i matching slowly: https://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 "(^|(?/dev/null;; - # Create array of regexps # Iterate through each regexp and check whether it matches the domainQuery # If it does, print the matching regexp and continue looping # Input 1 - regexps | Input 2 - domainQuery - "regex" ) awk 'NR==FNR{regexps[$0];next}{for (r in regexps)if($0 ~ r)print r}' \ - <(echo "${lists}") <(echo "${domain}") 2>/dev/null;; + "regex" ) + for list in ${lists}; do + if [[ "${domain}" =~ ${list} ]]; then + printf "%b\n" "${list}"; + fi + done;; * ) grep -i "${esc_domain}" ${lists} /dev/null 2>/dev/null;; esac } diff --git a/advanced/Scripts/setupLCD.sh b/advanced/Scripts/setupLCD.sh index 00eb963f..82523643 100755 --- a/advanced/Scripts/setupLCD.sh +++ b/advanced/Scripts/setupLCD.sh @@ -20,7 +20,7 @@ getInitSys() { elif [ -f /etc/init.d/cron ] && [ ! -h /etc/init.d/cron ]; then SYSTEMD=0 else - echo "Unrecognised init system" + echo "Unrecognized init system" return 1 fi } @@ -70,5 +70,5 @@ setupcon reboot # Start showing the stats on the screen by running the command on another tty: -# http://unix.stackexchange.com/questions/170063/start-a-process-on-a-different-tty +# https://unix.stackexchange.com/questions/170063/start-a-process-on-a-different-tty #setsid sh -c 'exec /usr/local/bin/chronometer.sh <> /dev/tty1 >&0 2>&1' diff --git a/advanced/Scripts/version.sh b/advanced/Scripts/version.sh index f6d4d344..f77ee635 100755 --- a/advanced/Scripts/version.sh +++ b/advanced/Scripts/version.sh @@ -84,6 +84,21 @@ getRemoteVersion(){ # Get the version from the remote origin local daemon="${1}" local version + local cachedVersions + local arrCache + cachedVersions="/etc/pihole/GitHubVersions" + + #If the above file exists, then we can read from that. Prevents overuse of GitHub API + if [[ -f "$cachedVersions" ]]; then + IFS=' ' read -r -a arrCache < "$cachedVersions" + case $daemon in + "pi-hole" ) echo "${arrCache[0]}";; + "AdminLTE" ) echo "${arrCache[1]}";; + "FTL" ) echo "${arrCache[2]}";; + esac + + return 0 + fi version=$(curl --silent --fail "https://api.github.com/repos/pi-hole/${daemon}/releases/latest" | \ awk -F: '$1 ~/tag_name/ { print $2 }' | \ @@ -97,22 +112,48 @@ getRemoteVersion(){ return 0 } +getLocalBranch(){ + # Get the checked out branch of the local directory + local directory="${1}" + local branch + + # Local FTL btranch is stored in /etc/pihole/ftlbranch + if [[ "$1" == "FTL" ]]; then + branch="$(pihole-FTL branch)" + else + cd "${directory}" 2> /dev/null || { echo "${DEFAULT}"; return 1; } + branch=$(git rev-parse --abbrev-ref HEAD || echo "$DEFAULT") + fi + if [[ ! "${branch}" =~ ^v ]]; then + if [[ "${branch}" == "master" ]]; then + echo "" + elif [[ "${branch}" == "HEAD" ]]; then + echo "in detached HEAD state at " + else + echo "${branch} " + fi + else + # Branch started in "v" + echo "release " + fi + return 0 +} + versionOutput() { [[ "$1" == "pi-hole" ]] && GITDIR=$COREGITDIR [[ "$1" == "AdminLTE" ]] && GITDIR=$WEBGITDIR [[ "$1" == "FTL" ]] && GITDIR="FTL" - [[ "$2" == "-c" ]] || [[ "$2" == "--current" ]] || [[ -z "$2" ]] && current=$(getLocalVersion $GITDIR) + [[ "$2" == "-c" ]] || [[ "$2" == "--current" ]] || [[ -z "$2" ]] && current=$(getLocalVersion $GITDIR) && branch=$(getLocalBranch $GITDIR) [[ "$2" == "-l" ]] || [[ "$2" == "--latest" ]] || [[ -z "$2" ]] && latest=$(getRemoteVersion "$1") if [[ "$2" == "-h" ]] || [[ "$2" == "--hash" ]]; then - [[ "$3" == "-c" ]] || [[ "$3" == "--current" ]] || [[ -z "$3" ]] && curHash=$(getLocalHash "$GITDIR") + [[ "$3" == "-c" ]] || [[ "$3" == "--current" ]] || [[ -z "$3" ]] && curHash=$(getLocalHash "$GITDIR") && branch=$(getLocalBranch $GITDIR) [[ "$3" == "-l" ]] || [[ "$3" == "--latest" ]] || [[ -z "$3" ]] && latHash=$(getRemoteHash "$1" "$(cd "$GITDIR" 2> /dev/null && git rev-parse --abbrev-ref HEAD)") fi - if [[ -n "$current" ]] && [[ -n "$latest" ]]; then - output="${1^} version is $current (Latest: $latest)" + output="${1^} version is $branch$current (Latest: $latest)" elif [[ -n "$current" ]] && [[ -z "$latest" ]]; then - output="Current ${1^} version is $current" + output="Current ${1^} version is $branch$current." elif [[ -z "$current" ]] && [[ -n "$latest" ]]; then output="Latest ${1^} version is $latest" elif [[ "$curHash" == "N/A" ]] || [[ "$latHash" == "N/A" ]]; then @@ -162,7 +203,7 @@ Repositories: Options: -c, --current Return the current version -l, --latest Return the latest version - --hash Return the Github hash from your local repositories + --hash Return the GitHub hash from your local repositories -h, --help Show this help dialog" exit 0 } diff --git a/advanced/Scripts/webpage.sh b/advanced/Scripts/webpage.sh index 829ba57b..1f7cc728 100755 --- a/advanced/Scripts/webpage.sh +++ b/advanced/Scripts/webpage.sh @@ -10,17 +10,22 @@ # This file is copyright under the latest version of the EUPL. # Please see LICENSE file for your rights under this license. -readonly setupVars="/etc/pihole/setupVars.conf" readonly dnsmasqconfig="/etc/dnsmasq.d/01-pihole.conf" readonly dhcpconfig="/etc/dnsmasq.d/02-pihole-dhcp.conf" readonly FTLconf="/etc/pihole/pihole-FTL.conf" # 03 -> wildcards readonly dhcpstaticconfig="/etc/dnsmasq.d/04-pihole-static-dhcp.conf" -readonly PI_HOLE_BIN_DIR="/usr/local/bin" readonly dnscustomfile="/etc/pihole/custom.list" +readonly dnscustomcnamefile="/etc/dnsmasq.d/05-pihole-custom-cname.conf" readonly gravityDBfile="/etc/pihole/gravity.db" +# Source install script for ${setupVars}, ${PI_HOLE_BIN_DIR} and valid_ip() +readonly PI_HOLE_FILES_DIR="/etc/.pihole" +# shellcheck disable=SC2034 # used in basic-install +PH_TEST="true" +source "${PI_HOLE_FILES_DIR}/automated install/basic-install.sh" + coltable="/opt/pihole/COL_TABLE" if [[ -f ${coltable} ]]; then source ${coltable} @@ -36,7 +41,6 @@ Options: -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 @@ -179,7 +183,6 @@ ProcessDNSSettings() { if [[ "${DNSSEC}" == true ]]; then echo "dnssec -trust-anchor=.,19036,8,2,49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5 trust-anchor=.,20326,8,2,E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D " >> "${dnsmasqconfig}" fi @@ -211,8 +214,34 @@ trust-anchor=.,20326,8,2,E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC68345710423 fi if [[ "${CONDITIONAL_FORWARDING}" == true ]]; then - add_dnsmasq_setting "server=/${CONDITIONAL_FORWARDING_DOMAIN}/${CONDITIONAL_FORWARDING_IP}" - add_dnsmasq_setting "server=/${CONDITIONAL_FORWARDING_REVERSE}/${CONDITIONAL_FORWARDING_IP}" + # Convert legacy "conditional forwarding" to rev-server configuration + REV_SERVER=true + add_setting "REV_SERVER" "true" + + REV_SERVER_DOMAIN="${CONDITIONAL_FORWARDING_DOMAIN}" + add_setting "REV_SERVER_DOMAIN" "${REV_SERVER_DOMAIN}" + + REV_SERVER_TARGET="${CONDITIONAL_FORWARDING_IP}" + add_setting "REV_SERVER_TARGET" "${REV_SERVER_TARGET}" + + # Remove obsolete settings from setupVars.conf + delete_setting "CONDITIONAL_FORWARDING" + delete_setting "CONDITIONAL_FORWARDING_REVERSE" + delete_setting "CONDITIONAL_FORWARDING_DOMAIN" + delete_setting "CONDITIONAL_FORWARDING_IP" + + # Convert existing input to /24 subnet (preserves legacy behavior) + # This sed converts "192.168.1.2" to "192.168.1.0/24" + # shellcheck disable=2001 + REV_SERVER_CIDR="$(sed "s+\\.[0-9]*$+\\.0/24+" <<< "${REV_SERVER_TARGET}")" + add_setting "REV_SERVER_CIDR" "${REV_SERVER_CIDR}" + fi + + if [[ "${REV_SERVER}" == true ]]; then + add_dnsmasq_setting "rev-server=${REV_SERVER_CIDR},${REV_SERVER_TARGET}" + if [ -n "${REV_SERVER_DOMAIN}" ]; then + add_dnsmasq_setting "server=/${REV_SERVER_DOMAIN}/${REV_SERVER_TARGET}" + fi fi # Prevent Firefox from automatically switching over to DNS-over-HTTPS @@ -227,7 +256,16 @@ SetDNSServers() { IFS=',' read -r -a array <<< "${args[2]}" for index in "${!array[@]}" do - add_setting "PIHOLE_DNS_$((index+1))" "${array[index]}" + # Replace possible "\#" by "#". This fixes AdminLTE#1427 + local ip + ip="${array[index]//\\#/#}" + + if valid_ip "${ip}" || valid_ip6 "${ip}" ; then + add_setting "PIHOLE_DNS_$((index+1))" "${ip}" + else + echo -e " ${CROSS} Invalid IP has been passed" + exit 1 + fi done if [[ "${args[3]}" == "domain-needed" ]]; then @@ -248,16 +286,13 @@ SetDNSServers() { change_setting "DNSSEC" "false" fi - if [[ "${args[6]}" == "conditional_forwarding" ]]; then - change_setting "CONDITIONAL_FORWARDING" "true" - change_setting "CONDITIONAL_FORWARDING_IP" "${args[7]}" - change_setting "CONDITIONAL_FORWARDING_DOMAIN" "${args[8]}" - change_setting "CONDITIONAL_FORWARDING_REVERSE" "${args[9]}" + if [[ "${args[6]}" == "rev-server" ]]; then + change_setting "REV_SERVER" "true" + change_setting "REV_SERVER_CIDR" "${args[7]}" + change_setting "REV_SERVER_TARGET" "${args[8]}" + change_setting "REV_SERVER_DOMAIN" "${args[9]}" else - change_setting "CONDITIONAL_FORWARDING" "false" - delete_setting "CONDITIONAL_FORWARDING_IP" - delete_setting "CONDITIONAL_FORWARDING_DOMAIN" - delete_setting "CONDITIONAL_FORWARDING_REVERSE" + change_setting "REV_SERVER" "false" fi ProcessDNSSettings @@ -402,22 +437,42 @@ SetWebUILayout() { change_setting "WEBUIBOXEDLAYOUT" "${args[2]}" } +SetWebUITheme() { + change_setting "WEBTHEME" "${args[2]}" +} + +CheckUrl(){ + local regex + # Check for characters NOT allowed in URLs + regex="[^a-zA-Z0-9:/?&%=~._-]" + if [[ "${1}" =~ ${regex} ]]; then + return 1 + else + return 0 + fi +} + CustomizeAdLists() { local address address="${args[3]}" local comment comment="${args[4]}" - if [[ "${args[2]}" == "enable" ]]; then - sqlite3 "${gravityDBfile}" "UPDATE adlist SET enabled = 1 WHERE address = '${address}'" - elif [[ "${args[2]}" == "disable" ]]; then - sqlite3 "${gravityDBfile}" "UPDATE adlist SET enabled = 0 WHERE address = '${address}'" - elif [[ "${args[2]}" == "add" ]]; then - sqlite3 "${gravityDBfile}" "INSERT OR IGNORE INTO adlist (address, comment) VALUES ('${address}', '${comment}')" - elif [[ "${args[2]}" == "del" ]]; then - sqlite3 "${gravityDBfile}" "DELETE FROM adlist WHERE address = '${address}'" + if CheckUrl "${address}"; then + if [[ "${args[2]}" == "enable" ]]; then + sqlite3 "${gravityDBfile}" "UPDATE adlist SET enabled = 1 WHERE address = '${address}'" + elif [[ "${args[2]}" == "disable" ]]; then + sqlite3 "${gravityDBfile}" "UPDATE adlist SET enabled = 0 WHERE address = '${address}'" + elif [[ "${args[2]}" == "add" ]]; then + sqlite3 "${gravityDBfile}" "INSERT OR IGNORE INTO adlist (address, comment) VALUES ('${address}', '${comment}')" + elif [[ "${args[2]}" == "del" ]]; then + sqlite3 "${gravityDBfile}" "DELETE FROM adlist WHERE address = '${address}'" + else + echo "Not permitted" + return 1 + fi else - echo "Not permitted" + echo "Invalid Url" return 1 fi } @@ -463,32 +518,6 @@ RemoveDHCPStaticAddress() { sed -i "/dhcp-host=${mac}.*/d" "${dhcpstaticconfig}" } -SetHostRecord() { - if [[ "${1}" == "-h" ]] || [[ "${1}" == "--help" ]]; then - echo "Usage: pihole -a hostrecord [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 -e " ${TICK} Setting host record for ${args[2]} to ${args[3]}" - else - change_setting "HOSTRECORD" "" - echo -e " ${TICK} Removing host record" - fi - - ProcessDNSSettings - - # Restart dnsmasq to load new configuration - RestartDNS -} - SetAdminEmail() { if [[ "${1}" == "-h" ]] || [[ "${1}" == "--help" ]]; then echo "Usage: pihole -a email
@@ -502,6 +531,16 @@ Options: fi if [[ -n "${args[2]}" ]]; then + + # Sanitize email address in case of security issues + # Regex from https://stackoverflow.com/a/2138832/4065967 + local regex + regex="^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\$" + if [[ ! "${args[2]}" =~ ${regex} ]]; then + echo -e " ${CROSS} Invalid email address" + exit 0 + fi + change_setting "ADMIN_EMAIL" "${args[2]}" echo -e " ${TICK} Setting admin contact to ${args[2]}" else @@ -597,6 +636,7 @@ SetPrivacyLevel() { # Set privacy level. Minimum is 0, maximum is 4 if [ "${args[2]}" -ge 0 ] && [ "${args[2]}" -le 4 ]; then changeFTLsetting "PRIVACYLEVEL" "${args[2]}" + pihole restartdns reload-lists fi } @@ -622,6 +662,28 @@ RemoveCustomDNSAddress() { RestartDNS } +AddCustomCNAMERecord() { + echo -e " ${TICK} Adding custom CNAME record..." + + domain="${args[2]}" + target="${args[3]}" + echo "cname=${domain},${target}" >> "${dnscustomcnamefile}" + + # Restart dnsmasq to load new custom CNAME records + RestartDNS +} + +RemoveCustomCNAMERecord() { + echo -e " ${TICK} Removing custom CNAME record..." + + domain="${args[2]}" + target="${args[3]}" + sed -i "/cname=${domain},${target}/d" "${dnscustomcnamefile}" + + # Restart dnsmasq to update removed custom CNAME records + RestartDNS +} + main() { args=("$@") @@ -640,12 +702,12 @@ main() { "enabledhcp" ) EnableDHCP;; "disabledhcp" ) DisableDHCP;; "layout" ) SetWebUILayout;; + "theme" ) SetWebUITheme;; "-h" | "--help" ) helpFunc;; "privacymode" ) SetPrivacyMode;; "resolve" ) ResolutionSettings;; "addstaticdhcp" ) AddDHCPStaticAddress;; "removestaticdhcp" ) RemoveDHCPStaticAddress;; - "-r" | "hostrecord" ) SetHostRecord "$3";; "-e" | "email" ) SetAdminEmail "$3";; "-i" | "interface" ) SetListeningMode "$@";; "-t" | "teleporter" ) Teleporter;; @@ -655,6 +717,8 @@ main() { "-l" | "privacylevel" ) SetPrivacyLevel;; "addcustomdns" ) AddCustomDNSAddress;; "removecustomdns" ) RemoveCustomDNSAddress;; + "addcustomcname" ) AddCustomCNAMERecord;; + "removecustomcname" ) RemoveCustomCNAMERecord;; * ) helpFunc;; esac diff --git a/advanced/Templates/gravity.db.sql b/advanced/Templates/gravity.db.sql index e543bd19..fcf3489b 100644 --- a/advanced/Templates/gravity.db.sql +++ b/advanced/Templates/gravity.db.sql @@ -10,17 +10,18 @@ CREATE TABLE "group" date_modified INTEGER NOT NULL DEFAULT (cast(strftime('%s', 'now') as int)), description TEXT ); -INSERT INTO "group" (id,enabled,name) VALUES (0,1,'Unassociated'); +INSERT INTO "group" (id,enabled,name,description) VALUES (0,1,'Default','The default group'); CREATE TABLE domainlist ( id INTEGER PRIMARY KEY AUTOINCREMENT, type INTEGER NOT NULL DEFAULT 0, - domain TEXT UNIQUE NOT NULL, + domain TEXT NOT NULL, enabled BOOLEAN NOT NULL DEFAULT 1, date_added INTEGER NOT NULL DEFAULT (cast(strftime('%s', 'now') as int)), date_modified INTEGER NOT NULL DEFAULT (cast(strftime('%s', 'now') as int)), - comment TEXT + comment TEXT, + UNIQUE(domain, type) ); CREATE TABLE adlist @@ -52,7 +53,7 @@ CREATE TABLE info value TEXT NOT NULL ); -INSERT INTO "info" VALUES('version','11'); +INSERT INTO "info" VALUES('version','12'); CREATE TABLE domain_audit ( @@ -167,7 +168,7 @@ CREATE TRIGGER tr_group_update AFTER UPDATE ON "group" CREATE TRIGGER tr_group_zero AFTER DELETE ON "group" BEGIN - INSERT OR IGNORE INTO "group" (id,enabled,name) VALUES (0,1,'Unassociated'); + INSERT OR IGNORE INTO "group" (id,enabled,name) VALUES (0,1,'Default'); END; CREATE TRIGGER tr_domainlist_delete AFTER DELETE ON domainlist diff --git a/advanced/Templates/pihole-FTL.service b/advanced/Templates/pihole-FTL.service index f32a5e89..6af449f3 100644 --- a/advanced/Templates/pihole-FTL.service +++ b/advanced/Templates/pihole-FTL.service @@ -1,8 +1,8 @@ #!/usr/bin/env bash ### BEGIN INIT INFO # Provides: pihole-FTL -# Required-Start: $remote_fs $syslog -# Required-Stop: $remote_fs $syslog +# Required-Start: $remote_fs $syslog $network +# Required-Stop: $remote_fs $syslog $network # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: pihole-FTL daemon @@ -10,7 +10,7 @@ ### END INIT INFO FTLUSER=pihole -PIDFILE=/var/run/pihole-FTL.pid +PIDFILE=/run/pihole-FTL.pid get_pid() { # First, try to obtain PID from PIDFILE @@ -37,12 +37,12 @@ start() { touch /var/log/pihole-FTL.log /var/log/pihole.log touch /run/pihole-FTL.pid /run/pihole-FTL.port touch /etc/pihole/dhcp.leases - mkdir -p /var/run/pihole + mkdir -p /run/pihole mkdir -p /var/log/pihole - chown pihole:pihole /var/run/pihole /var/log/pihole + chown pihole:pihole /run/pihole /var/log/pihole # Remove possible leftovers from previous pihole-FTL processes rm -f /dev/shm/FTL-* 2> /dev/null - rm /var/run/pihole/FTL.sock 2> /dev/null + rm /run/pihole/FTL.sock 2> /dev/null # Ensure that permissions are set so that pihole-FTL can edit all necessary files chown pihole:pihole /run/pihole-FTL.pid /run/pihole-FTL.port chown pihole:pihole /etc/pihole /etc/pihole/dhcp.leases 2> /dev/null @@ -50,7 +50,7 @@ start() { chmod 0644 /var/log/pihole-FTL.log /run/pihole-FTL.pid /run/pihole-FTL.port /var/log/pihole.log # Chown database files to the user FTL runs as. We ignore errors as the files may not (yet) exist chown pihole:pihole /etc/pihole/pihole-FTL.db /etc/pihole/gravity.db 2> /dev/null - if setcap CAP_NET_BIND_SERVICE,CAP_NET_RAW,CAP_NET_ADMIN+eip "$(which pihole-FTL)"; then + if setcap CAP_NET_BIND_SERVICE,CAP_NET_RAW,CAP_NET_ADMIN,CAP_SYS_NICE+eip "$(which pihole-FTL)"; then su -s /bin/sh -c "/usr/bin/pihole-FTL" "$FTLUSER" else echo "Warning: Starting pihole-FTL as root because setting capabilities is not supported on this system" diff --git a/advanced/Templates/pihole.cron b/advanced/Templates/pihole.cron index 8dc98721..ba89efdb 100644 --- a/advanced/Templates/pihole.cron +++ b/advanced/Templates/pihole.cron @@ -10,7 +10,7 @@ # # # This file is under source-control of the Pi-hole installation and update -# scripts, any changes made to this file will be overwritten when the softare +# scripts, any changes made to this file will be overwritten when the software # is updated or re-installed. Please make any changes to the appropriate crontab # or other cron file snippets. diff --git a/advanced/bash-completion/pihole b/advanced/bash-completion/pihole index cea36060..88282b02 100644 --- a/advanced/bash-completion/pihole +++ b/advanced/bash-completion/pihole @@ -15,7 +15,7 @@ _pihole() { COMPREPLY=( $(compgen -W "${opts_lists}" -- ${cur}) ) ;; "admin") - opts_admin="celsius email fahrenheit hostrecord interface kelvin password privacylevel" + opts_admin="celsius email fahrenheit interface kelvin password privacylevel" COMPREPLY=( $(compgen -W "${opts_admin}" -- ${cur}) ) ;; "checkout") diff --git a/advanced/blockingpage.css b/advanced/blockingpage.css index e74844d1..9f82a27e 100644 --- a/advanced/blockingpage.css +++ b/advanced/blockingpage.css @@ -6,45 +6,46 @@ * 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"; } +.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"; } +#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 occurred. 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 "; } /* cname.com */ -.cname:after { content: ", which may be blocked by Pi-hole."; } +.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 "; } /* cname.com */ +.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"; } +.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:"; } +#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"; } +#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"; } +#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 "; } +footer span::before { content: "Page generated on "; } /* Hide whitelisting form entirely */ /* #bpWLButtons { display: none; } */ + /* Text Customisation Options <=============================== */ /* http://necolas.github.io/normalize.css ======> */ @@ -98,7 +99,7 @@ html { font-size: 62.5%; } a { color: #3c8dbc; text-decoration: none; } a:hover { color: #72afda; text-decoration: underline; } -b { color: rgb(68,68,68); } +b { color: rgb(68, 68, 68); } p { margin: 0; } label, .buttons a { @@ -111,7 +112,7 @@ label, .buttons a { label, .buttons *:not([disabled]) { cursor: pointer; } /* Touch device dark tap highlight */ -header h1 a, label, .buttons * { -webkit-tap-highlight-color: transparent; } +header h1 a, label, .buttons * { -webkit-tap-highlight-color: transparent; } /* Webkit Focus Glow */ textarea, input, button { outline: none; } @@ -120,14 +121,20 @@ textarea, input, button { outline: none; } 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-display: swap; + src: local("Source Sans Pro Regular"), local("SourceSansPro-Regular"), + url("/admin/style/vendor/SourceSansPro/source-sans-pro-v13-latin-regular.woff2") format("woff2"), + url("/admin/style/vendor/SourceSansPro/source-sans-pro-v13-latin-regular.woff") format("woff"); } @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"); + font-display: swap; + src: local("Source Sans Pro Bold"), local("SourceSansPro-Bold"), + url("/admin/style/vendor/SourceSansPro/source-sans-pro-v13-latin-700.woff2") format("woff2"), + url("/admin/style/vendor/SourceSansPro/source-sans-pro-v13-latin-700.woff") format("woff"); } body { @@ -138,14 +145,14 @@ body { } /* 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 { 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); + box-shadow: 0 0 8px rgba(0, 0, 0, 0.5); } header { @@ -164,15 +171,15 @@ header h1, header h1 a, header .spc, header #bpAlt label { } h1 a { - background-color: rgba(0,0,0,0.1); - font-family: "Helvetica Neue", Helvetica, Arial ,sans-serif; + background-color: rgba(0, 0, 0, 0.1); + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 2rem; - font-weight: normal; + font-weight: 400; min-width: 230px; - text-align: center; + text-align: center; } -h1 a:hover, header #bpAlt:hover { background-color: rgba(0,0,0,0.12); color: inherit; text-decoration: none; } +h1 a:hover, header #bpAlt:hover { background-color: rgba(0, 0, 0, 0.12); color: inherit; text-decoration: none; } header .spc { width: 100%; } @@ -180,13 +187,14 @@ header #bpAlt label { background: url("/admin/img/logo.svg") no-repeat center left 15px; background-size: 15px 23px; padding: 0 15px; - text-indent: 30px; + text-indent: 30px; } -[type=checkbox][id$="Toggle"] { display: none; } -[type=checkbox][id$="Toggle"]:checked ~ #bpAbout, -[type=checkbox][id$="Toggle"]:checked ~ #bpMoreInfo { - display: block; } +[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 { @@ -197,28 +205,28 @@ header #bpAlt label { top: 0; opacity: 0; position: absolute; - width: 100%; + width: 100%; } #bpAbout { background: #3c8dbc; border-bottom-left-radius: 5px; - border: 1px solid #FFF; + border: 1px solid #fff; border-right-width: 0; - box-shadow: -1px 1px 1px rgba(0,0,0,0.12); + 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; + width: 280px; z-index: 1; } .aboutPH { box-sizing: border-box; - color: rgba(255,255,255,0.8); + color: rgba(255, 255, 255, 0.8); display: block; padding: 10px; width: 100%; @@ -237,7 +245,7 @@ header #bpAlt label { .aboutPH p { margin: 10px 0; } .aboutPH small { display: block; font-size: 1.2rem; } -.aboutLink { +.aboutLink { background: #fff; border-top: 1px solid #ddd; display: table; @@ -261,16 +269,16 @@ main { #bpOutput { background: #00c0ef; border-radius: 3px; - border: 1px solid rgba(0,0,0,0.1); + border: 1px solid rgba(0, 0, 0, 0.1); color: #fff; font-size: 1.4rem; margin-bottom: 10px; margin-top: 5px; - padding: 15px; + 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; +#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 011 1v1a1.371 1.371 0 01-1 1H1a1.371 1.371 0 01-1-1v-1a1.371 1.371 0 011-1h1V8H1a1.371 1.371 0 01-1-1V6a1.371 1.371 0 011-1h3a1.371 1.371 0 011 1v5h1zM3.5 0A1.5 1.5 0 112 1.5 1.5 1.5 0 013.5 0z'/%3E%3C/svg%3E") no-repeat center left; display: block; font-size: 1.8rem; text-indent: 15px; @@ -281,8 +289,8 @@ main { #bpOutput.error { background: #dd4b39; } .blockMsg, .flagMsg { - font: bold 1.8rem Consolas, Courier, monospace; - padding: 5px 10px 10px 10px; + font: 700 1.8rem Consolas, Courier, monospace; + padding: 5px 10px 10px; text-indent: 15px; } @@ -298,7 +306,7 @@ main { -moz-appearance: none; -webkit-appearance: none; border-radius: 3px; - border: 1px solid rgba(0,0,0,0.1); + border: 1px solid rgba(0, 0, 0, 0.1); box-sizing: content-box; display: table-cell; font-size: 1.65rem; @@ -309,46 +317,48 @@ main { text-align: center; vertical-align: top; white-space: nowrap; - width: auto; + 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; + 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); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); } -/* Input border colour */ +/* Input border color */ .buttons *:not([disabled]):hover, .buttons input:focus { - border-color: rgba(0,0,0,0.25); + border-color: rgba(0, 0, 0, 0.25); } -#bpButtons * { width: 50%; color: #FFF; } -#bpBack { background-color: #00a65a; } -#bpInfo { background-color: #3c8dbc; } +#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); } +#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; } +input[type="password"] { font-size: 1.5rem; } + +@-webkit-keyframes slidein { from { max-height: 0; opacity: 0; } to { max-height: 300px; opacity: 1; } } @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; } +#bpMoreToggle:checked ~ #bpMoreInfo { display: block; margin-top: 8px; -webkit-animation: slidein 0.05s linear; 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; + margin: 5px 0 0; overflow: auto; padding: 0 5px; -webkit-overflow-scrolling: touch; @@ -367,13 +377,13 @@ footer { border-top: 1px solid #d2d6de; color: #444; font: 1.2rem Consolas, Courier, monospace; - padding: 8px; + 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::before { content: "Generated "; } footer span { display: block; } } diff --git a/advanced/dnsmasq.conf.original b/advanced/dnsmasq.conf.original index 9e4cc92e..6758f0b8 100644 --- a/advanced/dnsmasq.conf.original +++ b/advanced/dnsmasq.conf.original @@ -46,7 +46,7 @@ #resolv-file= # By default, dnsmasq will send queries to any of the upstream -# servers it knows about and tries to favour servers to are known +# servers it knows about and tries to favor servers to are known # to be up. Uncommenting this forces dnsmasq to try each query # with each server strictly in the order they appear in # /etc/resolv.conf @@ -189,7 +189,7 @@ # add names to the DNS for the IPv6 address of SLAAC-configured dual-stack # hosts. Use the DHCPv4 lease to derive the name, network segment and # MAC address and assume that the host will also have an -# IPv6 address calculated using the SLAAC alogrithm. +# IPv6 address calculated using the SLAAC algorithm. #dhcp-range=1234::, ra-names # Do Router Advertisements, BUT NOT DHCP for this subnet. @@ -210,7 +210,7 @@ #dhcp-range=1234::, ra-stateless, ra-names # Do router advertisements for all subnets where we're doing DHCPv6 -# Unless overriden by ra-stateless, ra-names, et al, the router +# Unless overridden by ra-stateless, ra-names, et al, the router # advertisements will have the M and O bits set, so that the clients # get addresses and configuration from DHCPv6, and the A bit reset, so the # clients don't use SLAAC addresses. @@ -281,7 +281,7 @@ # Give a fixed IPv6 address and name to client with # DUID 00:01:00:01:16:d2:83:fc:92:d4:19:e2:d8:b2 # Note the MAC addresses CANNOT be used to identify DHCPv6 clients. -# Note also the they [] around the IPv6 address are obilgatory. +# Note also the they [] around the IPv6 address are obligatory. #dhcp-host=id:00:01:00:01:16:d2:83:fc:92:d4:19:e2:d8:b2, fred, [1234::5] # Ignore any clients which are not specified in dhcp-host lines @@ -404,14 +404,14 @@ #dhcp-option=vendor:MSFT,2,1i # Send the Encapsulated-vendor-class ID needed by some configurations of -# Etherboot to allow is to recognise the DHCP server. +# Etherboot to allow is to recognize the DHCP server. #dhcp-option=vendor:Etherboot,60,"Etherboot" # Send options to PXELinux. Note that we need to send the options even # though they don't appear in the parameter request list, so we need # to use dhcp-option-force here. # See http://syslinux.zytor.com/pxe.php#special for details. -# Magic number - needed before anything else is recognised +# Magic number - needed before anything else is recognized #dhcp-option-force=208,f1:00:74:7e # Configuration file name #dhcp-option-force=209,configs/common diff --git a/advanced/index.php b/advanced/index.php index b0c4a7c3..4356f0b0 100644 --- a/advanced/index.php +++ b/advanced/index.php @@ -6,8 +6,8 @@ * 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"]); +// Sanitize SERVER_NAME output +$serverName = htmlspecialchars($_SERVER["SERVER_NAME"]); // Remove external ipv6 brackets if any $serverName = preg_replace('/^\[(.*)\]$/', '${1}', $serverName); @@ -41,7 +41,7 @@ $validExtTypes = array("asp", "htm", "html", "php", "rss", "xml", ""); $currentUrlExt = pathinfo($_SERVER["REQUEST_URI"], PATHINFO_EXTENSION); // Set mobile friendly viewport -$viewPort = ''; +$viewPort = ''; // Set response header function setHeader($type = "x") { @@ -50,16 +50,29 @@ function setHeader($type = "x") { } // Determine block page type -if ($serverName === "pi.hole") { +if ($serverName === "pi.hole" + || (!empty($_SERVER["VIRTUAL_HOST"]) && $serverName === $_SERVER["VIRTUAL_HOST"])) { // Redirect to Web Interface exit(header("Location: /admin")); } elseif (filter_var($serverName, FILTER_VALIDATE_IP) || in_array($serverName, $authorizedHosts)) { // Set Splash Page output $splashPage = " - - $viewPort - -
Pi-hole: Your black hole for Internet advertisements
Did you mean to go to the admin panel? + + + + + $viewPort + ● $serverName + + + + + Pi-hole logo +
+

Pi-hole: Your black hole for Internet advertisements

+ Did you mean to go to the admin panel? + + "; // Set splash/landing page based off presence of $landPage @@ -68,25 +81,42 @@ if ($serverName === "pi.hole") { // 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 + // Render splash/landing page when directly browsing via IP or authorized hostname exit($renderPage); } elseif ($currentUrlExt === "js") { - // Serve Pi-hole Javascript for blocked domains requesting 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().' - - + exit(setHeader().' + + + + + + + '); } 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 = 'Blocked by Pi-hole'; - exit(setHeader()." - $viewPort + $blockImg = ' + + + + + Blocked by Pi-hole + + + '; + exit(setHeader()." + + + + $viewPort + $blockImg "); } @@ -131,7 +161,12 @@ ini_set("default_socket_timeout", 3); 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); + $queryAdsURL = sprintf( + "http://127.0.0.1:%s/admin/scripts/pi-hole/php/queryads.php?domain=%s&bp", + $_SERVER["SERVER_PORT"], + $serverName + ); + $queryAds = file($queryAdsURL, FILE_IGNORE_NEW_LINES); $queryAds = array_values(array_filter(preg_replace("/data:\s+/", "", $queryAds))); $queryTime = sprintf("%.0f", (microtime(true)-$_SERVER["REQUEST_TIME_FLOAT"]) - $preQueryTime); @@ -209,12 +244,12 @@ $phVersion = exec("cd /etc/.pihole/ && git describe --long --tags"); 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 +// Please Note: Text is added via CSS to allow an admin to provide a localized // language without the need to edit this file setHeader(); ?> - + - + - + - - + + ● <?=$serverName ?> - +