Unverified Commit 5ba41356 authored by Jacob Salmela's avatar Jacob Salmela Committed by GitHub
Browse files

Merge pull request #1776 from pi-hole/release/3.2

Pi-hole core v3.2
parents b0eceddc b3e969f0
**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)._
**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.
......@@ -3,4 +3,3 @@
*.swp
__pycache__
.cache
.pullapprove.yml
This diff is collapsed.
......@@ -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
# 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"
This diff is collapsed.
......@@ -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
......@@ -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
......
This diff is collapsed.
......@@ -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
......@@ -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
#!/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"
#!/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
......
/* 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; }
}
var x = "Pi-hole: A black hole for Internet advertisements."
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment