diff --git a/.codespellignore b/.codespellignore
new file mode 100644
index 0000000000..7f391ce767
--- /dev/null
+++ b/.codespellignore
@@ -0,0 +1,4 @@
+doubleclick
+wan
+nwe
+reenable
diff --git a/.editorconfig b/.editorconfig
index a50f2f70ba..714ed210b9 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -13,26 +13,8 @@ tab_width = 4
charset = utf-8
trim_trailing_whitespace = true
-# Matches multiple files with brace expansion notation
-# Set default charset
-[*.{js,py}]
-charset = utf-8
-
-# 4 space indentation
-[*.py]
-indent_style = space
-indent_size = 4
-
-# Tab indentation (no size specified)
-[Makefile]
-indent_style = tab
+[*.yml]
+tab_width = 2
-# Indentation override for all JS under lib directory
-[scripts/**.js]
-indent_style = space
-indent_size = 2
-
-# Matches the exact files either package.json or .travis.yml
-[{package.json,.travis.yml}]
-indent_style = space
-indent_size = 2
+[*.md]
+tab_width = 2
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index e10beb30f8..20163f5eb9 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -7,4 +7,37 @@ updates:
day: saturday
time: "10:00"
open-pull-requests-limit: 10
- target-branch: developement
\ No newline at end of file
+ target-branch: development
+ reviewers:
+ - "pi-hole/core-maintainers"
+- package-ecosystem: pip
+ directory: "/test"
+ schedule:
+ interval: weekly
+ day: saturday
+ time: "10:00"
+ open-pull-requests-limit: 10
+ target-branch: development
+ reviewers:
+ - "pi-hole/core-maintainers"
+# As above, but for development-v6
+- package-ecosystem: github-actions
+ directory: "/"
+ schedule:
+ interval: weekly
+ day: saturday
+ time: "10:00"
+ open-pull-requests-limit: 10
+ target-branch: development-v6
+ reviewers:
+ - "pi-hole/core-maintainers"
+- package-ecosystem: pip
+ directory: "/test"
+ schedule:
+ interval: weekly
+ day: saturday
+ time: "10:00"
+ open-pull-requests-limit: 10
+ target-branch: development-v6
+ reviewers:
+ - "pi-hole/core-maintainers"
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index a4f67b81f4..9cfd8a61d6 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -25,16 +25,16 @@ jobs:
steps:
-
name: Checkout repository
- uses: actions/checkout@v2
+ uses: actions/checkout@v4.1.2
# Initializes the CodeQL tools for scanning.
-
name: Initialize CodeQL
- uses: github/codeql-action/init@v1
+ uses: github/codeql-action/init@v3
with:
languages: 'python'
-
name: Autobuild
- uses: github/codeql-action/autobuild@v1
+ uses: github/codeql-action/autobuild@v3
-
name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v1
+ uses: github/codeql-action/analyze@v3
diff --git a/.github/workflows/merge-conflict.yml b/.github/workflows/merge-conflict.yml
new file mode 100644
index 0000000000..d86e9cd189
--- /dev/null
+++ b/.github/workflows/merge-conflict.yml
@@ -0,0 +1,21 @@
+name: "Check for merge conflicts"
+on:
+ # So that PRs touching the same files as the push are updated
+ push:
+ # So that the `dirtyLabel` is removed if conflicts are resolve
+ # We recommend `pull_request_target` so that github secrets are available.
+ # In `pull_request` we wouldn't be able to change labels of fork PRs
+ pull_request_target:
+ types: [synchronize]
+
+jobs:
+ main:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check if PRs are have merge conflicts
+ uses: eps1lon/actions-label-merge-conflict@v2.1.0
+ with:
+ dirtyLabel: "PR: Merge Conflict"
+ repoToken: "${{ secrets.GITHUB_TOKEN }}"
+ commentOnDirty: "This pull request has conflicts, please resolve those before we can evaluate the pull request."
+ commentOnClean: "Conflicts have been resolved."
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index 783f141967..c6a581ffde 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -2,24 +2,47 @@ name: Mark stale issues
on:
schedule:
- - cron: '0 * * * *'
+ - cron: '0 8 * * *'
workflow_dispatch:
+ issue_comment:
-jobs:
- stale:
+env:
+ stale_label: stale
+jobs:
+ stale_action:
+ if: github.event_name != 'issue_comment'
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- - uses: actions/stale@v4
- with:
- repo-token: ${{ secrets.GITHUB_TOKEN }}
- days-before-stale: 30
- days-before-close: 5
- stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Please comment or update this issue or it will be closed in 5 days.'
- stale-issue-label: 'stale'
- exempt-issue-labels: 'Internal, Fixed in next release, Bug: Confirmed, Documentation Needed'
- exempt-all-issue-assignees: true
- operations-per-run: 300
+ - uses: actions/stale@v9.0.0
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ days-before-stale: 30
+ days-before-close: 5
+ stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Please comment or update this issue or it will be closed in 5 days.'
+ stale-issue-label: '${{ env.stale_label }}'
+ exempt-issue-labels: 'Internal, Fixed in next release, Bug: Confirmed, Documentation Needed'
+ exempt-all-issue-assignees: true
+ operations-per-run: 300
+ close-issue-reason: 'not_planned'
+
+ remove_stale:
+ # trigger "stale" removal immediately when stale issues are commented on
+ # we need to explicitly check that the trigger does not run on comment on a PR as
+ # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issue_comment-on-issues-only-or-pull-requests-only
+ if: ${{ !github.event.issue.pull_request && github.event_name != 'schedule' }}
+ permissions:
+ contents: read # for actions/checkout
+ issues: write # to edit issues label
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4.1.2
+ - name: Remove 'stale' label
+ run: gh issue edit ${{ github.event.issue.number }} --remove-label ${{ env.stale_label }}
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
diff --git a/.github/workflows/stale_pr.yml b/.github/workflows/stale_pr.yml
new file mode 100644
index 0000000000..9665081899
--- /dev/null
+++ b/.github/workflows/stale_pr.yml
@@ -0,0 +1,35 @@
+name: Close stale PR
+# This action will add a `stale` label and close immediately every PR that meets the following conditions:
+# - it is already marked with "merge conflict" label
+# - there was no update/comment on the PR in the last 30 days.
+
+on:
+ schedule:
+ - cron: '0 10 * * *'
+ workflow_dispatch:
+
+jobs:
+ stale:
+
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ pull-requests: write
+
+ steps:
+ - uses: actions/stale@v9.0.0
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ # Do not automatically mark PR/issue as stale
+ days-before-stale: -1
+ # Override 'days-before-stale' for PR only
+ days-before-pr-stale: 30
+ # Close PRs immediately, after marking them 'stale'
+ days-before-pr-close: 0
+ # only run the action on merge conflict PR
+ any-of-labels: 'PR: Merge Conflict'
+ exempt-pr-labels: 'internal, never-stale, ON HOLD, WIP'
+ exempt-all-pr-assignees: true
+ operations-per-run: 300
+ stale-pr-message: ''
+ close-pr-message: 'Existing merge conflicts have not been addressed. This PR is considered abandoned.'
diff --git a/.github/workflows/sync-back-to-dev.yml b/.github/workflows/sync-back-to-dev.yml
index 5b9fa570e3..9b35a974d1 100644
--- a/.github/workflows/sync-back-to-dev.yml
+++ b/.github/workflows/sync-back-to-dev.yml
@@ -5,23 +5,36 @@ on:
branches:
- master
+# The section is needed to drop the default write-all permissions for all jobs
+# that are granted on `push` event. By specifying any permission explicitly
+# all others are set to none. By using the principle of least privilege the damage a compromised
+# workflow can do (because of an injection or compromised third party tool or
+# action) is restricted. Adding labels to issues, commenting
+# on pull-requests, etc. may need additional permissions:
+#
+# Syntax for this section:
+# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
+#
+# Reference for how to assign permissions on a job-by-job basis:
+# https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs
+#
+# Reference for available permissions that we can enable if needed:
+# https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token
+permissions: {}
+
jobs:
sync-branches:
+ # The job needs to be able to pull the code and create a pull request.
+ permissions:
+ contents: read # for actions/checkout
+ pull-requests: write # to create pull request
+
runs-on: ubuntu-latest
name: Syncing branches
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v4.1.2
- name: Opening pull request
- id: pull
- uses: tretuna/sync-branches@1.4.0
- with:
+ run: gh pr create -B development -H master --title 'Sync master back into development' --body 'Created by Github action' --label 'internal'
+ env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- FROM_BRANCH: 'master'
- TO_BRANCH: 'development'
- - name: Label the pull request to ignore for release note generation
- uses: actions-ecosystem/action-add-labels@v1
- with:
- labels: internal
- repo: ${{ github.repository }}
- number: ${{ steps.pull.outputs.PULL_REQUEST_NUMBER }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index d39852dc2b..d2282d2d62 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -8,44 +8,74 @@ permissions:
contents: read
jobs:
- smoke-test:
+ smoke-tests:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
steps:
- -
- name: Checkout repository
- uses: actions/checkout@v2
- -
- name: Run Smoke Tests
- run: |
- # Ensure scripts in repository are executable
- IFS=$'\n';
- for f in $(find . -name '*.sh'); do if [[ ! -x $f ]]; then echo "$f is not executable" && FAIL=1; fi ;done
- unset IFS;
- # If FAIL is 1 then we fail.
- [[ $FAIL == 1 ]] && exit 1 || echo "Smoke Tests Passed"
+ - name: Checkout repository
+ uses: actions/checkout@v4.1.2
+
+ - name: Check scripts in repository are executable
+ run: |
+ IFS=$'\n';
+ for f in $(find . -name '*.sh'); do if [[ ! -x $f ]]; then echo "$f is not executable" && FAIL=1; fi ;done
+ unset IFS;
+ # If FAIL is 1 then we fail.
+ [[ $FAIL == 1 ]] && exit 1 || echo "Scripts are executable!"
+
+ - name: Spell-Checking
+ uses: codespell-project/actions-codespell@master
+ with:
+ ignore_words_file: .codespellignore
+
+ - name: Get editorconfig-checker
+ uses: editorconfig-checker/action-editorconfig-checker@main # tag v1.0.0 is really out of date
+
+ - name: Run editorconfig-checker
+ run: editorconfig-checker
+
+ - name: Check python code formatting with black
+ uses: psf/black@stable
+ with:
+ src: "./test"
+ options: "--check --diff --color"
distro-test:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
- needs: smoke-test
+ needs: smoke-tests
strategy:
+ fail-fast: false
matrix:
- distro: [debian_9, debian_10, debian_11, ubuntu_16, ubuntu_18, ubuntu_20, ubuntu_21, centos_7, centos_8, fedora_33, fedora_34]
+ distro:
+ [
+ debian_10,
+ debian_11,
+ debian_12,
+ ubuntu_20,
+ ubuntu_22,
+ ubuntu_23,
+ centos_8,
+ centos_9,
+ fedora_38,
+ fedora_39,
+ ]
env:
DISTRO: ${{matrix.distro}}
steps:
- -
- name: Checkout repository
- uses: actions/checkout@v2
- -
- name: Set up Python 3.8
- uses: actions/setup-python@v3
- with:
- python-version: 3.8
- -
- name: Install dependencies
- run: pip install -r test/requirements.txt
- -
- name: Test with tox
- run: tox -c test/tox.${DISTRO}.ini
+ - name: Checkout repository
+ uses: actions/checkout@v4.1.2
+
+ - name: Set up Python 3.10
+ uses: actions/setup-python@v5.0.0
+ with:
+ python-version: "3.10"
+
+ - name: Install wheel
+ run: pip install wheel
+
+ - name: Install dependencies
+ run: pip install -r test/requirements.txt
+
+ - name: Test with tox
+ run: tox -c test/tox.${DISTRO}.ini
diff --git a/.stickler.yml b/.stickler.yml
index 8a2a1ce991..5fdbbf1ec1 100644
--- a/.stickler.yml
+++ b/.stickler.yml
@@ -1,6 +1,10 @@
+---
linters:
shellcheck:
shell: bash
phpcs:
flake8:
max-line-length: 120
+ yamllint:
+ config: ./.yamllint.conf
+ remarklint:
diff --git a/.yamllint.conf b/.yamllint.conf
new file mode 100644
index 0000000000..d1b0953bdf
--- /dev/null
+++ b/.yamllint.conf
@@ -0,0 +1,3 @@
+rules:
+ line-length: disable
+ document-start: disable
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 018b8c5f51..1ea98df296 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -3,5 +3,3 @@
Please read and understand the contribution guide before creating an issue or pull request.
The guide can be found here: [https://docs.pi-hole.net/guides/github/contributing/](https://docs.pi-hole.net/guides/github/contributing/)
-
-
diff --git a/README.md b/README.md
index 8ec737123e..481c59d426 100644
--- a/README.md
+++ b/README.md
@@ -1,28 +1,17 @@
-## This project is part of
+
-https://github.com/arevindh/pihole-speedtest
+# Pi-hole Speedtest
-## About the project
+[](https://gitter.im/pihole-speedtest/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://discord.gg/TW9TfyM)
-This project is just another fun project integrating speedtest to PiHole Web UI.
+Test your connection speed directly in the Pi-hole web interface!
-It will be using speedtest.net on background for testing. More frequent the speed tests more data will used.
+
-What does this mod have in extra ?
+---
-1. Speedtest results of 1/2/4/7/30 days as graph.
-2. Custom speed test server selection.
-3. Detailed speedtest results page.
-4. Ability to schedule speedtest interval.
-
-## Wiki
-
-Wiki is available here https://github.com/arevindh/pihole-speedtest/wiki
+Please go to the [main repository](https://github.com/arevindh/pihole-speedtest) for more information, including (un)installation instructions, pull requests, and issues.
## Disclaimer
-We are not affiliated or endorced by [Pi-hole](https://github.com/pi-hole/AdminLTE)
-
-## Use Official CLI Mode for best results.
-
-[Uninstall Instructions](https://github.com/arevindh/pihole-speedtest/wiki/Uninstalling-Speedtest-Mod)
\ No newline at end of file
+We are not affiliated with or endorsed by [Pi-hole](https://github.com/pi-hole/pi-hole)
diff --git a/advanced/01-pihole.conf b/advanced/01-pihole.conf
index 02bc93bf35..677910f654 100644
--- a/advanced/01-pihole.conf
+++ b/advanced/01-pihole.conf
@@ -29,14 +29,7 @@ bogus-priv
no-resolv
-server=@DNS1@
-server=@DNS2@
-
-interface=@INT@
-
-cache-size=@CACHE_SIZE@
-
log-queries
-log-facility=/var/log/pihole.log
+log-facility=/var/log/pihole/pihole.log
log-async
diff --git a/advanced/GIFs/25Bytes.gif b/advanced/GIFs/25Bytes.gif
deleted file mode 100644
index 472727f293..0000000000
Binary files a/advanced/GIFs/25Bytes.gif and /dev/null differ
diff --git a/advanced/GIFs/26Bytes.gif b/advanced/GIFs/26Bytes.gif
deleted file mode 100644
index 264e471abd..0000000000
Binary files a/advanced/GIFs/26Bytes.gif and /dev/null differ
diff --git a/advanced/GIFs/37Bytes.gif b/advanced/GIFs/37Bytes.gif
deleted file mode 100644
index b3aa80d843..0000000000
Binary files a/advanced/GIFs/37Bytes.gif and /dev/null differ
diff --git a/advanced/GIFs/43Bytes.gif b/advanced/GIFs/43Bytes.gif
deleted file mode 100644
index 9884f476b9..0000000000
Binary files a/advanced/GIFs/43Bytes.gif and /dev/null differ
diff --git a/advanced/Scripts/chronometer.sh b/advanced/Scripts/chronometer.sh
index fddb393677..bb94a857d2 100755
--- a/advanced/Scripts/chronometer.sh
+++ b/advanced/Scripts/chronometer.sh
@@ -14,7 +14,9 @@ LC_NUMERIC=C
# Retrieve stats from FTL engine
pihole-FTL() {
local ftl_port LINE
- ftl_port=$(cat /run/pihole-FTL.port 2> /dev/null)
+ # shellcheck disable=SC1091
+ . /opt/pihole/utils.sh
+ ftl_port=$(getFTLAPIPort)
if [[ -n "$ftl_port" ]]; then
# Open connection to FTL
exec 3<>"/dev/tcp/127.0.0.1/$ftl_port"
@@ -231,7 +233,7 @@ get_sys_stats() {
if [[ -n "${ph_ver_raw[0]}" ]]; then
ph_core_ver="${ph_ver_raw[0]}"
if [[ ${#ph_ver_raw[@]} -eq 2 ]]; then
- # AdminLTE not installed
+ # web not installed
ph_lte_ver="(not installed)"
ph_ftl_ver="${ph_ver_raw[1]}"
else
@@ -503,11 +505,11 @@ chronoFunc() {
fi
printFunc " Pi-hole: " "$ph_status" "$ph_info"
- printFunc " Ads Today: " "$ads_percentage_today%" "$ads_info"
+ printFunc " Blocked: " "$ads_percentage_today%" "$ads_info"
printFunc "Local Qrys: " "$queries_cached_percentage%" "$dns_info"
- printFunc " Blocked: " "$recent_blocked"
- printFunc "Top Advert: " "$top_ad"
+ printFunc "Last Block: " "$recent_blocked"
+ printFunc " Top Block: " "$top_ad"
# Provide more stats on screens with more lines
if [[ "$scr_lines" -eq 17 ]]; then
diff --git a/advanced/Scripts/database_migration/gravity-db.sh b/advanced/Scripts/database_migration/gravity-db.sh
index a7ba60a919..1459ecd9b7 100755
--- a/advanced/Scripts/database_migration/gravity-db.sh
+++ b/advanced/Scripts/database_migration/gravity-db.sh
@@ -19,13 +19,13 @@ upgrade_gravityDB(){
auditFile="${piholeDir}/auditlog.list"
# Get database version
- version="$(pihole-FTL sqlite3 "${database}" "SELECT \"value\" FROM \"info\" WHERE \"property\" = 'version';")"
+ version="$(pihole-FTL sqlite3 -ni "${database}" "SELECT \"value\" FROM \"info\" WHERE \"property\" = 'version';")"
if [[ "$version" == "1" ]]; then
# This migration script upgrades the gravity.db file by
# adding the domain_audit table
echo -e " ${INFO} Upgrading gravity database from version 1 to 2"
- pihole-FTL sqlite3 "${database}" < "${scriptPath}/1_to_2.sql"
+ pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/1_to_2.sql"
version=2
# Store audit domains in database table
@@ -40,28 +40,28 @@ upgrade_gravityDB(){
# renaming the regex table to regex_blacklist, and
# creating a new regex_whitelist table + corresponding linking table and views
echo -e " ${INFO} Upgrading gravity database from version 2 to 3"
- pihole-FTL sqlite3 "${database}" < "${scriptPath}/2_to_3.sql"
+ pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/2_to_3.sql"
version=3
fi
if [[ "$version" == "3" ]]; then
# This migration script unifies the formally separated domain
# lists into a single table with a UNIQUE domain constraint
echo -e " ${INFO} Upgrading gravity database from version 3 to 4"
- pihole-FTL sqlite3 "${database}" < "${scriptPath}/3_to_4.sql"
+ pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/3_to_4.sql"
version=4
fi
if [[ "$version" == "4" ]]; then
# This migration script upgrades the gravity and list views
# implementing necessary changes for per-client blocking
echo -e " ${INFO} Upgrading gravity database from version 4 to 5"
- pihole-FTL sqlite3 "${database}" < "${scriptPath}/4_to_5.sql"
+ pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/4_to_5.sql"
version=5
fi
if [[ "$version" == "5" ]]; then
# This migration script upgrades the adlist view
# to return an ID used in gravity.sh
echo -e " ${INFO} Upgrading gravity database from version 5 to 6"
- pihole-FTL sqlite3 "${database}" < "${scriptPath}/5_to_6.sql"
+ pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/5_to_6.sql"
version=6
fi
if [[ "$version" == "6" ]]; then
@@ -69,7 +69,7 @@ upgrade_gravityDB(){
# which is automatically associated to all clients not
# having their own group assignments
echo -e " ${INFO} Upgrading gravity database from version 6 to 7"
- pihole-FTL sqlite3 "${database}" < "${scriptPath}/6_to_7.sql"
+ pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/6_to_7.sql"
version=7
fi
if [[ "$version" == "7" ]]; then
@@ -77,21 +77,21 @@ upgrade_gravityDB(){
# to ensure uniqueness on the group name
# We also add date_added and date_modified columns
echo -e " ${INFO} Upgrading gravity database from version 7 to 8"
- pihole-FTL sqlite3 "${database}" < "${scriptPath}/7_to_8.sql"
+ pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/7_to_8.sql"
version=8
fi
if [[ "$version" == "8" ]]; then
# This migration fixes some issues that were introduced
# in the previous migration script.
echo -e " ${INFO} Upgrading gravity database from version 8 to 9"
- pihole-FTL sqlite3 "${database}" < "${scriptPath}/8_to_9.sql"
+ pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/8_to_9.sql"
version=9
fi
if [[ "$version" == "9" ]]; then
# This migration drops unused tables and creates triggers to remove
# obsolete groups assignments when the linked items are deleted
echo -e " ${INFO} Upgrading gravity database from version 9 to 10"
- pihole-FTL sqlite3 "${database}" < "${scriptPath}/9_to_10.sql"
+ pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/9_to_10.sql"
version=10
fi
if [[ "$version" == "10" ]]; then
@@ -101,31 +101,31 @@ upgrade_gravityDB(){
# to keep the copying process generic (needs the same columns in both the
# source and the destination databases).
echo -e " ${INFO} Upgrading gravity database from version 10 to 11"
- pihole-FTL sqlite3 "${database}" < "${scriptPath}/10_to_11.sql"
+ pihole-FTL sqlite3 -ni "${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"
- pihole-FTL sqlite3 "${database}" < "${scriptPath}/11_to_12.sql"
+ pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/11_to_12.sql"
version=12
fi
if [[ "$version" == "12" ]]; then
# Add column date_updated to adlist table
echo -e " ${INFO} Upgrading gravity database from version 12 to 13"
- pihole-FTL sqlite3 "${database}" < "${scriptPath}/12_to_13.sql"
+ pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/12_to_13.sql"
version=13
fi
if [[ "$version" == "13" ]]; then
# Add columns number and status to adlist table
echo -e " ${INFO} Upgrading gravity database from version 13 to 14"
- pihole-FTL sqlite3 "${database}" < "${scriptPath}/13_to_14.sql"
+ pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/13_to_14.sql"
version=14
fi
if [[ "$version" == "14" ]]; then
# Changes the vw_adlist created in 5_to_6
echo -e " ${INFO} Upgrading gravity database from version 14 to 15"
- pihole-FTL sqlite3 "${database}" < "${scriptPath}/14_to_15.sql"
+ pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/14_to_15.sql"
version=15
fi
}
diff --git a/advanced/Scripts/database_migration/gravity/11_to_12.sql b/advanced/Scripts/database_migration/gravity/11_to_12.sql
index 45fbc8451a..d480d46efc 100644
--- a/advanced/Scripts/database_migration/gravity/11_to_12.sql
+++ b/advanced/Scripts/database_migration/gravity/11_to_12.sql
@@ -16,4 +16,4 @@ CREATE TRIGGER tr_group_zero AFTER DELETE ON "group"
UPDATE info SET value = 12 WHERE property = 'version';
-COMMIT;
\ No newline at end of file
+COMMIT;
diff --git a/advanced/Scripts/database_migration/gravity/12_to_13.sql b/advanced/Scripts/database_migration/gravity/12_to_13.sql
index d16791d60a..7d85cb05e2 100644
--- a/advanced/Scripts/database_migration/gravity/12_to_13.sql
+++ b/advanced/Scripts/database_migration/gravity/12_to_13.sql
@@ -15,4 +15,4 @@ CREATE TRIGGER tr_adlist_update AFTER UPDATE OF address,enabled,comment ON adlis
UPDATE info SET value = 13 WHERE property = 'version';
-COMMIT;
\ No newline at end of file
+COMMIT;
diff --git a/advanced/Scripts/database_migration/gravity/3_to_4.sql b/advanced/Scripts/database_migration/gravity/3_to_4.sql
index 352b1baae3..05231f7299 100644
--- a/advanced/Scripts/database_migration/gravity/3_to_4.sql
+++ b/advanced/Scripts/database_migration/gravity/3_to_4.sql
@@ -93,4 +93,4 @@ CREATE VIEW vw_regex_blacklist AS SELECT domain, domainlist.id AS id, domainlist
UPDATE info SET value = 4 WHERE property = 'version';
-COMMIT;
\ No newline at end of file
+COMMIT;
diff --git a/advanced/Scripts/database_migration/gravity/4_to_5.sql b/advanced/Scripts/database_migration/gravity/4_to_5.sql
index 2ad906fc12..4ae9f980fb 100644
--- a/advanced/Scripts/database_migration/gravity/4_to_5.sql
+++ b/advanced/Scripts/database_migration/gravity/4_to_5.sql
@@ -35,4 +35,4 @@ CREATE TABLE client_by_group
UPDATE info SET value = 5 WHERE property = 'version';
-COMMIT;
\ No newline at end of file
+COMMIT;
diff --git a/advanced/Scripts/list.sh b/advanced/Scripts/list.sh
index f3f97da26f..76558e5827 100755
--- a/advanced/Scripts/list.sh
+++ b/advanced/Scripts/list.sh
@@ -100,21 +100,29 @@ Options:
ValidateDomain() {
# Convert to lowercase
domain="${1,,}"
+ local str validDomain
# Check validity of domain (don't check for regex entries)
- if [[ "${#domain}" -le 253 ]]; then
- if [[ ( "${typeId}" == "${regex_blacklist}" || "${typeId}" == "${regex_whitelist}" ) && "${wildcard}" == false ]]; then
- validDomain="${domain}"
- else
+ if [[ ( "${typeId}" == "${regex_blacklist}" || "${typeId}" == "${regex_whitelist}" ) && "${wildcard}" == false ]]; then
+ validDomain="${domain}"
+ else
+ # Check max length
+ 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
+ # set error string
+ str="is not a valid argument or domain name!"
+ else
+ validDomain=
+ str="is too long!"
+
fi
fi
if [[ -n "${validDomain}" ]]; then
domList=("${domList[@]}" "${validDomain}")
else
- echo -e " ${CROSS} ${domain} is not a valid argument or domain name!"
+ echo -e " ${CROSS} ${domain} ${str}"
fi
domaincount=$((domaincount+1))
@@ -142,18 +150,18 @@ AddDomain() {
domain="$1"
# Is the domain in the list we want to add it to?
- num="$(pihole-FTL sqlite3 "${gravityDBfile}" "SELECT COUNT(*) FROM domainlist WHERE domain = '${domain}';")"
+ num="$(pihole-FTL sqlite3 -ni "${gravityDBfile}" "SELECT COUNT(*) FROM domainlist WHERE domain = '${domain}';")"
requestedListname="$(GetListnameFromTypeId "${typeId}")"
if [[ "${num}" -ne 0 ]]; then
- existingTypeId="$(pihole-FTL sqlite3 "${gravityDBfile}" "SELECT type FROM domainlist WHERE domain = '${domain}';")"
+ existingTypeId="$(pihole-FTL sqlite3 -ni "${gravityDBfile}" "SELECT type FROM domainlist WHERE domain = '${domain}';")"
if [[ "${existingTypeId}" == "${typeId}" ]]; then
if [[ "${verbose}" == true ]]; then
echo -e " ${INFO} ${1} already exists in ${requestedListname}, no need to add!"
fi
else
existingListname="$(GetListnameFromTypeId "${existingTypeId}")"
- pihole-FTL sqlite3 "${gravityDBfile}" "UPDATE domainlist SET type = ${typeId} WHERE domain='${domain}';"
+ pihole-FTL sqlite3 -ni "${gravityDBfile}" "UPDATE domainlist SET type = ${typeId} WHERE domain='${domain}';"
if [[ "${verbose}" == true ]]; then
echo -e " ${INFO} ${1} already exists in ${existingListname}, it has been moved to ${requestedListname}!"
fi
@@ -169,10 +177,10 @@ AddDomain() {
# Insert only the domain here. The enabled and date_added fields will be filled
# with their default values (enabled = true, date_added = current timestamp)
if [[ -z "${comment}" ]]; then
- pihole-FTL sqlite3 "${gravityDBfile}" "INSERT INTO domainlist (domain,type) VALUES ('${domain}',${typeId});"
+ pihole-FTL sqlite3 -ni "${gravityDBfile}" "INSERT INTO domainlist (domain,type) VALUES ('${domain}',${typeId});"
else
# also add comment when variable has been set through the "--comment" option
- pihole-FTL sqlite3 "${gravityDBfile}" "INSERT INTO domainlist (domain,type,comment) VALUES ('${domain}',${typeId},'${comment}');"
+ pihole-FTL sqlite3 -ni "${gravityDBfile}" "INSERT INTO domainlist (domain,type,comment) VALUES ('${domain}',${typeId},'${comment}');"
fi
}
@@ -181,7 +189,7 @@ RemoveDomain() {
domain="$1"
# Is the domain in the list we want to remove it from?
- num="$(pihole-FTL sqlite3 "${gravityDBfile}" "SELECT COUNT(*) FROM domainlist WHERE domain = '${domain}' AND type = ${typeId};")"
+ num="$(pihole-FTL sqlite3 -ni "${gravityDBfile}" "SELECT COUNT(*) FROM domainlist WHERE domain = '${domain}' AND type = ${typeId};")"
requestedListname="$(GetListnameFromTypeId "${typeId}")"
@@ -198,14 +206,14 @@ RemoveDomain() {
fi
reload=true
# Remove it from the current list
- pihole-FTL sqlite3 "${gravityDBfile}" "DELETE FROM domainlist WHERE domain = '${domain}' AND type = ${typeId};"
+ pihole-FTL sqlite3 -ni "${gravityDBfile}" "DELETE FROM domainlist WHERE domain = '${domain}' AND type = ${typeId};"
}
Displaylist() {
local count num_pipes domain enabled status nicedate requestedListname
requestedListname="$(GetListnameFromTypeId "${typeId}")"
- data="$(pihole-FTL sqlite3 "${gravityDBfile}" "SELECT domain,enabled,date_modified FROM domainlist WHERE type = ${typeId};" 2> /dev/null)"
+ data="$(pihole-FTL sqlite3 -ni "${gravityDBfile}" "SELECT domain,enabled,date_modified FROM domainlist WHERE type = ${typeId};" 2> /dev/null)"
if [[ -z $data ]]; then
echo -e "Not showing empty list"
@@ -243,10 +251,10 @@ Displaylist() {
}
NukeList() {
- count=$(pihole-FTL sqlite3 "${gravityDBfile}" "SELECT COUNT(1) FROM domainlist WHERE type = ${typeId};")
+ count=$(pihole-FTL sqlite3 -ni "${gravityDBfile}" "SELECT COUNT(1) FROM domainlist WHERE type = ${typeId};")
listname="$(GetListnameFromTypeId "${typeId}")"
if [ "$count" -gt 0 ];then
- pihole-FTL sqlite3 "${gravityDBfile}" "DELETE FROM domainlist WHERE type = ${typeId};"
+ pihole-FTL sqlite3 -ni "${gravityDBfile}" "DELETE FROM domainlist WHERE type = ${typeId};"
echo " ${TICK} Removed ${count} domain(s) from the ${listname}"
else
echo " ${INFO} ${listname} already empty. Nothing to do!"
diff --git a/advanced/Scripts/piholeARPTable.sh b/advanced/Scripts/piholeARPTable.sh
index 5daa025d49..b92dd1242c 100755
--- a/advanced/Scripts/piholeARPTable.sh
+++ b/advanced/Scripts/piholeARPTable.sh
@@ -39,7 +39,7 @@ flushARP(){
# Truncate network_addresses table in pihole-FTL.db
# This needs to be done before we can truncate the network table due to
# foreign key constraints
- if ! output=$(pihole-FTL sqlite3 "${DBFILE}" "DELETE FROM network_addresses" 2>&1); then
+ if ! output=$(pihole-FTL sqlite3 -ni "${DBFILE}" "DELETE FROM network_addresses" 2>&1); then
echo -e "${OVER} ${CROSS} Failed to truncate network_addresses table"
echo " Database location: ${DBFILE}"
echo " Output: ${output}"
@@ -47,7 +47,7 @@ flushARP(){
fi
# Truncate network table in pihole-FTL.db
- if ! output=$(pihole-FTL sqlite3 "${DBFILE}" "DELETE FROM network" 2>&1); then
+ if ! output=$(pihole-FTL sqlite3 -ni "${DBFILE}" "DELETE FROM network" 2>&1); then
echo -e "${OVER} ${CROSS} Failed to truncate network table"
echo " Database location: ${DBFILE}"
echo " Output: ${output}"
diff --git a/advanced/Scripts/piholeCheckout.sh b/advanced/Scripts/piholeCheckout.sh
index 4c0a4f4042..cf57800c4c 100755
--- a/advanced/Scripts/piholeCheckout.sh
+++ b/advanced/Scripts/piholeCheckout.sh
@@ -9,7 +9,7 @@
# Please see LICENSE file for your rights under this license.
readonly PI_HOLE_FILES_DIR="/etc/.pihole"
-PH_TEST="true"
+SKIP_INSTALL="true"
source "${PI_HOLE_FILES_DIR}/automated install/basic-install.sh"
# webInterfaceGitUrl set in basic-install.sh
@@ -164,6 +164,8 @@ checkout() {
exit 1
fi
checkout_pull_branch "${webInterfaceDir}" "${2}"
+ # Update local and remote versions via updatechecker
+ /opt/pihole/updatecheck.sh
elif [[ "${1}" == "ftl" ]] ; then
local path
local oldbranch
@@ -178,6 +180,8 @@ checkout() {
FTLinstall "${binary}"
restart_service pihole-FTL
enable_service pihole-FTL
+ # Update local and remote versions via updatechecker
+ /opt/pihole/updatecheck.sh
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}') )
diff --git a/advanced/Scripts/piholeDebug.sh b/advanced/Scripts/piholeDebug.sh
index 844cbd237c..143ff9d76a 100755
--- a/advanced/Scripts/piholeDebug.sh
+++ b/advanced/Scripts/piholeDebug.sh
@@ -41,18 +41,15 @@ else
#OVER="\r\033[K"
fi
-OBFUSCATED_PLACEHOLDER=""
+# shellcheck disable=SC1091
+. /etc/pihole/versions
# FAQ URLs for use in showing the debug log
-FAQ_UPDATE_PI_HOLE="${COL_CYAN}https://discourse.pi-hole.net/t/how-do-i-update-pi-hole/249${COL_NC}"
-FAQ_CHECKOUT_COMMAND="${COL_CYAN}https://discourse.pi-hole.net/t/the-pihole-command-with-examples/738#checkout${COL_NC}"
FAQ_HARDWARE_REQUIREMENTS="${COL_CYAN}https://docs.pi-hole.net/main/prerequisites/${COL_NC}"
FAQ_HARDWARE_REQUIREMENTS_PORTS="${COL_CYAN}https://docs.pi-hole.net/main/prerequisites/#ports${COL_NC}"
FAQ_HARDWARE_REQUIREMENTS_FIREWALLD="${COL_CYAN}https://docs.pi-hole.net/main/prerequisites/#firewalld${COL_NC}"
FAQ_GATEWAY="${COL_CYAN}https://discourse.pi-hole.net/t/why-is-a-default-gateway-important-for-pi-hole/3546${COL_NC}"
-FAQ_ULA="${COL_CYAN}https://discourse.pi-hole.net/t/use-ipv6-ula-addresses-for-pi-hole/2127${COL_NC}"
FAQ_FTL_COMPATIBILITY="${COL_CYAN}https://github.com/pi-hole/FTL#compatibility-list${COL_NC}"
-FAQ_BAD_ADDRESS="${COL_CYAN}https://discourse.pi-hole.net/t/why-do-i-see-bad-address-at-in-pihole-log/3972${COL_NC}"
# Other URLs we may use
FORUMS_URL="${COL_CYAN}https://discourse.pi-hole.net${COL_NC}"
@@ -66,14 +63,16 @@ PIHOLE_DIRECTORY="/etc/pihole"
PIHOLE_SCRIPTS_DIRECTORY="/opt/pihole"
BIN_DIRECTORY="/usr/local/bin"
RUN_DIRECTORY="/run"
-LOG_DIRECTORY="/var/log"
-WEB_SERVER_LOG_DIRECTORY="${LOG_DIRECTORY}/lighttpd"
+LOG_DIRECTORY="/var/log/pihole"
+WEB_SERVER_LOG_DIRECTORY="/var/log/lighttpd"
WEB_SERVER_CONFIG_DIRECTORY="/etc/lighttpd"
+WEB_SERVER_CONFIG_DIRECTORY_FEDORA="${WEB_SERVER_CONFIG_DIRECTORY}/conf.d"
+WEB_SERVER_CONFIG_DIRECTORY_DEBIAN="${WEB_SERVER_CONFIG_DIRECTORY}/conf-enabled"
HTML_DIRECTORY="/var/www/html"
WEB_GIT_DIRECTORY="${HTML_DIRECTORY}/admin"
-#BLOCK_PAGE_DIRECTORY="${HTML_DIRECTORY}/pihole"
SHM_DIRECTORY="/dev/shm"
ETC="/etc"
+SPEEDTEST_GIT_DIRECTORY="/etc/pihole-speedtest"
# Files required by Pi-hole
# https://discourse.pi-hole.net/t/what-files-does-pi-hole-use/1684
@@ -81,6 +80,8 @@ PIHOLE_CRON_FILE="${CRON_D_DIRECTORY}/pihole"
WEB_SERVER_CONFIG_FILE="${WEB_SERVER_CONFIG_DIRECTORY}/lighttpd.conf"
WEB_SERVER_CUSTOM_CONFIG_FILE="${WEB_SERVER_CONFIG_DIRECTORY}/external.conf"
+WEB_SERVER_PIHOLE_CONFIG_FILE_DEBIAN="${WEB_SERVER_CONFIG_DIRECTORY_DEBIAN}/15-pihole-admin.conf"
+WEB_SERVER_PIHOLE_CONFIG_FILE_FEDORA="${WEB_SERVER_CONFIG_DIRECTORY_FEDORA}/pihole-admin.conf"
PIHOLE_INSTALL_LOG_FILE="${PIHOLE_DIRECTORY}/install.log"
PIHOLE_RAW_BLOCKLIST_FILES="${PIHOLE_DIRECTORY}/list.*"
@@ -89,6 +90,7 @@ PIHOLE_LOGROTATE_FILE="${PIHOLE_DIRECTORY}/logrotate"
PIHOLE_SETUP_VARS_FILE="${PIHOLE_DIRECTORY}/setupVars.conf"
PIHOLE_FTL_CONF_FILE="${PIHOLE_DIRECTORY}/pihole-FTL.conf"
PIHOLE_CUSTOM_HOSTS_FILE="${PIHOLE_DIRECTORY}/custom.list"
+PIHOLE_VERSIONS_FILE="${PIHOLE_DIRECTORY}/versions"
# Read the value of an FTL config key. The value is printed to stdout.
#
@@ -124,45 +126,27 @@ PIHOLE_COMMAND="${BIN_DIRECTORY}/pihole"
PIHOLE_COLTABLE_FILE="${BIN_DIRECTORY}/COL_TABLE"
FTL_PID="${RUN_DIRECTORY}/pihole-FTL.pid"
-FTL_PORT="${RUN_DIRECTORY}/pihole-FTL.port"
PIHOLE_LOG="${LOG_DIRECTORY}/pihole.log"
PIHOLE_LOG_GZIPS="${LOG_DIRECTORY}/pihole.log.[0-9].*"
PIHOLE_DEBUG_LOG="${LOG_DIRECTORY}/pihole_debug.log"
-PIHOLE_FTL_LOG="$(get_ftl_conf_value "LOGFILE" "${LOG_DIRECTORY}/pihole-FTL.log")"
+PIHOLE_FTL_LOG="$(get_ftl_conf_value "LOGFILE" "${LOG_DIRECTORY}/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"
+PIHOLE_WEB_SERVER_ACCESS_LOG_FILE="${WEB_SERVER_LOG_DIRECTORY}/access-pihole.log"
+PIHOLE_WEB_SERVER_ERROR_LOG_FILE="${WEB_SERVER_LOG_DIRECTORY}/error-pihole.log"
RESOLVCONF="${ETC}/resolv.conf"
DNSMASQ_CONF="${ETC}/dnsmasq.conf"
-# 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")
-
# Store Pi-hole's processes in an array for easy use and parsing
PIHOLE_PROCESSES=( "lighttpd" "pihole-FTL" )
-# Store the required directories in an array so it can be parsed through
-#REQUIRED_DIRECTORIES=("${CORE_GIT_DIRECTORY}"
-#"${CRON_D_DIRECTORY}"
-#"${DNSMASQ_D_DIRECTORY}"
-#"${PIHOLE_DIRECTORY}"
-#"${PIHOLE_SCRIPTS_DIRECTORY}"
-#"${BIN_DIRECTORY}"
-#"${RUN_DIRECTORY}"
-#"${LOG_DIRECTORY}"
-#"${WEB_SERVER_LOG_DIRECTORY}"
-#"${WEB_SERVER_CONFIG_DIRECTORY}"
-#"${HTML_DIRECTORY}"
-#"${WEB_GIT_DIRECTORY}"
-#"${BLOCK_PAGE_DIRECTORY}")
-
# Store the required directories in an array so it can be parsed through
REQUIRED_FILES=("${PIHOLE_CRON_FILE}"
"${WEB_SERVER_CONFIG_FILE}"
"${WEB_SERVER_CUSTOM_CONFIG_FILE}"
+"${WEB_SERVER_PIHOLE_CONFIG_FILE_DEBIAN}"
+"${WEB_SERVER_PIHOLE_CONFIG_FILE_FEDORA}"
"${PIHOLE_INSTALL_LOG_FILE}"
"${PIHOLE_RAW_BLOCKLIST_FILES}"
"${PIHOLE_LOCAL_HOSTS_FILE}"
@@ -172,7 +156,6 @@ REQUIRED_FILES=("${PIHOLE_CRON_FILE}"
"${PIHOLE_COMMAND}"
"${PIHOLE_COLTABLE_FILE}"
"${FTL_PID}"
-"${FTL_PORT}"
"${PIHOLE_LOG}"
"${PIHOLE_LOG_GZIPS}"
"${PIHOLE_DEBUG_LOG}"
@@ -181,7 +164,8 @@ REQUIRED_FILES=("${PIHOLE_CRON_FILE}"
"${PIHOLE_WEB_SERVER_ERROR_LOG_FILE}"
"${RESOLVCONF}"
"${DNSMASQ_CONF}"
-"${PIHOLE_CUSTOM_HOSTS_FILE}")
+"${PIHOLE_CUSTOM_HOSTS_FILE}"
+"${PIHOLE_VERSIONS_FILE}")
DISCLAIMER="This process collects information from your Pi-hole, and optionally uploads it to a unique and random directory on tricorder.pi-hole.net.
@@ -247,10 +231,8 @@ initialize_debug() {
# This is a function for visually displaying the current test that is being run.
# Accepts one variable: the name of what is being diagnosed
-# Colors do not show in the dasboard, but the icons do: [i], [✓], and [✗]
echo_current_diagnostic() {
# Colors are used for visually distinguishing each test in the output
- # These colors do not show in the GUI, but the formatting will
log_write "\\n${COL_PURPLE}*** [ DIAGNOSING ]:${COL_NC} ${1}"
}
@@ -259,15 +241,7 @@ compare_local_version_to_git_version() {
local git_dir="${1}"
# The named component of the project (Core or Web)
local pihole_component="${2}"
- # If we are checking the Core versions,
- if [[ "${pihole_component}" == "Core" ]]; then
- # We need to search for "Pi-hole" when using pihole -v
- local search_term="Pi-hole"
- elif [[ "${pihole_component}" == "Web" ]]; then
- # We need to search for "AdminLTE" so store it in a variable as well
- #shellcheck disable=2034
- local search_term="AdminLTE"
- fi
+
# Display what we are checking
echo_current_diagnostic "${pihole_component} version"
# Store the error message in a variable in case we want to change and/or reuse it
@@ -280,43 +254,35 @@ compare_local_version_to_git_version() {
log_write "${COL_RED}Could not cd into ${git_dir}$COL_NC"
if git status &> /dev/null; then
# The current version the user is on
- local remote_version
- remote_version=$(git describe --tags --abbrev=0);
+ local local_version
+ local_version=$(git describe --tags --abbrev=0);
# What branch they are on
- local remote_branch
- remote_branch=$(git rev-parse --abbrev-ref HEAD);
+ local local_branch
+ local_branch=$(git rev-parse --abbrev-ref HEAD);
# The commit they are on
- local remote_commit
- remote_commit=$(git describe --long --dirty --tags --always)
+ local local_commit
+ local_commit=$(git describe --long --dirty --tags --always)
# Status of the repo
local local_status
local_status=$(git status -s)
# echo this information out to the user in a nice format
- # If the current version matches what pihole -v produces, the user is up-to-date
- if [[ "${remote_version}" == "$(pihole -v | awk '/${search_term}/ {print $6}' | cut -d ')' -f1)" ]]; then
- log_write "${TICK} ${pihole_component}: ${COL_GREEN}${remote_version}${COL_NC}"
- # If not,
- else
- # echo the current version in yellow, signifying it's something to take a look at, but not a critical error
- # Also add a URL to an FAQ
- log_write "${INFO} ${pihole_component}: ${COL_YELLOW}${remote_version:-Untagged}${COL_NC} (${FAQ_UPDATE_PI_HOLE})"
- fi
+ log_write "${TICK} Version: ${local_version}"
# 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
+ if [[ "${local_branch}" == "master" ]]; then
# so the color of the text is green
- log_write "${INFO} Branch: ${COL_GREEN}${remote_branch}${COL_NC}"
+ log_write "${INFO} Branch: ${COL_GREEN}${local_branch}${COL_NC}"
# 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})"
+ log_write "${INFO} Branch: ${COL_YELLOW}${local_branch:-Detached}${COL_NC}"
fi
# echo the current commit
- log_write "${INFO} Commit: ${remote_commit}"
+ log_write "${INFO} Commit: ${local_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
@@ -350,18 +316,28 @@ compare_local_version_to_git_version() {
}
check_ftl_version() {
- local ftl_name="FTL"
- echo_current_diagnostic "${ftl_name} version"
+ local FTL_VERSION FTL_COMMIT FTL_BRANCH
+ echo_current_diagnostic "FTL version"
# Use the built in command to check FTL's version
FTL_VERSION=$(pihole-FTL version)
- # Compare the current FTL version to the remote version
- if [[ "${FTL_VERSION}" == "$(pihole -v | awk '/FTL/ {print $6}' | cut -d ')' -f1)" ]]; then
- # If they are the same, FTL is up-to-date
- log_write "${TICK} ${ftl_name}: ${COL_GREEN}${FTL_VERSION}${COL_NC}"
+ FTL_BRANCH=$(pihole-FTL branch)
+ FTL_COMMIT=$(pihole-FTL --hash)
+
+
+ log_write "${TICK} Version: ${FTL_VERSION}"
+
+ # If they use the master branch, they are on the stable codebase
+ if [[ "${FTL_BRANCH}" == "master" ]]; then
+ # so the color of the text is green
+ log_write "${INFO} Branch: ${COL_GREEN}${FTL_BRANCH}${COL_NC}"
+ # If it is any other branch, they are in a development branch
else
- # If not, show it in yellow, signifying there is an update
- log_write "${TICK} ${ftl_name}: ${COL_YELLOW}${FTL_VERSION}${COL_NC} (${FAQ_UPDATE_PI_HOLE})"
+ # 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}${FTL_BRANCH}${COL_NC}"
fi
+
+ # echo the current commit
+ log_write "${INFO} Commit: ${FTL_COMMIT}"
}
# Checks the core version of the Pi-hole codebase
@@ -370,6 +346,8 @@ check_component_versions() {
compare_local_version_to_git_version "${CORE_GIT_DIRECTORY}" "Core"
# Check the Web version, branch, and commit
compare_local_version_to_git_version "${WEB_GIT_DIRECTORY}" "Web"
+ # Check the Speedtest version, branch, and commit
+ compare_local_version_to_git_version "${SPEEDTEST_GIT_DIRECTORY}" "Speedtest"
# Check the FTL version
check_ftl_version
}
@@ -423,52 +401,64 @@ os_check() {
# Extract dig response
response="${cmdResult%%$'\n'*}"
- IFS=" " read -r -a supportedOS < <(echo "${response}" | tr -d '"')
- for distro_and_versions in "${supportedOS[@]}"
- do
- distro_part="${distro_and_versions%%=*}"
- versions_part="${distro_and_versions##*=}"
-
- if [[ "${detected_os^^}" =~ ${distro_part^^} ]]; then
- valid_os=true
- IFS="," read -r -a supportedVer <<<"${versions_part}"
- for version in "${supportedVer[@]}"
- do
- if [[ "${detected_version}" =~ $version ]]; then
- valid_version=true
- break
- fi
- done
- break
- fi
- done
-
- log_write "${INFO} dig return code: ${digReturnCode}"
- log_write "${INFO} dig response: ${response}"
+ if [ "${digReturnCode}" -ne 0 ]; then
+ log_write "${INFO} Distro: ${detected_os^}"
+ log_write "${INFO} Version: ${detected_version}"
+ log_write "${CROSS} dig return code: ${COL_RED}${digReturnCode}${COL_NC}"
+ log_write "${CROSS} dig response: ${response}"
+ log_write "${CROSS} Error: ${COL_RED}dig command failed - Unable to check OS${COL_NC}"
+ else
+ IFS=" " read -r -a supportedOS < <(echo "${response}" | tr -d '"')
+ for distro_and_versions in "${supportedOS[@]}"
+ do
+ distro_part="${distro_and_versions%%=*}"
+ versions_part="${distro_and_versions##*=}"
+
+ if [[ "${detected_os^^}" =~ ${distro_part^^} ]]; then
+ valid_os=true
+ IFS="," read -r -a supportedVer <<<"${versions_part}"
+ for version in "${supportedVer[@]}"
+ do
+ if [[ "${detected_version}" =~ $version ]]; then
+ valid_version=true
+ break
+ fi
+ done
+ break
+ fi
+ done
- if [ "$valid_os" = true ]; then
- log_write "${TICK} Distro: ${COL_GREEN}${detected_os^}${COL_NC}"
+ local finalmsg
+ 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}"
+ if [ "$valid_version" = true ]; then
+ log_write "${TICK} Version: ${COL_GREEN}${detected_version}${COL_NC}"
+ finalmsg="${TICK} ${COL_GREEN}Distro and version supported${COL_NC}"
+ else
+ log_write "${CROSS} Version: ${COL_RED}${detected_version}${COL_NC}"
+ finalmsg="${CROSS} Error: ${COL_RED}${detected_os^} is supported but version ${detected_version} is currently unsupported ${COL_NC}(${FAQ_HARDWARE_REQUIREMENTS})${COL_NC}"
+ fi
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}"
+ log_write "${CROSS} Distro: ${COL_RED}${detected_os^}${COL_NC}"
+ finalmsg="${CROSS} Error: ${COL_RED}${detected_os^} is not a supported distro ${COL_NC}(${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}"
+
+ # Print dig response and the final check result
+ log_write "${TICK} dig return code: ${COL_GREEN}${digReturnCode}${COL_NC}"
+ log_write "${INFO} dig response: ${response}"
+ log_write "${finalmsg}"
fi
}
diagnose_operating_system() {
- # error message in a variable so we can easily modify it later (or re-use it)
+ # error message in a variable so we can easily modify it later (or reuse it)
local error_msg="Distribution unknown -- most likely you are on an unsupported platform and may run into issues."
# Display the current test that is running
echo_current_diagnostic "Operating system"
- # If the PIHOLE_DOCKER_TAG variable is set, include this information in the debug output
- [ -n "${PIHOLE_DOCKER_TAG}" ] && log_write "${INFO} Pi-hole Docker Container: ${PIHOLE_DOCKER_TAG}"
+ # If DOCKER_VERSION is set (Sourced from /etc/pihole/versions at start of script), include this information in the debug output
+ [ -n "${DOCKER_VERSION}" ] && log_write "${INFO} Pi-hole Docker Container: ${DOCKER_VERSION}"
# 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
@@ -600,10 +590,10 @@ disk_usage() {
# Some lines of df might contain sensitive information like usernames and passwords.
# E.g. curlftpfs filesystems (https://www.looklinux.com/mount-ftp-share-on-linux-using-curlftps/)
# We are not interested in those lines so we collect keyword, to remove them from the output
- # Additinal keywords can be added, separated by "|"
+ # Additional keywords can be added, separated by "|"
hide="curlftpfs"
- # only show those lines not containg a sensitive phrase
+ # only show those lines not containing a sensitive phrase
for line in "${file_system[@]}"; do
if [[ ! $line =~ $hide ]]; then
log_write " ${line}"
@@ -678,15 +668,20 @@ ping_gateway() {
local protocol="${1}"
ping_ipv4_or_ipv6 "${protocol}"
# Check if we are using IPv4 or IPv6
- # Find the default gateway using IPv4 or IPv6
+ # Find the default gateways using IPv4 or IPv6
local gateway
- gateway="$(ip -"${protocol}" route | grep default | grep "${PIHOLE_INTERFACE}" | cut -d ' ' -f 3)"
- # If the gateway variable has a value (meaning a gateway was found),
- if [[ -n "${gateway}" ]]; then
- log_write "${INFO} Default IPv${protocol} gateway: ${gateway}"
+ log_write "${INFO} Default IPv${protocol} gateway(s):"
+
+ while IFS= read -r gateway; do
+ log_write " ${gateway}"
+ done < <(ip -"${protocol}" route | grep default | grep "${PIHOLE_INTERFACE}" | cut -d ' ' -f 3)
+
+ gateway=$(ip -"${protocol}" route | grep default | grep "${PIHOLE_INTERFACE}" | cut -d ' ' -f 3 | head -n 1)
+ # If there was at least one gateway
+ if [ -n "${gateway}" ]; then
# Let the user know we will ping the gateway for a response
- log_write " * Pinging ${gateway}..."
+ log_write " * Pinging first gateway ${gateway}..."
# Try to quietly ping the gateway 3 times, with a timeout of 3 seconds, using numeric output only,
# on the pihole interface, and tail the last three lines of the output
# If pinging the gateway is not successful,
@@ -804,7 +799,7 @@ check_networking() {
ping_gateway "6"
# Skip the following check if installed in docker container. Unpriv'ed containers do not have access to the information required
# to resolve the service name listening - and the container should not start if there was a port conflict anyway
- [ -z "${PIHOLE_DOCKER_TAG}" ] && check_required_ports
+ [ -z "${DOCKER_VERSION}" ] && check_required_ports
}
check_x_headers() {
@@ -814,39 +809,24 @@ check_x_headers() {
# Similarly, it will show "X-Pi-hole: The Pi-hole Web interface is working!" if you view the header returned
# when accessing the dashboard (i.e curl -I pi.hole/admin/)
# server is operating correctly
- echo_current_diagnostic "Dashboard and block page"
+ echo_current_diagnostic "Dashboard headers"
# Use curl -I to get the header and parse out just the X-Pi-hole one
- local block_page
- block_page=$(curl -Is localhost | awk '/X-Pi-hole/' | tr -d '\r')
- # Do it for the dashboard as well, as the header is different than above
+ local full_curl_output_dashboard
local dashboard
- dashboard=$(curl -Is localhost/admin/ | awk '/X-Pi-hole/' | tr -d '\r')
+ full_curl_output_dashboard="$(curl -Is localhost/admin/)"
+ dashboard=$(echo "${full_curl_output_dashboard}" | awk '/X-Pi-hole/' | tr -d '\r')
# Store what the X-Header should 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
dashboard_working="X-Pi-hole: The Pi-hole Web interface is working!"
- local full_curl_output_block_page
- full_curl_output_block_page="$(curl -Is localhost)"
- local full_curl_output_dashboard
- full_curl_output_dashboard="$(curl -Is localhost/admin/)"
- # If the X-header found by curl matches what is should be,
- if [[ $block_page == "$block_page_working" ]]; then
- # display a success message
- log_write "$TICK Block page X-Header: ${COL_GREEN}${block_page}${COL_NC}"
- else
- # Otherwise, show an error
- log_write "$CROSS Block page X-Header: ${COL_RED}X-Header does not match or could not be retrieved.${COL_NC}"
- log_write "${COL_RED}${full_curl_output_block_page}${COL_NC}"
- fi
- # Same logic applies to the dashboard as above, if the X-Header matches what a working system should have,
+ # If the X-Header matches what a working system should have,
if [[ $dashboard == "$dashboard_working" ]]; then
# then we can show a success
log_write "$TICK Web interface X-Header: ${COL_GREEN}${dashboard}${COL_NC}"
else
# Otherwise, it's a failure since the X-Headers either don't exist or have been modified in some way
log_write "$CROSS Web interface X-Header: ${COL_RED}X-Header does not match or could not be retrieved.${COL_NC}"
+
log_write "${COL_RED}${full_curl_output_dashboard}${COL_NC}"
fi
}
@@ -884,11 +864,15 @@ dig_at() {
local record_type="A"
fi
- # Find a random blocked url that has not been whitelisted.
+ # Find a random blocked url that has not been whitelisted and is not ABP style.
# This helps emulate queries to different domains that a user might query
# It will also give extra assurance that Pi-hole is correctly resolving and blocking domains
local random_url
- random_url=$(pihole-FTL sqlite3 "${PIHOLE_GRAVITY_DB_FILE}" "SELECT domain FROM vw_gravity ORDER BY RANDOM() LIMIT 1")
+ random_url=$(pihole-FTL sqlite3 -ni "${PIHOLE_GRAVITY_DB_FILE}" "SELECT domain FROM vw_gravity WHERE domain not like '||%^' ORDER BY RANDOM() LIMIT 1")
+ # Fallback if no non-ABP style domains were found
+ if [ -z "${random_url}" ]; then
+ random_url="flurry.com"
+ fi
# Next we need to check if Pi-hole can resolve a domain when the query is sent to it's IP address
# This better emulates how clients will interact with Pi-hole as opposed to above where Pi-hole is
@@ -964,10 +948,21 @@ process_status(){
else
# Otherwise, use the service command and mock the output of `systemctl is-active`
local status_of_process
- if service "${i}" status | grep -E 'is\srunning' &> /dev/null; then
- status_of_process="active"
+
+ # If DOCKER_VERSION is set, the output is slightly different (s6 init system on Docker)
+ if [ -n "${DOCKER_VERSION}" ]; then
+ if service "${i}" status | grep -E '^up' &> /dev/null; then
+ status_of_process="active"
+ else
+ status_of_process="inactive"
+ fi
else
- status_of_process="inactive"
+ # non-Docker system
+ if service "${i}" status | grep -E 'is\srunning' &> /dev/null; then
+ status_of_process="active"
+ else
+ status_of_process="inactive"
+ fi
fi
fi
# and print it out to the user
@@ -993,6 +988,20 @@ ftl_full_status(){
fi
}
+lighttpd_test_configuration(){
+ # let lighttpd test it's own configuration
+ local lighttpd_conf_test
+ echo_current_diagnostic "Lighttpd configuration test"
+ lighttpd_conf_test=$(lighttpd -tt -f /etc/lighttpd/lighttpd.conf)
+ if [ -z "${lighttpd_conf_test}" ]; then
+ # empty output
+ log_write "${TICK} ${COL_GREEN}No error in lighttpd configuration${COL_NC}"
+ else
+ log_write "${CROSS} ${COL_RED}Error in lighttpd configuration${COL_NC}"
+ log_write " ${lighttpd_conf_test}"
+ fi
+}
+
make_array_from_file() {
local filename="${1}"
# The second argument can put a limit on how many line should be read from the file
@@ -1009,7 +1018,7 @@ make_array_from_file() {
else
# Otherwise, read the file line by line
while IFS= read -r line;do
- # Othwerise, strip out comments and blank lines
+ # Otherwise, strip out comments and blank lines
new_line=$(echo "${line}" | sed -e 's/^\s*#.*$//' -e '/^$/d')
# If the line still has content (a non-zero value)
if [[ -n "${new_line}" ]]; then
@@ -1067,7 +1076,7 @@ parse_file() {
}
check_name_resolution() {
- # Check name resolution from localhost, Pi-hole's IP, and Google's name severs
+ # Check name resolution from localhost, Pi-hole's IP, and Google's name servers
# using the function we created earlier
dig_at 4
dig_at 6
@@ -1085,10 +1094,13 @@ dir_check() {
# check if exists first; if it does,
if ls "${filename}" 1> /dev/null 2>&1; then
# do nothing
- :
+ true
+ return
else
# Otherwise, show an error
log_write "${COL_RED}${directory} does not exist.${COL_NC}"
+ false
+ return
fi
done
}
@@ -1096,6 +1108,19 @@ dir_check() {
list_files_in_dir() {
# Set the first argument passed to this function as a named variable for better readability
local dir_to_parse="${1}"
+
+ # show files and sizes of some directories, don't print the file content (yet)
+ if [[ "${dir_to_parse}" == "${SHM_DIRECTORY}" ]]; then
+ # SHM file - we do not want to see the content, but we want to see the files and their sizes
+ log_write "$(ls -lh "${dir_to_parse}/")"
+ elif [[ "${dir_to_parse}" == "${WEB_SERVER_CONFIG_DIRECTORY_FEDORA}" ]]; then
+ # we want to see all files files in /etc/lighttpd/conf.d
+ log_write "$(ls -lh "${dir_to_parse}/" 2> /dev/null )"
+ elif [[ "${dir_to_parse}" == "${WEB_SERVER_CONFIG_DIRECTORY_DEBIAN}" ]]; then
+ # we want to see all files files in /etc/lighttpd/conf.d
+ log_write "$(ls -lh "${dir_to_parse}/"/ 2> /dev/null )"
+ fi
+
# Store the files found in an array
mapfile -t files_found < <(ls "${dir_to_parse}")
# For each file in the array,
@@ -1111,11 +1136,8 @@ list_files_in_dir() {
[[ "${dir_to_parse}/${each_file}" == "${PIHOLE_WEB_SERVER_ACCESS_LOG_FILE}" ]] || \
[[ "${dir_to_parse}/${each_file}" == "${PIHOLE_LOG_GZIPS}" ]]; then
:
- elif [[ "${dir_to_parse}" == "${SHM_DIRECTORY}" ]]; then
- # SHM file - we do not want to see the content, but we want to see the files and their sizes
- log_write "$(ls -lhd "${dir_to_parse}"/"${each_file}")"
elif [[ "${dir_to_parse}" == "${DNSMASQ_D_DIRECTORY}" ]]; then
- # in case of the dnsmasq directory inlcuede all files in the debug output
+ # in case of the dnsmasq directory include all files in the debug output
log_write "\\n${COL_GREEN}$(ls -lhd "${dir_to_parse}"/"${each_file}")${COL_NC}"
make_array_from_file "${dir_to_parse}/${each_file}"
else
@@ -1148,9 +1170,10 @@ show_content_of_files_in_dir() {
# Set a local variable for better readability
local directory="${1}"
# Check if the directory exists
- dir_check "${directory}"
- # if it does, list the files in it
- list_files_in_dir "${directory}"
+ if dir_check "${directory}"; then
+ # if it does, list the files in it
+ list_files_in_dir "${directory}"
+ fi
}
show_content_of_pihole_files() {
@@ -1158,6 +1181,8 @@ show_content_of_pihole_files() {
show_content_of_files_in_dir "${PIHOLE_DIRECTORY}"
show_content_of_files_in_dir "${DNSMASQ_D_DIRECTORY}"
show_content_of_files_in_dir "${WEB_SERVER_CONFIG_DIRECTORY}"
+ show_content_of_files_in_dir "${WEB_SERVER_CONFIG_DIRECTORY_FEDORA}"
+ show_content_of_files_in_dir "${WEB_SERVER_CONFIG_DIRECTORY_DEBIAN}"
show_content_of_files_in_dir "${CRON_D_DIRECTORY}"
show_content_of_files_in_dir "${WEB_SERVER_LOG_DIRECTORY}"
show_content_of_files_in_dir "${LOG_DIRECTORY}"
@@ -1204,7 +1229,7 @@ show_db_entries() {
IFS=$'\r\n'
local entries=()
mapfile -t entries < <(\
- pihole-FTL sqlite3 "${PIHOLE_GRAVITY_DB_FILE}" \
+ pihole-FTL sqlite3 -ni "${PIHOLE_GRAVITY_DB_FILE}" \
-cmd ".headers on" \
-cmd ".mode column" \
-cmd ".width ${widths}" \
@@ -1229,7 +1254,7 @@ show_FTL_db_entries() {
IFS=$'\r\n'
local entries=()
mapfile -t entries < <(\
- pihole-FTL sqlite3 "${PIHOLE_FTL_DB_FILE}" \
+ pihole-FTL sqlite3 -ni "${PIHOLE_FTL_DB_FILE}" \
-cmd ".headers on" \
-cmd ".mode column" \
-cmd ".width ${widths}" \
@@ -1249,7 +1274,7 @@ check_dhcp_servers() {
OLD_IFS="$IFS"
IFS=$'\n'
local entries=()
- mapfile -t entries < <(pihole-FTL dhcp-discover)
+ mapfile -t entries < <(pihole-FTL dhcp-discover & spinner)
for line in "${entries[@]}"; do
log_write " ${line}"
@@ -1278,15 +1303,24 @@ show_messages() {
show_FTL_db_entries "Pi-hole diagnosis messages" "SELECT count (message) as count, datetime(max(timestamp),'unixepoch','localtime') as 'last timestamp', type, message, blob1, blob2, blob3, blob4, blob5 FROM message GROUP BY type, message, blob1, blob2, blob3, blob4, blob5;" "6 19 20 60 20 20 20 20 20"
}
+database_permissions() {
+ local permissions
+ permissions=$(ls -lhd "${1}")
+ log_write "${COL_GREEN}${permissions}${COL_NC}"
+}
+
analyze_gravity_list() {
echo_current_diagnostic "Gravity Database"
- local gravity_permissions
- gravity_permissions=$(ls -lhd "${PIHOLE_GRAVITY_DB_FILE}")
- log_write "${COL_GREEN}${gravity_permissions}${COL_NC}"
+ database_permissions "${PIHOLE_GRAVITY_DB_FILE}"
+
+ # if users want to check database integrity
+ if [[ "${CHECK_DATABASE}" = true ]]; then
+ database_integrity_check "${PIHOLE_GRAVITY_DB_FILE}"
+ fi
show_db_entries "Info table" "SELECT property,value FROM info" "20 40"
- gravity_updated_raw="$(pihole-FTL sqlite3 "${PIHOLE_GRAVITY_DB_FILE}" "SELECT value FROM info where property = 'updated'")"
+ gravity_updated_raw="$(pihole-FTL sqlite3 -ni "${PIHOLE_GRAVITY_DB_FILE}" "SELECT value FROM info where property = 'updated'")"
gravity_updated="$(date -d @"${gravity_updated_raw}")"
log_write " Last gravity run finished at: ${COL_CYAN}${gravity_updated}${COL_NC}"
log_write ""
@@ -1294,7 +1328,7 @@ analyze_gravity_list() {
OLD_IFS="$IFS"
IFS=$'\r\n'
local gravity_sample=()
- mapfile -t gravity_sample < <(pihole-FTL sqlite3 "${PIHOLE_GRAVITY_DB_FILE}" "SELECT domain FROM vw_gravity LIMIT 10")
+ mapfile -t gravity_sample < <(pihole-FTL sqlite3 -ni "${PIHOLE_GRAVITY_DB_FILE}" "SELECT domain FROM vw_gravity LIMIT 10")
log_write " ${COL_CYAN}----- First 10 Gravity Domains -----${COL_NC}"
for line in "${gravity_sample[@]}"; do
@@ -1305,49 +1339,87 @@ analyze_gravity_list() {
IFS="$OLD_IFS"
}
-obfuscated_pihole_log() {
- local pihole_log=("$@")
- local line
- local error_to_check_for
- local line_to_obfuscate
- local obfuscated_line
- for line in "${pihole_log[@]}"; do
- # A common error in the pihole.log is when there is a non-hosts formatted file
- # that the DNS server is attempting to read. Since it's not formatted
- # correctly, there will be an entry for "bad address at line n"
- # So we can check for that here and highlight it in red so the user can see it easily
- error_to_check_for=$(echo "${line}" | grep 'bad address at')
- # Some users may not want to have the domains they visit sent to us
- # To that end, we check for lines in the log that would contain a domain name
- line_to_obfuscate=$(echo "${line}" | grep ': query\|: forwarded\|: reply')
- # If the variable contains a value, it found an error in the log
- if [[ -n ${error_to_check_for} ]]; then
- # So we can print it in red to make it visible to the user
- log_write " ${CROSS} ${COL_RED}${line}${COL_NC} (${FAQ_BAD_ADDRESS})"
+analyze_ftl_db() {
+ echo_current_diagnostic "Pi-hole FTL Query Database"
+ database_permissions "${PIHOLE_FTL_DB_FILE}"
+ # if users want to check database integrity
+ if [[ "${CHECK_DATABASE}" = true ]]; then
+ database_integrity_check "${PIHOLE_FTL_DB_FILE}"
+ fi
+}
+
+database_integrity_check(){
+ local result
+ local database="${1}"
+
+ log_write "${INFO} Checking integrity of ${database} ... (this can take several minutes)"
+ result="$(pihole-FTL "${database}" "PRAGMA integrity_check" 2>&1 & spinner)"
+ if [[ ${result} = "ok" ]]; then
+ log_write "${TICK} Integrity of ${database} intact"
+
+
+ log_write "${INFO} Checking foreign key constraints of ${database} ... (this can take several minutes)"
+ unset result
+ result="$(pihole-FTL sqlite3 -ni "${database}" -cmd ".headers on" -cmd ".mode column" "PRAGMA foreign_key_check" 2>&1 & spinner)"
+ if [[ -z ${result} ]]; then
+ log_write "${TICK} No foreign key errors in ${database}"
else
- # If the variable does not a value (the current default behavior), so do not obfuscate anything
- if [[ -z ${OBFUSCATE} ]]; then
- log_write " ${line}"
- # Othwerise, a flag was passed to this command to obfuscate domains in the log
- else
- # So first check if there are domains in the log that should be obfuscated
- if [[ -n ${line_to_obfuscate} ]]; then
- # If there are, we need to use awk to replace only the domain name (the 6th field in the log)
- # so we 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
- log_write " ${line}"
- fi
- fi
+ log_write "${CROSS} ${COL_RED}Foreign key errors in ${database} found.${COL_NC}"
+ while IFS= read -r line ; do
+ log_write " $line"
+ done <<< "$result"
fi
- done
+
+ else
+ log_write "${CROSS} ${COL_RED}Integrity errors in ${database} found.\n${COL_NC}"
+ while IFS= read -r line ; do
+ log_write " $line"
+ done <<< "$result"
+ fi
+
+}
+
+# Show a text spinner during a long process run
+spinner(){
+ # Show the spinner only if there is a tty
+ if tty -s; then
+ # PID of the most recent background process
+ _PID=$!
+ _spin="/-\|"
+ _start=0
+ _elapsed=0
+ _i=1
+
+ # Start the counter
+ _start=$(date +%s)
+
+ # Hide the cursor
+ tput civis > /dev/tty
+
+ # ensures cursor is visible again, in case of premature exit
+ trap 'tput cnorm > /dev/tty' EXIT
+
+ while [ -d /proc/$_PID ]; do
+ _elapsed=$(( $(date +%s) - _start ))
+ # use hours only if needed
+ if [ "$_elapsed" -lt 3600 ]; then
+ printf "\r${_spin:_i++%${#_spin}:1} %02d:%02d" $((_elapsed/60)) $((_elapsed%60)) >"$(tty)"
+ else
+ printf "\r${_spin:_i++%${#_spin}:1} %02d:%02d:%02d" $((_elapsed/3600)) $(((_elapsed/60)%60)) $((_elapsed%60)) >"$(tty)"
+ fi
+ sleep 0.25
+ done
+
+ # Return to the begin of the line after completion (the spinner will be overwritten)
+ printf "\r" >"$(tty)"
+
+ # Restore cursor visibility
+ tput cnorm > /dev/tty
+ fi
}
analyze_pihole_log() {
echo_current_diagnostic "Pi-hole log"
- local pihole_log_head=()
- local pihole_log_tail=()
local pihole_log_permissions
local logging_enabled
@@ -1357,22 +1429,10 @@ analyze_pihole_log() {
log_write "${INFO} Query logging is disabled"
log_write ""
fi
- # Put the current Internal Field Separator into another variable so it can be restored later
- OLD_IFS="$IFS"
- # Get the lines that are in the file(s) and store them in an array for parsing later
- IFS=$'\r\n'
+
pihole_log_permissions=$(ls -lhd "${PIHOLE_LOG}")
log_write "${COL_GREEN}${pihole_log_permissions}${COL_NC}"
- mapfile -t pihole_log_head < <(head -n 20 ${PIHOLE_LOG})
- log_write " ${COL_CYAN}-----head of $(basename ${PIHOLE_LOG})------${COL_NC}"
- obfuscated_pihole_log "${pihole_log_head[@]}"
- log_write ""
- mapfile -t pihole_log_tail < <(tail -n 20 ${PIHOLE_LOG})
- log_write " ${COL_CYAN}-----tail of $(basename ${PIHOLE_LOG})------${COL_NC}"
- obfuscated_pihole_log "${pihole_log_tail[@]}"
- log_write ""
- # Set the IFS back to what it was
- IFS="$OLD_IFS"
+ head_tail_log "${PIHOLE_LOG}" 20
}
curl_to_tricorder() {
@@ -1394,7 +1454,7 @@ curl_to_tricorder() {
upload_to_tricorder() {
local username="pihole"
# Set the permissions and owner
- chmod 644 ${PIHOLE_DEBUG_LOG}
+ chmod 640 ${PIHOLE_DEBUG_LOG}
chown "$USER":"${username}" ${PIHOLE_DEBUG_LOG}
# Let the user know debugging is complete with something strikingly visual
@@ -1446,11 +1506,11 @@ upload_to_tricorder() {
# If no token was generated
else
# Show an error and some help instructions
- # Skip this if being called from web interface and autmatic mode was not chosen (users opt-out to upload)
+ # Skip this if being called from web interface and automatic mode was not chosen (users opt-out to upload)
if [[ "${WEBCALL}" ]] && [[ ! "${AUTOMATED}" ]]; then
:
else
- log_write "${CROSS} ${COL_RED}There was an error uploading your debug log.${COL_NC}"
+ log_write "${CROSS} ${COL_RED}There was an error uploading your debug log.${COL_NC}"
log_write " * Please try again or contact the Pi-hole team for assistance."
fi
fi
@@ -1477,8 +1537,10 @@ check_name_resolution
check_dhcp_servers
process_status
ftl_full_status
+lighttpd_test_configuration
parse_setup_vars
check_x_headers
+analyze_ftl_db
analyze_gravity_list
show_groups
show_domainlist
diff --git a/advanced/Scripts/piholeLogFlush.sh b/advanced/Scripts/piholeLogFlush.sh
index 57f901f52d..b06aac8bb6 100755
--- a/advanced/Scripts/piholeLogFlush.sh
+++ b/advanced/Scripts/piholeLogFlush.sh
@@ -31,7 +31,7 @@ if [ -z "$DBFILE" ]; then
fi
if [[ "$@" != *"quiet"* ]]; then
- echo -ne " ${INFO} Flushing /var/log/pihole.log ..."
+ echo -ne " ${INFO} Flushing /var/log/pihole/pihole.log ..."
fi
if [[ "$@" == *"once"* ]]; then
# Nightly logrotation
@@ -44,9 +44,9 @@ if [[ "$@" == *"once"* ]]; then
# Note that moving the file is not an option, as
# dnsmasq would happily continue writing into the
# moved file (it will have the same file handler)
- cp -p /var/log/pihole.log /var/log/pihole.log.1
- echo " " > /var/log/pihole.log
- chmod 644 /var/log/pihole.log
+ cp -p /var/log/pihole/pihole.log /var/log/pihole/pihole.log.1
+ echo " " > /var/log/pihole/pihole.log
+ chmod 640 /var/log/pihole/pihole.log
fi
else
# Manual flushing
@@ -56,20 +56,20 @@ else
/usr/sbin/logrotate --force --state "${STATEFILE}" /etc/pihole/logrotate
else
# Flush both pihole.log and pihole.log.1 (if existing)
- echo " " > /var/log/pihole.log
- if [ -f /var/log/pihole.log.1 ]; then
- echo " " > /var/log/pihole.log.1
- chmod 644 /var/log/pihole.log.1
+ echo " " > /var/log/pihole/pihole.log
+ if [ -f /var/log/pihole/pihole.log.1 ]; then
+ echo " " > /var/log/pihole/pihole.log.1
+ chmod 640 /var/log/pihole/pihole.log.1
fi
fi
# Delete most recent 24 hours from FTL's database, leave even older data intact (don't wipe out all history)
- deleted=$(pihole-FTL sqlite3 "${DBFILE}" "DELETE FROM query_storage WHERE timestamp >= strftime('%s','now')-86400; select changes() from query_storage limit 1")
+ deleted=$(pihole-FTL sqlite3 -ni "${DBFILE}" "DELETE FROM query_storage WHERE timestamp >= strftime('%s','now')-86400; select changes() from query_storage limit 1")
# Restart pihole-FTL to force reloading history
sudo pihole restartdns
fi
if [[ "$@" != *"quiet"* ]]; then
- echo -e "${OVER} ${TICK} Flushed /var/log/pihole.log"
+ echo -e "${OVER} ${TICK} Flushed /var/log/pihole/pihole.log"
echo -e " ${TICK} Deleted ${deleted} queries from database"
fi
diff --git a/advanced/Scripts/query.sh b/advanced/Scripts/query.sh
index 8f7bfea42d..ebcc6f79c9 100755
--- a/advanced/Scripts/query.sh
+++ b/advanced/Scripts/query.sh
@@ -16,7 +16,6 @@ GRAVITYDB="${piholeDir}/gravity.db"
options="$*"
all=""
exact=""
-blockpage=""
matchType="match"
# Source pihole-FTL from install script
pihole_FTL="${piholeDir}/pihole-FTL.conf"
@@ -31,33 +30,6 @@ gravityDBfile="${GRAVITYDB}"
colfile="/opt/pihole/COL_TABLE"
source "${colfile}"
-# Scan an array of files for matching strings
-scanList(){
- # Escape full stops
- local domain="${1}" esc_domain="${1//./\\.}" lists="${2}" type="${3:-}"
-
- # Prevent grep from printing file path
- cd "$piholeDir" || exit 1
-
- # 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
- case "${type}" in
- "exact" ) grep -i -E -l "(^|(?/dev/null;;
- # 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" )
- 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
-}
-
if [[ "${options}" == "-h" ]] || [[ "${options}" == "--help" ]]; then
echo "Usage: pihole -q [option]
Example: 'pihole -q -exact domain.com'
@@ -71,57 +43,93 @@ Options:
fi
# Handle valid options
-if [[ "${options}" == *"-bp"* ]]; then
- exact="exact"; blockpage=true
-else
- [[ "${options}" == *"-all"* ]] && all=true
- if [[ "${options}" == *"-exact"* ]]; then
- exact="exact"; matchType="exact ${matchType}"
- fi
+[[ "${options}" == *"-all"* ]] && all=true
+if [[ "${options}" == *"-exact"* ]]; then
+ exact="exact"; matchType="exact ${matchType}"
fi
# Strip valid options, leaving only the domain and invalid options
# This allows users to place the options before or after the domain
-options=$(sed -E 's/ ?-(bp|adlists?|all|exact) ?//g' <<< "${options}")
+options=$(sed -E 's/ ?-(all|exact) ?//g' <<< "${options}")
# Handle remaining options
# If $options contain non ASCII characters, convert to punycode
case "${options}" in
"" ) str="No domain specified";;
*" "* ) str="Unknown query option specified";;
- *[![:ascii:]]* ) domainQuery=$(idn2 "${options}");;
- * ) domainQuery="${options}";;
+ *[![:ascii:]]* ) rawDomainQuery=$(idn2 "${options}");;
+ * ) rawDomainQuery="${options}";;
esac
+# convert the domain to lowercase
+domainQuery=$(echo "${rawDomainQuery}" | tr '[:upper:]' '[:lower:]')
+
if [[ -n "${str:-}" ]]; then
echo -e "${str}${COL_NC}\\nTry 'pihole -q --help' for more information."
exit 1
fi
+# Scan a domain again a list of RegEX
+scanRegExList(){
+ local domain="${1}" list="${2}"
+
+ for entry in ${list}; do
+ if [[ "${domain}" =~ ${entry} ]]; then
+ printf "%b\n" "${entry}";
+ fi
+ done
+
+}
+
scanDatabaseTable() {
- local domain table type querystr result extra
+ local domain table list_type querystr result extra abpquerystr abpfound abpentry searchstr
domain="$(printf "%q" "${1}")"
table="${2}"
- type="${3:-}"
+ list_type="${3:-}"
# As underscores are legitimate parts of domains, we escape them when using the LIKE operator.
# Underscores are SQLite wildcards matching exactly one character. We obviously want to suppress this
# behavior. The "ESCAPE '\'" clause specifies that an underscore preceded by an '\' should be matched
# as a literal underscore character. We pretreat the $domain variable accordingly to escape underscores.
if [[ "${table}" == "gravity" ]]; then
+
+ # Are there ABP entries on gravity?
+ # Return 1 if abp_domain=1 or Zero if abp_domain=0 or not set
+ abpquerystr="SELECT EXISTS (SELECT 1 FROM info WHERE property='abp_domains' and value='1')"
+ abpfound="$(pihole-FTL sqlite3 -ni "${gravityDBfile}" "${abpquerystr}")" 2> /dev/null
+
+ # Create search string for ABP entries only if needed
+ if [ "${abpfound}" -eq 1 ]; then
+ abpentry="${domain}"
+
+ searchstr="'||${abpentry}^'"
+
+ # While a dot is found ...
+ while [ "${abpentry}" != "${abpentry/./}" ]
+ do
+ # ... remove text before the dot (including the dot) and append the result to $searchstr
+ abpentry=$(echo "${abpentry}" | cut -f 2- -d '.')
+ searchstr="$searchstr, '||${abpentry}^'"
+ done
+
+ # The final search string will look like:
+ # "domain IN ('||sub2.sub1.domain.com^', '||sub1.domain.com^', '||domain.com^', '||com^') OR"
+ searchstr="domain IN (${searchstr}) OR "
+ fi
+
case "${exact}" in
"exact" ) querystr="SELECT gravity.domain,adlist.address,adlist.enabled FROM gravity LEFT JOIN adlist ON adlist.id = gravity.adlist_id WHERE domain = '${domain}'";;
- * ) querystr="SELECT gravity.domain,adlist.address,adlist.enabled FROM gravity LEFT JOIN adlist ON adlist.id = gravity.adlist_id WHERE domain LIKE '%${domain//_/\\_}%' ESCAPE '\\'";;
+ * ) querystr="SELECT gravity.domain,adlist.address,adlist.enabled FROM gravity LEFT JOIN adlist ON adlist.id = gravity.adlist_id WHERE ${searchstr} domain LIKE '%${domain//_/\\_}%' ESCAPE '\\'";;
esac
else
case "${exact}" in
- "exact" ) querystr="SELECT domain,enabled FROM domainlist WHERE type = '${type}' AND domain = '${domain}'";;
- * ) querystr="SELECT domain,enabled FROM domainlist WHERE type = '${type}' AND domain LIKE '%${domain//_/\\_}%' ESCAPE '\\'";;
+ "exact" ) querystr="SELECT domain,enabled FROM domainlist WHERE type = '${list_type}' AND domain = '${domain}'";;
+ * ) querystr="SELECT domain,enabled FROM domainlist WHERE type = '${list_type}' AND domain LIKE '%${domain//_/\\_}%' ESCAPE '\\'";;
esac
fi
# Send prepared query to gravity database
- result="$(pihole-FTL sqlite3 "${gravityDBfile}" "${querystr}")" 2> /dev/null
+ result="$(pihole-FTL sqlite3 -ni -separator ',' "${gravityDBfile}" "${querystr}")" 2> /dev/null
if [[ -z "${result}" ]]; then
# Return early when there are no matches in this table
return
@@ -136,19 +144,13 @@ scanDatabaseTable() {
wbMatch=true
# Print table name
- if [[ -z "${blockpage}" ]]; then
- echo " ${matchType^} found in ${COL_BOLD}exact ${table}${COL_NC}"
- fi
+ echo " ${matchType^} found in ${COL_BOLD}exact ${table}${COL_NC}"
# Loop over results and print them
mapfile -t results <<< "${result}"
for result in "${results[@]}"; do
- if [[ -n "${blockpage}" ]]; then
- echo "π ${result}"
- exit 0
- fi
- domain="${result/|*}"
- if [[ "${result#*|}" == "0" ]]; then
+ domain="${result/,*}"
+ if [[ "${result#*,}" == "0" ]]; then
extra=" (disabled)"
else
extra=""
@@ -158,20 +160,20 @@ scanDatabaseTable() {
}
scanRegexDatabaseTable() {
- local domain list
+ local domain list list_type
domain="${1}"
list="${2}"
- type="${3:-}"
+ list_type="${3:-}"
# Query all regex from the corresponding database tables
- mapfile -t regexList < <(pihole-FTL sqlite3 "${gravityDBfile}" "SELECT domain FROM domainlist WHERE type = ${type}" 2> /dev/null)
+ mapfile -t regexList < <(pihole-FTL sqlite3 -ni "${gravityDBfile}" "SELECT domain FROM domainlist WHERE type = ${list_type}" 2> /dev/null)
# If we have regexps to process
if [[ "${#regexList[@]}" -ne 0 ]]; then
# Split regexps over a new line
str_regexList=$(printf '%s\n' "${regexList[@]}")
# Check domain against regexps
- mapfile -t regexMatches < <(scanList "${domain}" "${str_regexList}" "regex")
+ mapfile -t regexMatches < <(scanRegExList "${domain}" "${str_regexList}")
# If there were regex matches
if [[ "${#regexMatches[@]}" -ne 0 ]]; then
# Split matching regexps over a new line
@@ -181,18 +183,13 @@ scanRegexDatabaseTable() {
# Form a "results" message
str_result="${COL_BOLD}${str_regexMatches}${COL_NC}"
# If we are displaying more than just the source of the block
- if [[ -z "${blockpage}" ]]; then
- # Set the wildcard match flag
- wcMatch=true
- # Echo the "matched" message, indented by one space
- echo " ${str_message}"
- # Echo the "results" message, each line indented by three spaces
- # shellcheck disable=SC2001
- echo "${str_result}" | sed 's/^/ /'
- else
- echo "π .wildcard"
- exit 0
- fi
+ # Set the wildcard match flag
+ wcMatch=true
+ # Echo the "matched" message, indented by one space
+ echo " ${str_message}"
+ # Echo the "results" message, each line indented by three spaces
+ # shellcheck disable=SC2001
+ echo "${str_result}" | sed 's/^/ /'
fi
fi
}
@@ -222,25 +219,23 @@ elif [[ -z "${all}" ]] && [[ "${#results[*]}" -ge 100 ]]; then
fi
# Print "Exact matches for" title
-if [[ -n "${exact}" ]] && [[ -z "${blockpage}" ]]; then
+if [[ -n "${exact}" ]]; then
plural=""; [[ "${#results[*]}" -gt 1 ]] && plural="es"
echo " ${matchType^}${plural} for ${COL_BOLD}${domainQuery}${COL_NC} found in:"
fi
for result in "${results[@]}"; do
- match="${result/|*/}"
- extra="${result#*|}"
- adlistAddress="${extra/|*/}"
- extra="${extra#*|}"
+ match="${result/,*/}"
+ extra="${result#*,}"
+ adlistAddress="${extra/,*/}"
+ extra="${extra#*,}"
if [[ "${extra}" == "0" ]]; then
extra=" (disabled)"
else
extra=""
fi
- if [[ -n "${blockpage}" ]]; then
- echo "0 ${adlistAddress}"
- elif [[ -n "${exact}" ]]; then
+ if [[ -n "${exact}" ]]; then
echo " - ${adlistAddress}${extra}"
else
if [[ ! "${adlistAddress}" == "${adlistAddress_prev:-}" ]]; then
diff --git a/advanced/Scripts/setupLCD.sh b/advanced/Scripts/setupLCD.sh
deleted file mode 100755
index 8252364323..0000000000
--- a/advanced/Scripts/setupLCD.sh
+++ /dev/null
@@ -1,74 +0,0 @@
-#!/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.
-#
-# Automatically configures the Pi to use the 2.8 LCD screen to display stats on it (also works over ssh)
-#
-# This file is copyright under the latest version of the EUPL.
-# Please see LICENSE file for your rights under this license.
-
-
-
-############ FUNCTIONS ###########
-
-# Borrowed from adafruit-pitft-helper < borrowed from raspi-config
-# https://github.com/adafruit/Adafruit-PiTFT-Helper/blob/master/adafruit-pitft-helper#L324-L334
-getInitSys() {
- if command -v systemctl > /dev/null && systemctl | grep -q '\-\.mount'; then
- SYSTEMD=1
- elif [ -f /etc/init.d/cron ] && [ ! -h /etc/init.d/cron ]; then
- SYSTEMD=0
- else
- echo "Unrecognized init system"
- return 1
- fi
-}
-
-# Borrowed from adafruit-pitft-helper:
-# https://github.com/adafruit/Adafruit-PiTFT-Helper/blob/master/adafruit-pitft-helper#L274-L285
-autoLoginPiToConsole() {
- if [ -e /etc/init.d/lightdm ]; then
- if [ ${SYSTEMD} -eq 1 ]; then
- systemctl set-default multi-user.target
- ln -fs /etc/systemd/system/autologin@.service /etc/systemd/system/getty.target.wants/getty@tty1.service
- else
- update-rc.d lightdm disable 2
- sed /etc/inittab -i -e "s/1:2345:respawn:\/sbin\/getty --noclear 38400 tty1/1:2345:respawn:\/bin\/login -f pi tty1 <\/dev\/tty1 >\/dev\/tty1 2>&1/"
- fi
- fi
-}
-
-######### SCRIPT ###########
-# Set pi to log in automatically
-getInitSys
-autoLoginPiToConsole
-
-# Set chronomter to run automatically when pi logs in
-echo /usr/local/bin/chronometer.sh >> /home/pi/.bashrc
-# OR
-#$SUDO echo /usr/local/bin/chronometer.sh >> /etc/profile
-
-# Set up the LCD screen based on Adafruits instuctions:
-# https://learn.adafruit.com/adafruit-pitft-28-inch-resistive-touchscreen-display-raspberry-pi/easy-install
-curl -SLs https://apt.adafruit.com/add-pin | bash
-apt-get -y install raspberrypi-bootloader
-apt-get -y install adafruit-pitft-helper
-adafruit-pitft-helper -t 28r
-
-# Download the cmdline.txt file that prevents the screen from going blank after a period of time
-mv /boot/cmdline.txt /boot/cmdline.orig
-curl -o /boot/cmdline.txt https://raw.githubusercontent.com/pi-hole/pi-hole/master/advanced/cmdline.txt
-
-# Back up the original file and download the new one
-mv /etc/default/console-setup /etc/default/console-setup.orig
-curl -o /etc/default/console-setup https://raw.githubusercontent.com/pi-hole/pi-hole/master/advanced/console-setup
-
-# Instantly apply the font change to the LCD screen
-setupcon
-
-reboot
-
-# Start showing the stats on the screen by running the command on another 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/speedtestmod/lib.sh b/advanced/Scripts/speedtestmod/lib.sh
new file mode 100755
index 0000000000..b0c7d42f67
--- /dev/null
+++ b/advanced/Scripts/speedtestmod/lib.sh
@@ -0,0 +1,511 @@
+#!/bin/bash
+#
+# The Library Script, Speedtest Mod for Pi-hole Helper Functions
+#
+# shellcheck disable=SC2015
+#
+
+declare PKG_MANAGER
+PKG_MANAGER=$(command -v apt-get || command -v dnf || command -v yum)
+readonly PKG_MANAGER
+
+#######################################
+# Get the version of a repository, either from a local clone or from the installed package
+# Globals:
+# None
+# Arguments:
+# $1: The path to, or name of, the repository
+# $2: Non-empty string to get the hash, empty string to get the tag if it exists
+# Returns:
+# The version of the repository
+#######################################
+getVersion() {
+ local found_version=""
+
+ if [[ -d "$1" && -d "$1/.git" ]]; then
+ pushd "$1" &>/dev/null || exit 1
+ found_version=$(git status --porcelain=2 -b | grep branch.oid | awk '{print $3;}')
+ [[ $found_version != *"("* ]] || found_version=$(git rev-parse HEAD 2>/dev/null)
+
+ if [[ -z "${2:-}" ]]; then
+ local tags
+ local found_tag=$found_version
+ tags=$(git ls-remote -t origin || git show-ref --tags)
+ ! grep -q "$found_version" <<<"$tags" || found_tag=$(grep "$found_version" <<<"$tags" | awk '{print $2;}' | cut -d '/' -f 3 | sort -V | tail -n1)
+ [[ -z "$found_tag" ]] || found_version=$found_tag
+ fi
+
+ popd &>/dev/null || exit 1
+ elif [[ -x "$(command -v pihole)" ]]; then
+ local versions
+ versions=$(pihole -v | grep "$1")
+ found_version=$(cut -d ' ' -f 6 <<<"$versions")
+ [[ "$found_version" == *.* || ${#found_version} -ge 40 ]] || found_version=$(cut -d ' ' -f 7 <<<"$versions")
+ fi
+
+ echo "$found_version"
+}
+
+#######################################
+# Fetch a repository, optionally a specific version
+# Globals:
+# None
+# Arguments:
+# $1: The path to download the repository to
+# $2: The name of the repository
+# $3: The URL of the repository
+# $4: The desired version, hash or tag, to download (optional, none by default)
+# $5: The branch to download (optional, master by default)
+# $6: Whether to snap to the tag (optional, true by default)
+# Outputs:
+# The repository at the desired version
+#######################################
+download() {
+ local path=$1
+ local name=$2
+ local url=$3
+ local desired_version="${4:-}"
+ local branch="${5:-master}"
+ local snap_to_tag="${6:-true}"
+ local dest=$path/$name
+ local aborting=false
+
+ [[ ! -d "$dest" || -d "$dest/.git" ]] || mv -f "$dest" "$dest.old"
+ [[ -d "$dest" ]] || git clone --depth=1 -b "$branch" "$url" "$dest" -q
+ pushd "$dest" &>/dev/null || exit 1
+ git config --global --add safe.directory "$dest"
+
+ if [[ -n "$desired_version" && "$desired_version" != *.* ]]; then
+ local repos=("Pi-hole" "web" "speedtest")
+
+ for repo in "${repos[@]}"; do
+ if [[ "$desired_version" == *"$repo"* ]]; then
+ aborting=true
+ break
+ fi
+ done
+ fi
+
+ if ! $aborting; then
+ ! git remote -v | grep -q "old" && git remote -v | grep -q "origin" && git remote rename origin old || :
+ ! git remote -v | grep -q "origin" || git remote remove origin
+ git remote add -t "$branch" origin "$url"
+ elif git remote -v | grep -q "old"; then
+ ! git remote -v | grep -q "origin" || git remote remove origin
+ git remote rename old origin
+ url=$(git remote get-url origin)
+ fi
+
+ [[ "$url" != *"ipitio"* ]] || snap_to_tag=$(grep -q "true" <<<"$snap_to_tag" && echo "false" || echo "true")
+ git fetch origin --depth=1 "$branch":refs/remotes/origin/"$branch" -q
+ git reset --hard origin/"$branch" -q
+ git checkout -B "$branch" -q
+ local current_hash
+ local tags
+ current_hash=$(getVersion "$dest" hash)
+ tags=$(git ls-remote -t origin || git show-ref --tags)
+
+ if [[ -z "$desired_version" ]]; then # if empty, get the latest version
+ local latest_tag=""
+ [[ "$snap_to_tag" != "true" ]] || latest_tag=$(awk -F/ '{print $3}' <<<"$tags" | grep '^v[0-9]' | grep -v '\^{}' | sort -V | tail -n1)
+ [[ -n "$latest_tag" ]] && desired_version=$latest_tag || desired_version=$current_hash
+ elif $aborting; then
+ desired_version=$(getVersion "$desired_version" hash)
+ fi
+
+ if [[ "$desired_version" == *.* ]]; then
+ grep -q "$desired_version$" <<<"$tags" && desired_version=$(grep "$desired_version$" <<<"$tags" | awk '{print $1;}') || desired_version=$current_hash
+ fi
+
+ if [[ "$current_hash" != "$desired_version" ]]; then
+ git fetch origin --depth=1 "$desired_version" -q
+ git reset --hard "$desired_version" -q
+ fi
+
+ popd &>/dev/null || exit 1
+}
+
+#######################################
+# Check if the package is available
+# Globals:
+# PKG_MANAGER
+# Arguments:
+# $1: Package name
+# Returns:
+# 0 if available, 1 if not
+#######################################
+isAvailable() {
+ if [[ "$PKG_MANAGER" == *"apt"* ]]; then
+ # Check if there is a candidate and it is not "(none)"
+ apt-cache policy "$1" | grep -q "Candidate:" && ! apt-cache policy "$1" | grep -q "Candidate: (none)" && return 0 || return 1
+ elif [[ "$PKG_MANAGER" == *"dnf"* || "$PKG_MANAGER" == *"yum"* ]]; then
+ $PKG_MANAGER list available "$1" &>/dev/null && return 0 || return 1
+ else
+ echo "Unsupported package manager!"
+ exit 1
+ fi
+}
+
+#######################################
+# Check if a package is installed, only used below when --continuous is not
+# Globals:
+# None
+# Arguments:
+# $1: The package to check
+# Returns:
+# 0 if the package is not installed, 1 if it is
+#######################################
+notInstalled() {
+ if [[ "$PKG_MANAGER" == *"apt"* ]]; then
+ dpkg -s "$1" &>/dev/null || return 0
+ elif [[ "$PKG_MANAGER" == *"dnf"* || "$PKG_MANAGER" == *"yum"* ]]; then
+ rpm -q "$1" &>/dev/null || return 0
+ else
+ echo "Unsupported package manager!"
+ exit 1
+ fi
+
+ return 1
+}
+
+#######################################
+# Set a key-value pair in a configuration file, used below for --reinstall
+# Globals:
+# None
+# Arguments:
+# $1: The key to set
+# $2: The value to set
+# $3: The configuration file to set the key-value pair in
+# $4: Whether to replace the value if it already exists
+# Outputs:
+# The configuration file with the key-value pair set
+#######################################
+setCnf() {
+ grep -q "^$1=" "$3" || echo "$1=$2" >>"$3"
+ [[ "${4:-false}" == "true" ]] || sed -i "s|^$1=.*|$1=$2|" "$3"
+}
+
+#######################################
+# Get a key-value pair from a configuration file, used below for --reinstall
+# Globals:
+# None
+# Arguments:
+# $1: The configuration file to get the key-value pair from
+# $2: The key to get the value of
+# $3: Non-empty string to get the hash, empty string to get the tag if it exists
+# Returns:
+# The value of the key-value pair
+#######################################
+getCnf() {
+ local keydir
+ local value
+ keydir=$(echo "$2" | sed 's/^mod-//;s/^org-//')
+ value=$(grep "^$2=" "$1" | cut -d '=' -f 2)
+ [[ -n "$value" ]] || value=$(getVersion "$keydir" "${3:-}")
+ echo "$value"
+}
+
+#######################################
+# Download and install librespeed
+# Globals:
+# PKG_MANAGERsetupVars
+# Arguments:
+# None
+# Returns:
+# 0 if the installation was successful, 1 if it was not
+#######################################
+libreSpeed() {
+ echo "Installing librespeed-cli..."
+ $PKG_MANAGER remove -y speedtest-cli speedtest >/dev/null 2>&1
+
+ if notInstalled golang; then
+ if grep -q "Raspbian" /etc/os-release; then
+ if [[ ! -f /etc/apt/sources.list.d/testing.list ]] && ! grep -q "testing" /etc/apt/sources.list; then
+ echo "Adding testing repo to sources.list.d"
+ echo "deb http://archive.raspbian.org/raspbian/ testing main" >/etc/apt/sources.list.d/testing.list
+ printf "Package: *\nPin: release a=testing\nPin-Priority: 50" >/etc/apt/preferences.d/limit-testing
+ $PKG_MANAGER update -y &>/dev/null
+ fi
+
+ isAvailable golang || $PKG_MANAGER update -y &>/dev/null
+ $PKG_MANAGER install -y -t testing golang >/dev/null 2>&1
+ else
+ [[ $PKG_MANAGER == *"apt"* ]] && ! isAvailable golang && $PKG_MANAGER update -y &>/dev/null || :
+ $PKG_MANAGER install -y golang >/dev/null 2>&1
+ fi
+ fi
+
+ download /etc/pihole librespeed https://github.com/librespeed/speedtest-cli
+ pushd /etc/pihole/librespeed &>/dev/null || return 1
+ [[ ! -d out ]] || rm -rf out
+ ./build.sh
+ rm -f /usr/bin/speedtest
+ mv -f out/* /usr/bin/speedtest
+ popd &>/dev/null || return 1
+ chmod +x /usr/bin/speedtest
+
+ if [[ -x /usr/bin/speedtest ]]; then
+ echo "Installed librespeed-cli"
+ return 0
+ fi
+
+ echo "Installation of $candidate Failed!"
+ return 1
+}
+
+#######################################
+# Install a package, removing a conflicting package if necessary
+# Globals:
+# PKG_MANAGER
+# Arguments:
+# None
+# Outputs:
+# The installed package
+#######################################
+swivelSpeed() {
+ local candidate="${1:-speedtest-cli}"
+ local target="${2:-speedtest}"
+ [[ ! -f /usr/bin/speedtest ]] || rm -f /usr/bin/speedtest
+ echo "Installing $candidate..."
+
+ case "$PKG_MANAGER" in
+ /usr/bin/apt-get)
+ ! isAvailable "$candidate" && echo "And Updating Package Cache..." && $PKG_MANAGER update -y &>/dev/null || :
+ "$PKG_MANAGER" install -y "$candidate" "$target"- &>/dev/null
+ ;;
+ /usr/bin/dnf) "$PKG_MANAGER" install -y --allowerasing "$candidate" &>/dev/null ;;
+ /usr/bin/yum) "$PKG_MANAGER" install -y --allowerasing "$candidate" &>/dev/null ;;
+ esac
+
+ if ! notInstalled "$candidate" && [[ -x /usr/bin/speedtest ]]; then
+ printf "Installed "
+ local version=
+ version=$(/usr/bin/speedtest --version) || return 1
+ echo "${version%%$'\n'*}"
+ return 0
+ fi
+
+ echo "Installation of $candidate Failed!"
+ return 1
+}
+
+#######################################
+# Add the Ookla speedtest CLI source and install the package
+# Globals:
+# PKG_MANAGER
+# Arguments:
+# None
+# Outputs:
+# The source for the speedtest CLI and the package
+#######################################
+ooklaSpeed() {
+ if [[ "$PKG_MANAGER" == *"yum"* || "$PKG_MANAGER" == *"dnf"* && ! -f /etc/yum.repos.d/ookla_speedtest-cli.repo ]]; then
+ echo "Adding speedtest source for RPM..."
+ curl -sSLN https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh | sudo bash
+ elif [[ "$PKG_MANAGER" == *"apt"* && ! -f /etc/apt/sources.list.d/ookla_speedtest-cli.list ]]; then
+ echo "Adding speedtest source for DEB..."
+ if [[ -e /etc/os-release ]]; then
+ # shellcheck disable=SC1091
+ source /etc/os-release
+ local -r base="ubuntu debian"
+ local os=${ID}
+ local dist=${VERSION_CODENAME}
+ # shellcheck disable=SC2076
+ if [[ -n "${ID_LIKE:-}" && "${base//\"/}" =~ "${ID_LIKE//\"/}" && "${os}" != "ubuntu" ]]; then
+ os=${ID_LIKE%% *}
+ [[ -z "${UBUNTU_CODENAME:-}" ]] && UBUNTU_CODENAME=$(/usr/bin/lsb_release -cs)
+ dist=${UBUNTU_CODENAME}
+ [[ -z "$dist" ]] && dist=${VERSION_CODENAME}
+ fi
+ wget -O /tmp/script.deb.sh https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh >/dev/null 2>&1
+ chmod +x /tmp/script.deb.sh
+ os=$os dist=$dist /tmp/script.deb.sh >/dev/null 2>&1
+ rm -f /tmp/script.deb.sh
+ else
+ curl -sSLN https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh | sudo bash >/dev/null 2>&1
+ fi
+
+ sed -i 's/g]/g allow-insecure=yes trusted=yes]/' /etc/apt/sources.list.d/ookla_speedtest-cli.list
+ apt-get update -y &>/dev/null
+ fi
+
+ swivelSpeed speedtest speedtest-cli
+}
+
+#######################################
+# Use an interval to generate a systemd calendar
+# Globals:
+# None
+# Arguments:
+# $1: The interval in hours, down to the minute
+# Outputs:
+# The systemd unit and timer files
+#######################################
+generate_systemd_service() {
+ local interval_hours="$1"
+ local freq_entries=()
+ local total_seconds
+ total_seconds=$(echo "$interval_hours * 3600" | bc)
+
+ if (($(echo "$total_seconds < 60" | bc -l))); then # less than a minute
+ total_seconds=60
+ addOrEditKeyValPair "/etc/pihole/setupVars.conf" "SPEEDTESTSCHEDULE" "0.017"
+ fi
+
+ if (($(echo "$total_seconds >= 60 && $total_seconds < 3600" | bc -l))); then # less than an hour
+ local minute_interval
+ minute_interval=$(echo "$total_seconds / 60" | bc)
+ freq_entries+=("*-*-* *:00/$minute_interval:00")
+ elif (($(echo "$total_seconds == 3600" | bc -l))); then # exactly an hour
+ freq_entries+=("*-*-* *:00:00")
+ elif (($(echo "$total_seconds < 86400" | bc -l))); then # less than a day
+ if (($(awk "BEGIN {print ($total_seconds / 3600) % 1}") == 0)); then # divides evenly into an hour
+ local hour_interval
+ hour_interval=$(echo "$total_seconds / 3600" | bc)
+ freq_entries+=("*-*-* 00/$hour_interval:00:00")
+ else # does not divide evenly into an hour
+ local current_second=0
+
+ while (($(echo "$current_second < 86400" | bc -l))); do
+ local hour
+ hour=$(echo "$current_second / 3600" | bc)
+ local minute
+ minute=$(awk "BEGIN {print ($current_second % 3600) / 60}")
+ hour=${hour%.*}
+ minute=${minute%.*}
+ freq_entries+=("*-*-* $(printf "%02d:%02d:00" "$hour" "$minute")")
+ current_second=$(echo "$current_second + $total_seconds" | bc)
+ done
+ fi
+ else # more than a day
+ local full_days
+ local remaining_hours
+ full_days=$(echo "$interval_hours / 24" | bc)
+ remaining_hours=$(echo "$interval_hours - ($full_days * 24)" | bc)
+
+ if (($(echo "$full_days > 0" | bc -l))); then
+ freq_entries+=("*-*-1/$(printf "%02.0f" "$full_days")")
+ fi
+
+ if (($(echo "$remaining_hours > 0" | bc -l))); then # partial day
+ local remaining_minutes
+ remaining_minutes=$(echo "($remaining_hours - ($remaining_hours / 1)) * 60" | bc)
+ remaining_hours=${remaining_hours%.*}
+ remaining_minutes=${remaining_minutes%.*}
+ freq_entries+=("*-*-* $(printf "%02d:%02d:00" "$remaining_hours" "$remaining_minutes")")
+ fi
+ fi
+
+ sudo bash -c 'cat > /etc/systemd/system/pihole-speedtest.service << EOF
+[Unit]
+Description=Pi-hole Speedtest
+After=network.target
+
+[Service]
+User=root
+Type=forking
+ExecStart=/usr/local/bin/pihole -a -sn
+
+[Install]
+WantedBy=multi-user.target
+EOF'
+ sudo bash -c 'cat > /etc/systemd/system/pihole-speedtest.timer << EOF
+[Unit]
+Description=Pi-hole Speedtest Timer
+
+[Install]
+WantedBy=timers.target
+
+[Timer]
+Persistent=true
+EOF'
+
+ for freq in "${freq_entries[@]}"; do
+ sudo bash -c "echo 'OnCalendar=$freq' >> /etc/systemd/system/pihole-speedtest.timer"
+ done
+
+ systemctl daemon-reload
+ systemctl reenable pihole-speedtest.timer &>/dev/null
+ systemctl restart pihole-speedtest.timer
+}
+
+#######################################
+# Save the interval to a file that is run by cron every minute
+# Globals:
+# None
+# Arguments:
+# $1: The interval in hours
+# Outputs:
+# The interval in a file and the cron job
+#######################################
+generate_cron_job() {
+ local total_seconds="nan"
+ local schedule_script="/opt/pihole/speedtestmod/schedule_check.sh"
+
+ if [[ "$1" != "nan" ]] && [[ "$1" =~ ^([0-9]+(\.[0-9]*)?|\.[0-9]+)$ ]] && (($(echo "$1 > 0" | bc -l))); then
+ total_seconds=$(echo "$1 * 3600" | bc)
+ if (($(echo "$total_seconds < 60" | bc -l))); then
+ total_seconds=60
+ fi
+
+ local remainder
+ remainder=$(awk "BEGIN {print $total_seconds % 60}")
+
+ if (($(echo "$remainder < 30" | bc -l))); then
+ total_seconds=$(echo "$total_seconds - $remainder" | bc -l)
+ else
+ total_seconds=$(echo "$total_seconds + (60 - $remainder)" | bc -l)
+ fi
+
+ addOrEditKeyValPair "/etc/pihole/setupVars.conf" "SPEEDTESTSCHEDULE" "$(echo "scale=3; $total_seconds / 3600" | bc)"
+ fi
+
+ [ -d /opt/pihole/speedtestmod ] || return
+ sudo bash -c "cat > $(printf %q "$schedule_script")" < 0" | bc -l) )) || exit 0
+
+if [[ -f "\$LAST_RUN_FILE" ]]; then
+ declare last_run
+ last_run=\$(<"\$LAST_RUN_FILE")
+ current_time=\$(date +%s)
+ (( \$(echo "\$current_time - \$last_run >= \$INTERVAL_SECONDS" | bc -l) )) || exit 0
+fi
+
+[[ \$(/usr/bin/tmux list-sessions 2>/dev/null | grep -c pimodtest) -eq 0 ]] || exit 0
+echo "\$current_time" > "\$LAST_RUN_FILE"
+/usr/bin/tmux new-session -d -s pimodtest "sudo bash /opt/pihole/speedtestmod/speedtest.sh"
+EOF
+ sudo chmod +x "$schedule_script"
+
+ crontab -l 2>/dev/null | grep -v "$schedule_script" | crontab -
+
+ if [[ "$total_seconds" == "nan" ]] || (($(echo "$total_seconds > 0" | bc -l))); then
+ crontab -l &>/dev/null || crontab -l 2>/dev/null | {
+ cat
+ echo ""
+ } | crontab -
+ (
+ crontab -l
+ echo "* * * * * /bin/bash $schedule_script"
+ ) | crontab -
+ fi
+}
diff --git a/advanced/Scripts/speedtestmod/mod.sh b/advanced/Scripts/speedtestmod/mod.sh
new file mode 100755
index 0000000000..e2e5e57d0a
--- /dev/null
+++ b/advanced/Scripts/speedtestmod/mod.sh
@@ -0,0 +1,562 @@
+#!/bin/bash
+#
+# The Mod Script, Speedtest Mod for Pi-hole Installation Manager
+# Please run this with the --help option for usage information
+#
+# shellcheck disable=SC2015
+#
+
+declare -r MOD_REPO="arevindh"
+declare -r MOD_BRANCH="master"
+declare -r CORE_BRANCH="master"
+declare -r ADMIN_BRANCH="master"
+declare -r HTML_DIR="/var/www/html"
+declare -r CORE_DIR="/etc/.pihole"
+declare -r OPT_DIR="/opt/pihole"
+declare -r ETC_DIR="/etc/pihole"
+declare -r MOD_DIR="/etc/pihole-speedtest"
+declare -r CURR_WP="$OPT_DIR/webpage.sh"
+declare -r CURR_DB="$ETC_DIR/speedtest.db"
+declare -r LAST_DB="$CURR_DB.old"
+declare -r DB_TABLE="speedtest"
+declare cleanup
+declare aborted
+cleanup=$(mktemp)
+aborted=$(mktemp)
+echo "false" >"$cleanup"
+echo "false" >"$aborted"
+# shellcheck disable=SC2034
+SKIP_INSTALL=true
+# shellcheck disable=SC1091
+source "$CORE_DIR/automated install/basic-install.sh"
+# shellcheck disable=SC1090,SC1091
+[[ -f "$OPT_DIR/speedtestmod/lib.sh" ]] && source "$OPT_DIR/speedtestmod/lib.sh" || source <(curl -sSLN https://github.com/"$MOD_REPO"/pi-hole/raw/"$CORE_BRANCH"/advanced/Scripts/speedtestmod/lib.sh)
+
+#######################################
+# Display the help message
+# Globals:
+# None
+# Arguments:
+# None
+# Outputs:
+# The help message
+#######################################
+help() {
+ local -r help_text=(
+ "The Mod Script"
+ "Usage: sudo bash /path/to/mod.sh [options]"
+ " or: curl -sSLN //link/to/mod.sh | sudo bash [-s -- options]"
+ " or: pihole -a -sm [options]"
+ "(Re)install Speedtest Mod and/or the following options:"
+ ""
+ "Installation:"
+ " -u, --update, up also update Pi-hole"
+ " -r, --reinstall repair currently installed version of the Mod"
+ " -t, --testing try unstable changes"
+ ""
+ "Restoration:"
+ " -n, --uninstall, un purge the Mod, keeping the speedtest package, logs, and database"
+ " -b, --backup backup Pi-hole for faster offline restore"
+ " -o, --online force online restore of Pi-hole"
+ ""
+ "Standalone:"
+ " -d, --database, db flush/restore the database if it's not/empty"
+ " -s, --speedtest[=] install Ookla's or the specified CLI immediately"
+ " -x, --verbose show the commands being run"
+ " -v, --version display the installed version of the Mod and exit"
+ " -h, --help display this help message and exit"
+ ""
+ "Examples:"
+ " pihole -a -sm -d -slibre"
+ " sudo bash /opt/pihole/speedtestmod/mod.sh --update"
+ " curl -sSL https://github.com/$MOD_REPO/pihole-speedtest/raw/$CORE_BRANCH/mod | sudo bash"
+ " curl -sSLN https://github.com/$MOD_REPO/pi-hole/raw/$CORE_BRANCH/advanced/Scripts/speedtestmod/mod.sh | sudo bash -s -- -bo"
+ )
+
+ printf "%s\n" "${help_text[@]}"
+}
+
+#######################################
+# Check if a database is empty
+# Globals:
+# DB_TABLE
+# Arguments:
+# $1: The database to check
+# Returns:
+# 0 if the database is empty, 1 if it is not
+#######################################
+isEmpty() {
+ [[ -f "$1" ]] && sqlite3 "$1" "select * from $DB_TABLE limit 1;" &>/dev/null && [[ -n "$(sqlite3 "$1" "select * from $DB_TABLE limit 1;")" ]] && return 1 || return 0
+}
+
+#######################################
+# Copy scripts from the CORE to the OPT repository
+# Globals:
+# OPT_DIR
+# Arguments:
+# None
+# Outputs:
+# The scripts copied to the OPT repository
+#######################################
+swapScripts() {
+ set +u
+ installScripts >/dev/null 2>&1
+ set -u
+}
+
+#######################################
+# Restore a backup, used after --backup unless --online or --install are used
+# Globals:
+# None
+# Arguments:
+# $1: The backup to restore
+# Returns:
+# 1 if the backup does not exist or is stale, 0 if it does and isn't
+# Outputs:
+# The backup restored
+#######################################
+restore() {
+ [[ -d "$1".bak && "$(getVersion "$1".bak hash)" == "$(getCnf $MOD_DIR/cnf org-"$1" hash)" ]] || return 1
+ [[ ! -e "$1" ]] || rm -rf "$1"
+ mv -f "$1".bak "$1"
+}
+
+#######################################
+# Purge the mod, used for --uninstall
+# Globals:
+# CORE_DIR
+# HTML_DIR
+# OPT_DIR
+# CURR_DB
+# ETC_DIR
+# Arguments:
+# None
+# Outputs:
+# The mod purged
+#######################################
+purge() {
+ if [[ -f /etc/systemd/system/pihole-speedtest.timer ]]; then
+ rm -f /etc/systemd/system/pihole-speedtest.service
+ rm -f /etc/systemd/system/pihole-speedtest.timer
+ systemctl daemon-reload
+ fi
+
+ rm -rf $OPT_DIR/speedtestmod
+ rm -rf $CORE_DIR.bak
+ rm -rf $HTML_DIR/admin.bak
+ rm -rf $CORE_DIR.mod
+ rm -rf $HTML_DIR/admin.mod
+ rm -f "$CURR_DB".*
+ rm -f $ETC_DIR/last_speedtest.*
+ ! isEmpty $CURR_DB || rm -f $CURR_DB
+}
+
+#######################################
+# Abort the process
+# Globals:
+# CORE_DIR
+# HTML_DIR
+# MOD_DIR
+# OPT_DIR
+# CURR_WP
+# CURR_DB
+# LAST_DB
+# ETC_DIR
+# cleanup
+# aborted
+# Arguments:
+# None
+# Outputs:
+# The changes reverted
+# shellcheck disable=SC2317 ###########
+abort() {
+ if grep -q true "$cleanup" && grep -q false "$aborted"; then
+ echo "Process Aborting..."
+ echo "true" >"$aborted"
+
+ if [[ -d "$CORE_DIR"/.git/refs/remotes/old ]]; then
+ download /etc .pihole "" Pi-hole
+ swapScripts
+
+ if [[ -d "$CORE_DIR"/advanced/Scripts/speedtestmod ]]; then
+ \cp -af "$CORE_DIR"/advanced/Scripts/speedtestmod/. "$OPT_DIR"/speedtestmod/
+ pihole -a -s
+ fi
+ fi
+
+ [[ ! -d "$MOD_DIR" || ! -d "$MOD_DIR"/.git/refs/remotes/old ]] || download /etc pihole-speedtest "" speedtest
+ [[ ! -d "$HTML_DIR"/admin/.git/refs/remotes/old ]] || download "$HTML_DIR" admin "" web
+ [[ ! -f "$LAST_DB" || -f "$CURR_DB" ]] || mv "$LAST_DB" "$CURR_DB"
+ [[ -f "$CURR_WP" ]] && ! grep -q SpeedTest "$CURR_WP" && purge || :
+ printf "Please try again before reporting an issue.\n\n%s\n" "$(date)"
+ fi
+}
+
+#######################################
+# Commit the changes
+# Globals:
+# CORE_DIR
+# HTML_DIR
+# cleanup
+# Arguments:
+# None
+# Outputs:
+# The repositories cleaned up
+# shellcheck disable=SC2317 ###########
+commit() {
+ if grep -q true "$cleanup"; then
+ for dir in $CORE_DIR $HTML_DIR/admin; do
+ [[ ! -d "$dir" ]] && continue || pushd "$dir" &>/dev/null || exit 1
+ ! git remote -v | grep -q "old" || git remote remove old
+ git clean -ffdx
+ popd &>/dev/null
+ done
+
+ printf "Done!\n\n%s\n" "$(date)"
+ fi
+}
+
+#######################################
+# Manage the installation
+# Globals:
+# CORE_DIR
+# HTML_DIR
+# MOD_DIR
+# OPT_DIR
+# CURR_WP
+# CURR_DB
+# LAST_DB
+# ETC_DIR
+# cleanup
+# Arguments:
+# $@: The options for managing the installation
+# Outputs:
+# The installation managed
+#######################################
+main() {
+ set -u
+
+ local -r short_opts=-ubortnds::vxh
+ local -r long_opts=update,backup,online,reinstall,testing,uninstall,database,speedtest::,version,verbose,help
+ local parsed_opts
+
+ if ! parsed_opts=$(getopt --options ${short_opts} --longoptions ${long_opts} --name "$0" -- "$@"); then
+ help
+ return 1
+ fi
+
+ eval set -- "${parsed_opts}"
+
+ declare -a POSITIONAL EXTRA_ARGS
+ local -i dashes=0
+ local update=false
+ local backup=false
+ local online=false
+ local reinstall=false
+ local stable=true
+ local uninstall=false
+ local database=false
+ local verbose=false
+ local select_test=false
+ local selected_test=""
+ local do_main=false
+ local st_ver=""
+ local mod_core_ver=""
+ local mod_admin_ver=""
+
+ while [[ $# -gt 0 ]]; do
+ case "$1" in
+ -u | --update)
+ update=true
+ do_main=true
+ ;;
+ -b | --backup)
+ backup=true
+ do_main=true
+ ;;
+ -o | --online)
+ online=true
+ do_main=true
+ ;;
+ -r | --reinstall)
+ reinstall=true
+ do_main=true
+ ;;
+ -t | --testing)
+ stable=false
+ do_main=true
+ ;;
+ -n | --uninstall)
+ uninstall=true
+ do_main=true
+ ;;
+ -d | --database) database=true ;;
+ -s | --speedtest)
+ select_test=true
+
+ if [[ -n "$2" && ! "$2" =~ sivel|libre ]]; then
+ help
+ return 1
+ fi
+
+ selected_test=$2
+ shift
+ ;;
+ -v | --version)
+ getVersion $MOD_DIR
+ return 0
+ ;;
+ -x | --verbose) verbose=true ;;
+ -h | --help)
+ help
+ return 0
+ ;;
+ --) dashes=1 ;;
+ *) [[ $dashes -eq 0 ]] && POSITIONAL+=("$1") || EXTRA_ARGS+=("$1") ;;
+ esac
+ shift
+ done
+
+ set -- "${POSITIONAL[@]}"
+
+ # backward compatibility
+ for arg in "$@"; do
+ case $arg in
+ up) update=true ;;
+ un) uninstall=true ;;
+ db) database=true ;;
+ *)
+ help
+ return 1
+ ;;
+ esac
+ done
+
+ echo "true" >"$cleanup"
+ ! $do_main && ! $database && ! $select_test && do_main=true || :
+ readonly update backup online reinstall stable uninstall database verbose select_test selected_test do_main
+ printf "%s\n\nRunning the Mod Script by @ipitio...\n" "$(date)"
+ ! $verbose || set -x
+
+ if $select_test; then
+ case $selected_test in
+ sivel) swivelSpeed ;;
+ libre) libreSpeed ;;
+ *) ooklaSpeed ;;
+ esac
+ fi
+
+ set -Eeo pipefail
+ trap '[ "$?" -eq "0" ] && commit || abort' EXIT
+ trap 'abort' INT TERM ERR
+ shopt -s dotglob
+
+ if $database; then
+ if [[ -f $CURR_DB ]] && ! isEmpty $CURR_DB; then
+ echo "Flushing Database..."
+ mv -f $CURR_DB $LAST_DB
+ [[ ! -f $ETC_DIR/last_speedtest ]] || mv -f $ETC_DIR/last_speedtest $ETC_DIR/last_speedtest.old
+
+ if [[ -f /var/log/pihole/speedtest.log ]]; then
+ mv -f /var/log/pihole/speedtest.log /var/log/pihole/speedtest.log.old
+ rm -f $ETC_DIR/speedtest.log
+ fi
+ elif [[ -f $LAST_DB ]]; then
+ echo "Restoring Database..."
+ mv -f $LAST_DB $CURR_DB
+ [[ ! -f $ETC_DIR/last_speedtest.old ]] || mv -f $ETC_DIR/last_speedtest.old $ETC_DIR/last_speedtest
+
+ if [[ -f /var/log/pihole/speedtest.log.old ]]; then
+ mv -f /var/log/pihole/speedtest.log.old /var/log/pihole/speedtest.log
+ \cp -af /var/log/pihole/speedtest.log $ETC_DIR/speedtest.log
+ fi
+ fi
+ fi
+
+ if $do_main; then
+ if [[ ! -f /usr/local/bin/pihole ]]; then
+ # https://discourse.pi-hole.net/t/pi-hole-as-part-of-a-post-installation-script/3523/15
+ if [[ ! -f /etc/pihole/setupVars.conf ]]; then
+ cat </etc/pihole/setupVars.conf
+WEBPASSWORD={{ pihole_admin_password | hash('sha256') | hash('sha256') }}
+PIHOLE_INTERFACE=eth0
+IPV4_ADDRESS=192.168.x.y/24
+IPV6_ADDRESS=fd00::2
+QUERY_LOGGING=true
+INSTALL_WEB_INTERFACE=true
+LIGHTTPD_ENABLED=false
+INSTALL_WEB_SERVER=false
+DNSMASQ_LISTENING=single
+PIHOLE_DNS_1=8.8.8.8
+PIHOLE_DNS_2=4.4.4.4
+PIHOLE_DNS_3=2001:4860:4860:0:0:0:0:8888
+PIHOLE_DNS_4=2001:4860:4860:0:0:0:0:8844
+DNS_FQDN_REQUIRED=true
+DNS_BOGUS_PRIV=true
+DNSSEC=false
+TEMPERATUREUNIT=C
+WEBUIBOXEDLAYOUT=traditional
+API_EXCLUDE_DOMAINS=
+API_EXCLUDE_CLIENTS=
+API_QUERY_LOG_SHOW=all
+API_PRIVACY_MODE=false
+BLOCKING_ENABLED=true
+REV_SERVER=true
+REV_SERVER_CIDR=192.168.x.0/24
+REV_SERVER_TARGET=192.168.x.z
+REV_SERVER_DOMAIN=your.domain
+CACHE_SIZE=10000
+EOF
+ fi
+
+ echo "Installing Pi-hole..."
+ curl -sSL https://install.pi-hole.net | sudo bash /dev/stdin --unattended
+ fi
+
+ pushd ~ >/dev/null || exit 1
+ pihole updatechecker
+ pihole -v || :
+
+ if [[ -f $CURR_WP ]] && grep -q SpeedTest "$CURR_WP"; then
+ if $reinstall; then
+ for repo in $CORE_DIR $HTML_DIR/admin $MOD_DIR; do
+ if [[ -d "$repo" ]]; then
+ local hash_tag
+ hash_tag=$(getVersion "$repo") # if hashes are the same, we may be on an older tag
+ [[ "$(getVersion "$repo" hash)" != "$(getCnf $MOD_DIR/cnf mod-"$repo" hash)" ]] || hash_tag=$(getCnf $MOD_DIR/cnf mod-"$repo")
+
+ case "$repo" in
+ "$CORE_DIR") mod_core_ver=$hash_tag ;;
+ "$HTML_DIR/admin") mod_admin_ver=$hash_tag ;;
+ "$MOD_DIR") st_ver=$hash_tag ;;
+ esac
+ fi
+ done
+ fi
+
+ local core_ver=""
+ local admin_ver=""
+ echo "Restoring Pi-hole$($online && echo " Online..." || echo "...")"
+ pihole -a -s -1
+
+ if [[ -f $MOD_DIR/cnf ]]; then
+ core_ver=$(getCnf $MOD_DIR/cnf org-$CORE_DIR)
+ admin_ver=$(getCnf $MOD_DIR/cnf org-$HTML_DIR/admin)
+ fi
+
+ readonly core_ver admin_ver
+ ! $online && restore $HTML_DIR/admin || download $HTML_DIR admin https://github.com/pi-hole/AdminLTE "$admin_ver"
+ ! $online && restore $CORE_DIR || download /etc .pihole https://github.com/pi-hole/pi-hole "$core_ver"
+ [[ ! -d $MOD_DIR ]] || rm -rf $MOD_DIR
+ swapScripts
+
+ for repo in $CORE_DIR $HTML_DIR/admin; do
+ pushd "$repo" &>/dev/null || exit 1
+ git tag -l | xargs git tag -d >/dev/null 2>&1
+ git fetch --tags -f -q
+ popd &>/dev/null
+ done
+ fi
+
+ if $uninstall; then
+ echo "Purging Mod..."
+ purge
+ else
+ echo "Checking Dependencies..."
+ local -r php_version=$(php -v | head -n 1 | awk '{print $2}' | cut -d "." -f 1,2)
+ local -r pkgs=(bc nano sqlite3 jq tar tmux wget "php$php_version-sqlite3")
+ local missingpkgs=()
+
+ for pkg in "${pkgs[@]}"; do
+ ! notInstalled "$pkg" || missingpkgs+=("$pkg")
+ done
+
+ readonly missingpkgs
+ if [[ ${#missingpkgs[@]} -gt 0 ]]; then
+ echo "Installing Missing Dependencies..."
+ if ! $PKG_MANAGER install -y "${missingpkgs[@]}" &>/dev/null; then
+ [[ "$PKG_MANAGER" == *"apt"* ]] || exit 1
+ echo "And Updating Package Cache..."
+ $PKG_MANAGER update -y &>/dev/null
+ $PKG_MANAGER install -y "${missingpkgs[@]}" &>/dev/null
+ fi
+ fi
+
+ if ! $update; then
+ if ! $reinstall; then
+ local -r installed_core_ver=$(getVersion "Pi-hole")
+ local -r installed_admin_ver=$(getVersion "web")
+ if [[ "$installed_core_ver" == *.* && "$installed_admin_ver" == *.* ]]; then
+ echo "Finding Latest Compatible Versions..."
+ local -r remote_core_ver=$(git ls-remote "https://github.com/$MOD_REPO/pi-hole")
+ local -r remote_admin_ver=$(git ls-remote "https://github.com/$MOD_REPO/AdminLTE")
+ mod_core_ver=$(grep -q "$installed_core_ver" <<<"$remote_core_ver" && grep "$installed_core_ver" <<<"$remote_core_ver" | awk '{print $2;}' | cut -d '/' -f 3 | sort -Vr | head -n1 || echo "")
+ mod_admin_ver=$(grep -q "$installed_admin_ver" <<<"$remote_admin_ver" && grep "$installed_admin_ver" <<<"$remote_admin_ver" | awk '{print $2;}' | cut -d '/' -f 3 | sort -Vr | head -n1 || echo "")
+ fi
+ fi
+ elif [[ -d /run/systemd/system ]]; then
+ echo "Updating Pi-hole..."
+ PIHOLE_SKIP_OS_CHECK=true sudo -E pihole -up
+ else
+ echo "Systemd not found. Skipping Pi-hole Update..."
+ fi
+
+ if $backup; then
+ echo "Creating Backup..."
+ download /etc .pihole.mod https://github.com/"$MOD_REPO"/pi-hole "$mod_core_ver" "$CORE_BRANCH" $stable
+ download $HTML_DIR admin.mod https://github.com/"$MOD_REPO"/AdminLTE "$mod_admin_ver" "$ADMIN_BRANCH" $stable
+ fi
+
+ $reinstall && echo "Reinstalling Mod..." || echo "Installing Mod..."
+ download /etc pihole-speedtest https://github.com/"$MOD_REPO"/pihole-speedtest "$st_ver" "$MOD_BRANCH" $stable
+ [[ -f $MOD_DIR/cnf ]] || touch $MOD_DIR/cnf
+ setCnf mod-$MOD_DIR "$(getVersion $MOD_DIR)" $MOD_DIR/cnf $reinstall
+ local stock_tag
+
+ for repo in $CORE_DIR $HTML_DIR/admin; do
+ if [[ -d "$repo" ]]; then
+ stock_tag=$(getVersion "$repo")
+ setCnf org-"$repo" "$stock_tag" $MOD_DIR/cnf
+
+ if $backup; then
+ if [[ ! -d "$repo".bak || "$(getVersion "$repo".bak)" != "$stock_tag" ]]; then
+ rm -rf "$repo".bak
+ mv -f "$repo" "$repo".bak
+ fi
+
+ rm -rf "$repo"
+ mv -f "$repo".mod "$repo"
+ fi
+ fi
+ done
+
+ $backup || download /etc .pihole https://github.com/"$MOD_REPO"/pi-hole "$mod_core_ver" "$CORE_BRANCH" $stable
+ swapScripts
+ \cp -af $CORE_DIR/advanced/Scripts/speedtestmod/. $OPT_DIR/speedtestmod/
+ pihole -a -s
+ $backup || download $HTML_DIR admin https://github.com/"$MOD_REPO"/AdminLTE "$mod_admin_ver" "$ADMIN_BRANCH" $stable
+ setCnf mod-$CORE_DIR "$(getVersion $CORE_DIR)" $MOD_DIR/cnf $reinstall
+ setCnf mod-$HTML_DIR/admin "$(getVersion $HTML_DIR/admin)" $MOD_DIR/cnf $reinstall
+ fi
+
+ pihole updatechecker
+ pihole -v || :
+ popd >/dev/null
+ fi
+
+ exit 0
+}
+
+if [[ $EUID != 0 ]]; then
+ sudo "$0" "$@"
+ exit $?
+fi
+
+rm -f /tmp/pimod.log
+touch /tmp/pimod.log
+main "$@" 2>&1 | tee -a /tmp/pimod.log
+grep -q false "$cleanup" || mv -f /tmp/pimod.log /var/log/pihole/mod.log && rm -f /tmp/pimod.log
+return_status=$(<"$aborted")
+rm -f "$cleanup"
+rm -f "$aborted"
+[[ "$return_status" == "true" ]] && exit 1 || exit 0
diff --git a/advanced/Scripts/speedtestmod/speedtest.sh b/advanced/Scripts/speedtestmod/speedtest.sh
new file mode 100755
index 0000000000..0e3ff08ed7
--- /dev/null
+++ b/advanced/Scripts/speedtestmod/speedtest.sh
@@ -0,0 +1,256 @@
+#!/bin/bash
+#
+# The Test Script, Speedtest Mod for Pi-hole Run Supervisor
+# Please run this with the --help option for usage information
+#
+# shellcheck disable=SC2015
+#
+
+declare -r MOD_REPO="arevindh"
+declare -r CORE_BRANCH="master"
+declare -r OPT_DIR="/opt/pihole"
+declare -r OUT_FILE=/tmp/speedtest.log
+declare -r CREATE_TABLE="create table if not exists speedtest (
+id integer primary key autoincrement,
+start_time text,
+stop_time text,
+from_server text,
+from_ip text,
+server text,
+server_dist real,
+server_ping real,
+download real,
+upload real,
+share_url text
+);"
+declare START
+START=$(date -u --rfc-3339='seconds')
+readonly START
+serverid=$(grep 'SPEEDTEST_SERVER' "/etc/pihole/setupVars.conf" | cut -d '=' -f2)
+run_status=$(mktemp)
+database="/etc/pihole/speedtest.db"
+echo "0" >"$run_status"
+# shellcheck disable=SC1090,SC1091
+[[ -f "$OPT_DIR/speedtestmod/lib.sh" ]] && source "$OPT_DIR/speedtestmod/lib.sh" || source <(curl -sSLN https://github.com/"$MOD_REPO"/pi-hole/raw/"$CORE_BRANCH"/advanced/Scripts/speedtestmod/lib.sh)
+
+#######################################
+# Display the help message
+# Globals:
+# None
+# Arguments:
+# None
+# Outputs:
+# The help message
+#######################################
+help() {
+ local -r help_text=(
+ "The Test Script"
+ "Usage: sudo bash /path/to/speedtest.sh [options]"
+ " or: curl -sSLN //link/to/speedtest.sh | sudo bash [-s -- options]"
+ " or: pihole -a -sn [options]"
+ "Run the speedtest"
+ ""
+ "Options:"
+ " -s, --server= Speedtest server id"
+ " -l, --list List all speedtest servers"
+ " -o, --output= Sqlite3 database (default: /etc/pihole/speedtest.db)"
+ " -a, --attempts= Number of attempts (default: 3)"
+ " -x, --verbose Show the commands being run"
+ " -h, --help Display this help message"
+ ""
+ "Examples:"
+ " pihole -a -sn -a 1"
+ " sudo bash /opt/pihole/speedtestmod/speedtest.sh"
+ " curl -sSL https://github.com/$MOD_REPO/pihole-speedtest/raw/$CORE_BRANCH/test | sudo bash"
+ " curl -sSLN https://github.com/$MOD_REPO/pi-hole/raw/$CORE_BRANCH/advanced/Scripts/speedtestmod/speedtest.sh | sudo bash -s -- --verbose"
+ )
+
+ printf "%s\n" "${help_text[@]}"
+ exit 1
+}
+
+#######################################
+# Run the speedtest
+# Globals:
+# serverid
+# Arguments:
+# None
+# Outputs:
+# The speedtest results
+#######################################
+speedtest() {
+ if /usr/bin/speedtest --version | grep -q "official"; then
+ [[ -n "${serverid}" ]] && /usr/bin/speedtest -s "$serverid" --accept-gdpr --accept-license -f json || /usr/bin/speedtest --accept-gdpr --accept-license -f json
+ else
+ [[ -n "${serverid}" ]] && /usr/bin/speedtest --server "$serverid" --json --share --secure || /usr/bin/speedtest --json --share --secure
+ fi
+}
+
+#######################################
+# Run the speedtest and save the results
+# Globals:
+# PKG_MANAGER
+# START
+# Arguments:
+# $1: Number of attempts (optional, 3 by default)
+# $2: Current attempt (optional, 0 by default)
+# Returns:
+# 1 if the speedtest failed, 0 if successful
+#######################################
+run() {
+ local isp="No Internet"
+ local from_ip="-"
+ local server_name="-"
+ local server_dist=0
+ local server_ping=0
+ local download=0
+ local upload=0
+ local share_url="#"
+ local res
+ local stop
+
+ if [[ "${2:-0}" -gt 0 || ! -f /usr/bin/speedtest ]]; then
+ if notInstalled speedtest && notInstalled speedtest-cli; then
+ [[ ! -f /usr/bin/speedtest ]] || rm -f /usr/bin/speedtest
+ ! ooklaSpeed && ! swivelSpeed && libreSpeed || :
+ elif ! notInstalled speedtest && isAvailable speedtest-cli; then
+ ! swivelSpeed && ! libreSpeed && ooklaSpeed || :
+ else
+ ! libreSpeed && ! ooklaSpeed && swivelSpeed || :
+ fi
+ fi
+
+ if [[ "${1}" -gt "${2:-0}" ]]; then
+ [[ -n "${2:-}" ]] || echo "Running Test..."
+ speedtest | jq . >/tmp/speedtest_results || echo "Attempt ${2:-0} Failed!"
+ stop=$(date -u --rfc-3339='seconds')
+
+ if [[ -s /tmp/speedtest_results ]]; then
+ res=$(/dev/null; then
+ local server_id
+ local servers
+ server_id=$(jq -r '.server.id' <<<"$res")
+ servers="$(curl 'https://www.speedtest.net/api/js/servers' --compressed -H 'Upgrade-Insecure-Requests: 1' -H 'DNT: 1' -H 'Sec-GPC: 1')"
+ server_dist=$(jq --arg id "$server_id" '.[] | select(.id == $id) | .distance' <<<"$servers")
+
+ if /usr/bin/speedtest --version | grep -q "official"; then # ookla
+ server_name=$(jq -r '.server.name' <<<"$res")
+ download=$(jq -r '.download.bandwidth' <<<"$res" | awk '{$1=$1*8/1000/1000; print $1;}' | sed 's/,/./g')
+ upload=$(jq -r '.upload.bandwidth' <<<"$res" | awk '{$1=$1*8/1000/1000; print $1;}' | sed 's/,/./g')
+ isp=$(jq -r '.isp' <<<"$res")
+ from_ip=$(jq -r '.interface.externalIp' <<<"$res")
+ server_ping=$(jq -r '.ping.latency' <<<"$res")
+ share_url=$(jq -r '.result.url' <<<"$res")
+ [[ -n "$server_dist" ]] || server_dist="-1"
+ else # speedtest-cli
+ server_name=$(jq -r '.server.sponsor' <<<"$res")
+ download=$(jq -r '.download' <<<"$res" | awk '{$1=$1/1000/1000; print $1;}' | sed 's/,/./g')
+ upload=$(jq -r '.upload' <<<"$res" | awk '{$1=$1/1000/1000; print $1;}' | sed 's/,/./g')
+ isp=$(jq -r '.client.isp' <<<"$res")
+ from_ip=$(jq -r '.client.ip' <<<"$res")
+ server_ping=$(jq -r '.ping' <<<"$res")
+ share_url=$(jq -r '.share' <<<"$res")
+ [[ -n "$server_dist" ]] || server_dist=$(jq -r '.server.d' <<<"$res")
+ fi
+ else # if jq -e '.[].server' /tmp/speedtest_results &>/dev/null; then # librespeed
+ server_name=$(jq -r '.[].server.name' <<<"$res")
+ download=$(jq -r '.[].download' <<<"$res")
+ upload=$(jq -r '.[].upload' <<<"$res")
+ isp="Unknown"
+ from_ip=$(curl -sSL https://ipv4.icanhazip.com)
+ server_ping=$(jq -r '.[].ping' <<<"$res")
+ share_url=$(jq -r '.[].share' <<<"$res")
+ server_dist="-1"
+ fi
+ else
+ run $1 $((${2:-0} + 1))
+ fi
+ else
+ echo "Timeout!"
+ fi
+
+ local -r rm_empty="
+ def walk(f): . as \$in | if type == \"object\" then reduce keys_unsorted[] as \$key ({}; . + { (\$key): (\$in[\$key] | walk(f)) }) | f else if type == \"array\" then map( walk(f) ) | f else f end;
+ def nonempty: . and length > 0 and (type != \"object\" or . != {}) and (type != \"array\" or any(.[]; . != \"\"));
+ if type == \"array\" then map(walk(if type == \"object\" then with_entries(select(.value | nonempty)) else . end)) else walk(if type == \"object\" then with_entries(select(.value | nonempty)) else . end) end
+"
+ local -r temp_file=$(mktemp)
+ local -r json_file="/tmp/speedtest_results"
+ jq "$rm_empty" "$json_file" >"$temp_file" && mv -f "$temp_file" "$json_file"
+ rm -f "$temp_file"
+ chmod 644 /tmp/speedtest_results
+
+ if [[ -f /usr/local/bin/pihole ]]; then
+ mv -f /tmp/speedtest_results /var/log/pihole/speedtest.log
+ \cp -af /var/log/pihole/speedtest.log /etc/pihole/speedtest.log
+ fi
+
+ sqlite3 "$database" "$CREATE_TABLE"
+ sqlite3 "$database" "insert into speedtest values (NULL, '${START}', '${stop}', '${isp}', '${from_ip}', '${server_name}', ${server_dist}, ${server_ping}, ${download}, ${upload}, '${share_url}');"
+ [[ "$isp" == "No Internet" ]] && return 1 || return 0
+}
+
+#######################################
+# Start the runner
+# Globals:
+# PKG_MANAGER
+# Arguments:
+# None
+# Outputs:
+# The speedtest status
+#######################################
+main() {
+ local -r short_opts=-s:lo:a:xh
+ local -r long_opts=server:,list,output:,attempts:,verbose,help
+ local -r parsed_opts=$(getopt --options ${short_opts} --longoptions ${long_opts} --name "$0" -- "$@")
+ local POSITIONAL=()
+ local attempts="3"
+ local verbose=false
+ eval set -- "${parsed_opts}"
+
+ while [[ $# -gt 0 ]]; do
+ case "$1" in
+ -s | --server)
+ serverid="$2"
+ shift
+ ;;
+ -l | --list)
+ /usr/bin/speedtest --version | grep -q official && sudo /usr/bin/speedtest -L || /usr/bin/speedtest --secure --list 2>&1
+ exit 0
+ ;;
+ -o | --output)
+ database="$2"
+ shift
+ ;;
+ -a | --attempts)
+ attempts="$2"
+ shift
+ ;;
+ -x | --verbose) verbose=true ;;
+ -h | --help) help ;;
+ *) POSITIONAL+=("$1") ;;
+ esac
+ shift
+ done
+
+ set -- "${POSITIONAL[@]}"
+ [[ "$attempts" =~ ^[0-9]+$ ]] || attempts="3"
+ ! $verbose || set -x
+ run $attempts
+ echo "$?" >"$run_status"
+}
+
+if [[ $EUID != 0 ]]; then
+ sudo "$0" "$@"
+ exit $?
+fi
+
+rm -f "$OUT_FILE"
+touch "$OUT_FILE"
+main "$@" 2>&1 | tee -a "$OUT_FILE"
+mv -f "$OUT_FILE" /var/log/pihole/speedtest-run.log || rm -f "$OUT_FILE"
+exit_code=$(<"$run_status")
+rm -f "$run_status"
+[[ "$exit_code" -eq 1 ]] && exit 1 || exit 0
diff --git a/advanced/Scripts/update.sh b/advanced/Scripts/update.sh
index ce1478ab0b..8c6f59c349 100755
--- a/advanced/Scripts/update.sh
+++ b/advanced/Scripts/update.sh
@@ -17,7 +17,7 @@ readonly PI_HOLE_GIT_URL="https://github.com/arevindh/pi-hole.git"
readonly PI_HOLE_FILES_DIR="/etc/.pihole"
# shellcheck disable=SC2034
-PH_TEST=true
+SKIP_INSTALL=true
# when --check-only is passed to this script, it will not perform the actual update
CHECK_ONLY=false
@@ -216,9 +216,8 @@ main() {
fi
if [[ "${FTL_update}" == true || "${core_update}" == true || "${web_update}" == true ]]; then
- # Force an update of the updatechecker
+ # Update local and remote versions via updatechecker
/opt/pihole/updatecheck.sh
- /opt/pihole/updatecheck.sh x remote
echo -e " ${INFO} Local version file information updated."
fi
diff --git a/advanced/Scripts/updatecheck.sh b/advanced/Scripts/updatecheck.sh
index afb03ebb7f..e71d73339b 100755
--- a/advanced/Scripts/updatecheck.sh
+++ b/advanced/Scripts/updatecheck.sh
@@ -8,87 +8,169 @@
# 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
-}
-
function get_local_branch() {
# Return active branch
cd "${1}" 2> /dev/null || return 1
- git rev-parse --abbrev-ref HEAD || return 1
+ local foundBranch
+ foundBranch=$(git status --porcelain=2 -b | grep branch.head | awk '{print $3;}')
+ [[ $foundBranch != *"("* ]] || foundBranch=$(git show-ref --heads | grep "$(git rev-parse HEAD)" | awk '{print $2;}' | cut -d '/' -f 3)
+ echo "${foundBranch:-HEAD}"
}
function get_local_version() {
- # Return active branch
+ # Return active version
cd "${1}" 2> /dev/null || return 1
- git describe --long --dirty --tags 2> /dev/null || return 1
+ local tags
+ local foundVersion
+ tags=$(git ls-remote -t origin || git show-ref --tags)
+ foundVersion=$(git status --porcelain=2 -b | grep branch.oid | awk '{print $3;}')
+ [[ $foundVersion != *"("* ]] || foundVersion=$(git rev-parse HEAD 2>/dev/null)
+ local foundTag=$foundVersion
+ # shellcheck disable=SC2015
+ grep -q "^$foundVersion" <<<"$tags" && foundTag=$(grep "^$foundVersion.*/v[0-9].*$" <<<"$tags" | awk '{print $2;}' | cut -d '/' -f 3 | sort -V | tail -n1) || :
+ [[ -z "${foundTag}" ]] || foundVersion="${foundTag}"
+ echo "${foundVersion}"
+}
+
+function get_local_hash() {
+ cd "${1}" 2> /dev/null || return 1
+ foundHash=$(git status --porcelain=2 -b | grep branch.oid | awk '{print $3;}')
+ [[ $foundHash != *"("* ]] || foundHash=$(git rev-parse HEAD 2>/dev/null)
+ echo "${foundHash}"
+}
+
+function get_remote_version() {
+ if [[ "${1}" == "docker-pi-hole" || "${1}" == "FTL" ]]; then
+ curl -s "https://api.github.com/repos/pi-hole/${1}/releases/latest" 2> /dev/null | jq --raw-output .tag_name || return 1
+ else
+ curl -s "https://api.github.com/repos/arevindh/${1}/releases/latest" 2> /dev/null | jq --raw-output .tag_name || { curl -s "https://api.github.com/repos/pi-hole/${1}/releases/latest" 2> /dev/null | jq --raw-output .tag_name || return 1; }
+ fi
+}
+
+function get_remote_hash(){
+ local foundHash=""
+
+ for repo in "arevindh" "pi-hole" "ipitio"; do
+ [[ "${repo}" != "pi-hole" || "${1}" != "pihole-speedtest" ]] || continue
+ foundHash=$(git ls-remote "https://github.com/${repo}/${1}" --tags "${2}" | awk '{print $1;}' 2> /dev/null)
+ [[ -z "${foundHash}" ]] || break
+ done
+
+ [[ -n "${foundHash}" ]] && echo "${foundHash}" || return 1
}
# Source the setupvars config file
# shellcheck disable=SC1091
. /etc/pihole/setupVars.conf
-if [[ "$2" == "remote" ]]; then
+# Source the utils file for addOrEditKeyValPair()
+# shellcheck disable=SC1091
+. /opt/pihole/utils.sh
+
+# Remove the below three legacy files if they exist
+rm -f "/etc/pihole/GitHubVersions"
+rm -f "/etc/pihole/localbranches"
+rm -f "/etc/pihole/localversions"
+
+# Create new versions file if it does not exist
+VERSION_FILE="/etc/pihole/versions"
+touch "${VERSION_FILE}"
+chmod 644 "${VERSION_FILE}"
+
+# if /pihole.docker.tag file exists, we will use it's value later in this script
+DOCKER_TAG=$(cat /pihole.docker.tag 2>/dev/null)
+regex='^([0-9]+\.){1,2}(\*|[0-9]+)(-.*)?$|(^nightly$)|(^dev.*$)'
+if [[ ! "${DOCKER_TAG}" =~ $regex ]]; then
+ # DOCKER_TAG does not match the pattern (see https://regex101.com/r/RsENuz/1), so unset it.
+ unset DOCKER_TAG
+fi
- if [[ "$3" == "reboot" ]]; then
+# used in cronjob
+if [[ "$1" == "reboot" ]]; then
sleep 30
- fi
+fi
- GITHUB_VERSION_FILE="/etc/pihole/GitHubVersions"
- GITHUB_CORE_VERSION="$(json_extract tag_name "$(curl -s 'https://api.github.com/repos/pi-hole/pi-hole/releases/latest' 2> /dev/null)")"
- echo -n "${GITHUB_CORE_VERSION}" > "${GITHUB_VERSION_FILE}"
- chmod 644 "${GITHUB_VERSION_FILE}"
+# get Core versions
- if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
- GITHUB_WEB_VERSION="$(json_extract tag_name "$(curl -s 'https://api.github.com/repos/pi-hole/AdminLTE/releases/latest' 2> /dev/null)")"
- echo -n " ${GITHUB_WEB_VERSION}" >> "${GITHUB_VERSION_FILE}"
- fi
+CORE_VERSION="$(get_local_version /etc/.pihole)"
+addOrEditKeyValPair "${VERSION_FILE}" "CORE_VERSION" "${CORE_VERSION}"
- GITHUB_FTL_VERSION="$(json_extract tag_name "$(curl -s 'https://api.github.com/repos/pi-hole/FTL/releases/latest' 2> /dev/null)")"
- echo -n " ${GITHUB_FTL_VERSION}" >> "${GITHUB_VERSION_FILE}"
+CORE_BRANCH="$(get_local_branch /etc/.pihole)"
+addOrEditKeyValPair "${VERSION_FILE}" "CORE_BRANCH" "${CORE_BRANCH}"
-else
+CORE_HASH="$(get_local_hash /etc/.pihole)"
+addOrEditKeyValPair "${VERSION_FILE}" "CORE_HASH" "${CORE_HASH}"
- LOCAL_BRANCH_FILE="/etc/pihole/localbranches"
+GITHUB_CORE_VERSION="$(get_remote_version pi-hole)"
+addOrEditKeyValPair "${VERSION_FILE}" "GITHUB_CORE_VERSION" "${GITHUB_CORE_VERSION}"
- CORE_BRANCH="$(get_local_branch /etc/.pihole)"
- echo -n "${CORE_BRANCH}" > "${LOCAL_BRANCH_FILE}"
- chmod 644 "${LOCAL_BRANCH_FILE}"
+GITHUB_CORE_HASH="$(get_remote_hash pi-hole "${CORE_BRANCH}")"
+addOrEditKeyValPair "${VERSION_FILE}" "GITHUB_CORE_HASH" "${GITHUB_CORE_HASH}"
- if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
- WEB_BRANCH="$(get_local_branch /var/www/html/admin)"
- echo -n " ${WEB_BRANCH}" >> "${LOCAL_BRANCH_FILE}"
- fi
- FTL_BRANCH="$(pihole-FTL branch)"
- echo -n " ${FTL_BRANCH}" >> "${LOCAL_BRANCH_FILE}"
+# get Web versions
- LOCAL_VERSION_FILE="/etc/pihole/localversions"
+if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
- CORE_VERSION="$(get_local_version /etc/.pihole)"
- echo -n "${CORE_VERSION}" > "${LOCAL_VERSION_FILE}"
- chmod 644 "${LOCAL_VERSION_FILE}"
+ WEB_VERSION="$(get_local_version /var/www/html/admin)"
+ addOrEditKeyValPair "${VERSION_FILE}" "WEB_VERSION" "${WEB_VERSION}"
- if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
- WEB_VERSION="$(get_local_version /var/www/html/admin)"
- echo -n " ${WEB_VERSION}" >> "${LOCAL_VERSION_FILE}"
- fi
+ WEB_BRANCH="$(get_local_branch /var/www/html/admin)"
+ addOrEditKeyValPair "${VERSION_FILE}" "WEB_BRANCH" "${WEB_BRANCH}"
+
+ WEB_HASH="$(get_local_hash /var/www/html/admin)"
+ addOrEditKeyValPair "${VERSION_FILE}" "WEB_HASH" "${WEB_HASH}"
+
+ GITHUB_WEB_VERSION="$(get_remote_version AdminLTE)"
+ addOrEditKeyValPair "${VERSION_FILE}" "GITHUB_WEB_VERSION" "${GITHUB_WEB_VERSION}"
- FTL_VERSION="$(pihole-FTL version)"
- echo -n " ${FTL_VERSION}" >> "${LOCAL_VERSION_FILE}"
+ GITHUB_WEB_HASH="$(get_remote_hash AdminLTE "${WEB_BRANCH}")"
+ addOrEditKeyValPair "${VERSION_FILE}" "GITHUB_WEB_HASH" "${GITHUB_WEB_HASH}"
fi
+
+# get FTL versions
+
+FTL_VERSION="$(pihole-FTL version)"
+addOrEditKeyValPair "${VERSION_FILE}" "FTL_VERSION" "${FTL_VERSION}"
+
+FTL_BRANCH="$(pihole-FTL branch)"
+addOrEditKeyValPair "${VERSION_FILE}" "FTL_BRANCH" "${FTL_BRANCH}"
+
+FTL_HASH="$(pihole-FTL --hash)"
+addOrEditKeyValPair "${VERSION_FILE}" "FTL_HASH" "${FTL_HASH}"
+
+GITHUB_FTL_VERSION="$(get_remote_version FTL)"
+addOrEditKeyValPair "${VERSION_FILE}" "GITHUB_FTL_VERSION" "${GITHUB_FTL_VERSION}"
+
+GITHUB_FTL_HASH="$(get_remote_hash FTL "${FTL_BRANCH}")"
+addOrEditKeyValPair "${VERSION_FILE}" "GITHUB_FTL_HASH" "${GITHUB_FTL_HASH}"
+
+
+# get Docker versions
+
+if [[ "${DOCKER_TAG}" ]]; then
+ addOrEditKeyValPair "${VERSION_FILE}" "DOCKER_VERSION" "${DOCKER_TAG}"
+
+ GITHUB_DOCKER_VERSION="$(get_remote_version docker-pi-hole)"
+ addOrEditKeyValPair "${VERSION_FILE}" "GITHUB_DOCKER_VERSION" "${GITHUB_DOCKER_VERSION}"
+fi
+
+
+# get Speedtest versions
+
+SPEEDTEST_VERSION="$(get_local_version /etc/pihole-speedtest)"
+addOrEditKeyValPair "${VERSION_FILE}" "SPEEDTEST_VERSION" "${SPEEDTEST_VERSION}"
+
+SPEEDTEST_BRANCH="$(get_local_branch /etc/pihole-speedtest)"
+addOrEditKeyValPair "${VERSION_FILE}" "SPEEDTEST_BRANCH" "${SPEEDTEST_BRANCH}"
+
+SPEEDTEST_HASH="$(get_local_hash /etc/pihole-speedtest)"
+addOrEditKeyValPair "${VERSION_FILE}" "SPEEDTEST_HASH" "${SPEEDTEST_HASH}"
+
+GITHUB_SPEEDTEST_VERSION="$(get_remote_version pihole-speedtest)"
+addOrEditKeyValPair "${VERSION_FILE}" "GITHUB_SPEEDTEST_VERSION" "${GITHUB_SPEEDTEST_VERSION}"
+
+GITHUB_SPEEDTEST_HASH="$(get_remote_hash pihole-speedtest "${SPEEDTEST_BRANCH}")"
+addOrEditKeyValPair "${VERSION_FILE}" "GITHUB_SPEEDTEST_HASH" "${GITHUB_SPEEDTEST_HASH}"
diff --git a/advanced/Scripts/utils.sh b/advanced/Scripts/utils.sh
index f0a7cc3739..f655e56ce0 100755
--- a/advanced/Scripts/utils.sh
+++ b/advanced/Scripts/utils.sh
@@ -12,7 +12,7 @@
# Basic Housekeeping rules
# - Functions must be self contained
-# - Functions must be added in alphabetical order
+# - Functions should be grouped with other similar functions
# - Functions must be documented
# - New functions must have a test added for them in test/test_any_utils.py
@@ -31,9 +31,12 @@ addOrEditKeyValPair() {
local key="${2}"
local value="${3}"
+ # touch file to prevent grep error if file does not exist yet
+ touch "${file}"
+
if grep -q "^${key}=" "${file}"; then
- # Key already exists in file, modify the value
- sed -i "/^${key}=/c\\${key}=${value}" "${file}"
+ # Key already exists in file, modify the value
+ sed -i "/^${key}=/c\\${key}=${value}" "${file}"
else
# Key does not already exist, add it and it's value
echo "${key}=${value}" >> "${file}"
@@ -51,9 +54,16 @@ addKey(){
local file="${1}"
local key="${2}"
- if ! grep -q "^${key}" "${file}"; then
- # Key does not exist, add it.
- echo "${key}" >> "${file}"
+ # touch file to prevent grep error if file does not exist yet
+ touch "${file}"
+
+ # Match key against entire line, using both anchors. We assume
+ # that the file's keys never have bounding whitespace. Anchors
+ # are necessary to ensure the key is considered absent when it
+ # is a substring of another key present in the file.
+ if ! grep -q "^${key}$" "${file}"; then
+ # Key does not exist, add it.
+ echo "${key}" >> "${file}"
fi
}
@@ -70,29 +80,68 @@ removeKey() {
sed -i "/^${key}/d" "${file}"
}
+
#######################
-# returns FTL's current telnet API port
-#######################
+# returns FTL's current telnet API port based on the setting in /etc/pihole-FTL.conf
+########################
getFTLAPIPort(){
+ local FTLCONFFILE="/etc/pihole/pihole-FTL.conf"
+ local DEFAULT_FTL_PORT=4711
+ local ftl_api_port
+
+ if [ -s "$FTLCONFFILE" ]; then
+ # if FTLPORT is not set in pihole-FTL.conf, use the default port
+ ftl_api_port="$({ grep '^FTLPORT=' "${FTLCONFFILE}" || echo "${DEFAULT_FTL_PORT}"; } | cut -d'=' -f2-)"
+ # Exploit prevention: set the port to the default port if there is malicious (non-numeric)
+ # content set in pihole-FTL.conf
+ expr "${ftl_api_port}" : "[^[:digit:]]" > /dev/null && ftl_api_port="${DEFAULT_FTL_PORT}"
+ else
+ # if there is no pihole-FTL.conf, use the default port
+ ftl_api_port="${DEFAULT_FTL_PORT}"
+ fi
+
+ echo "${ftl_api_port}"
+}
+
+#######################
+# returns path of FTL's PID file
+#######################
+getFTLPIDFile() {
local FTLCONFFILE="/etc/pihole/pihole-FTL.conf"
- local DEFAULT_PORT_FILE="/run/pihole-FTL.port"
- local DEFAULT_FTL_PORT=4711
- local PORTFILE
- local ftl_api_port
-
- if [ -f "$FTLCONFFILE" ]; then
- # if PORTFILE is not set in pihole-FTL.conf, use the default path
- PORTFILE="$( (grep "^PORTFILE=" $FTLCONFFILE || echo "$DEFAULT_PORT_FILE") | cut -d"=" -f2-)"
- fi
+ local DEFAULT_PID_FILE="/run/pihole-FTL.pid"
+ local FTL_PID_FILE
- if [ -s "$PORTFILE" ]; then
- # -s: FILE exists and has a size greater than zero
- ftl_api_port=$(cat "${PORTFILE}")
- # Exploit prevention: unset the variable if there is malicious content
- # Verify that the value read from the file is numeric
- expr "$ftl_api_port" : "[^[:digit:]]" > /dev/null && unset ftl_api_port
+ if [ -s "${FTLCONFFILE}" ]; then
+ # if PIDFILE is not set in pihole-FTL.conf, use the default path
+ FTL_PID_FILE="$({ grep '^PIDFILE=' "${FTLCONFFILE}" || echo "${DEFAULT_PID_FILE}"; } | cut -d'=' -f2-)"
+ else
+ # if there is no pihole-FTL.conf, use the default path
+ FTL_PID_FILE="${DEFAULT_PID_FILE}"
fi
- # echo the port found in the portfile or default to the default port
- echo "${ftl_api_port:=$DEFAULT_FTL_PORT}"
+ echo "${FTL_PID_FILE}"
+}
+
+#######################
+# returns FTL's PID based on the content of the pihole-FTL.pid file
+#
+# Takes one argument: path to pihole-FTL.pid
+# Example getFTLPID "/run/pihole-FTL.pid"
+#######################
+getFTLPID() {
+ local FTL_PID_FILE="${1}"
+ local FTL_PID
+
+ if [ -s "${FTL_PID_FILE}" ]; then
+ # -s: FILE exists and has a size greater than zero
+ FTL_PID="$(cat "${FTL_PID_FILE}")"
+ # Exploit prevention: unset the variable if there is malicious content
+ # Verify that the value read from the file is numeric
+ expr "${FTL_PID}" : "[^[:digit:]]" > /dev/null && unset FTL_PID
+ fi
+
+ # If FTL is not running, or the PID file contains malicious stuff, substitute
+ # negative PID to signal this
+ FTL_PID=${FTL_PID:=-1}
+ echo "${FTL_PID}"
}
diff --git a/advanced/Scripts/version.sh b/advanced/Scripts/version.sh
index 14d41e9242..cc76ba3155 100755
--- a/advanced/Scripts/version.sh
+++ b/advanced/Scripts/version.sh
@@ -1,4 +1,4 @@
-#!/usr/bin/env bash
+#!/usr/bin/env sh
# 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.
@@ -8,183 +8,109 @@
# This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license.
-# Variables
-DEFAULT="-1"
-COREGITDIR="/etc/.pihole/"
-WEBGITDIR="/var/www/html/admin/"
-
# Source the setupvars config file
# shellcheck disable=SC1091
-source /etc/pihole/setupVars.conf
+. /etc/pihole/setupVars.conf
-getLocalVersion() {
- # FTL requires a different method
- if [[ "$1" == "FTL" ]]; then
- pihole-FTL version
- return 0
- fi
+# Source the versions file poupulated by updatechecker.sh
+cachedVersions="/etc/pihole/versions"
- # Get the tagged version of the local repository
- local directory="${1}"
- local version
+if [ -f ${cachedVersions} ]; then
+ # shellcheck disable=SC1090
+ . "$cachedVersions"
+else
+ echo "Could not find /etc/pihole/versions. Running update now."
+ pihole updatechecker
+ # shellcheck disable=SC1090
+ . "$cachedVersions"
+fi
- cd "${directory}" 2> /dev/null || { echo "${DEFAULT}"; return 1; }
- version=$(git describe --tags --always || echo "$DEFAULT")
- if [[ "${version}" =~ ^v ]]; then
- echo "${version}"
- elif [[ "${version}" == "${DEFAULT}" ]]; then
- echo "ERROR"
- return 1
- else
- echo "Untagged"
- fi
- return 0
+getLocalVersion() {
+ case ${1} in
+ "Pi-hole" ) echo "${CORE_VERSION:=N/A}";;
+ "web" ) [ "${INSTALL_WEB_INTERFACE}" = true ] && echo "${WEB_VERSION:=N/A}";;
+ "FTL" ) echo "${FTL_VERSION:=N/A}";;
+ "Speedtest" ) echo "${SPEEDTEST_VERSION:=N/A}";;
+ esac
}
getLocalHash() {
- # Local FTL hash does not exist on filesystem
- if [[ "$1" == "FTL" ]]; then
- echo "N/A"
- return 0
- fi
-
- # Get the short hash of the local repository
- local directory="${1}"
- local hash
-
- cd "${directory}" 2> /dev/null || { echo "${DEFAULT}"; return 1; }
- hash=$(git rev-parse --short HEAD || echo "$DEFAULT")
- if [[ "${hash}" == "${DEFAULT}" ]]; then
- echo "ERROR"
- return 1
- else
- echo "${hash}"
- fi
- return 0
+ case ${1} in
+ "Pi-hole" ) echo "${CORE_HASH:=N/A}";;
+ "web" ) [ "${INSTALL_WEB_INTERFACE}" = true ] && echo "${WEB_HASH:=N/A}";;
+ "FTL" ) echo "${FTL_HASH:=N/A}";;
+ "Speedtest" ) echo "${SPEEDTEST_HASH:=N/A}";;
+ esac
}
getRemoteHash(){
- # Remote FTL hash is not applicable
- if [[ "$1" == "FTL" ]]; then
- echo "N/A"
- return 0
- fi
-
- local daemon="${1}"
- local branch="${2}"
-
- hash=$(git ls-remote --heads "https://github.com/pi-hole/${daemon}" | \
- awk -v bra="$branch" '$0~bra {print substr($0,0,8);exit}')
- if [[ -n "$hash" ]]; then
- echo "$hash"
- else
- echo "ERROR"
- return 1
- fi
- return 0
+ case ${1} in
+ "Pi-hole" ) echo "${GITHUB_CORE_HASH:=N/A}";;
+ "web" ) [ "${INSTALL_WEB_INTERFACE}" = true ] && echo "${GITHUB_WEB_HASH:=N/A}";;
+ "FTL" ) echo "${GITHUB_FTL_HASH:=N/A}";;
+ "Speedtest" ) echo "${GITHUB_SPEEDTEST_HASH:=N/A}";;
+ esac
}
getRemoteVersion(){
- # Get the version from the remote origin
- local daemon="${1}"
- local version
- local cachedVersions
- local arrCache
- local owner="pi-hole"
- 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" ) [[ "${INSTALL_WEB_INTERFACE}" == true ]] && echo "${arrCache[1]}";;
- "FTL" ) [[ "${INSTALL_WEB_INTERFACE}" == true ]] && echo "${arrCache[2]}" || echo "${arrCache[1]}";;
- esac
-
- return 0
- fi
-
- if [[ "$daemon" == "AdminLTE" ]]; then
- owner="arevindh"
- fi
-
- version=$(curl --silent --fail "https://api.github.com/repos/${owner}/${daemon}/releases/latest" | \
- awk -F: '$1 ~/tag_name/ { print $2 }' | \
- tr -cd '[[:alnum:]]._-')
- if [[ "${version}" =~ ^v ]]; then
- echo "${version}"
- else
- echo "ERROR"
- return 1
- fi
- return 0
+ case ${1} in
+ "Pi-hole" ) echo "${GITHUB_CORE_VERSION:=N/A}";;
+ "web" ) [ "${INSTALL_WEB_INTERFACE}" = true ] && echo "${GITHUB_WEB_VERSION:=N/A}";;
+ "FTL" ) echo "${GITHUB_FTL_VERSION:=N/A}";;
+ "Speedtest" ) echo "${GITHUB_SPEEDTEST_VERSION:=N/A}";;
+ esac
}
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
+ case ${1} in
+ "Pi-hole" ) echo "${CORE_BRANCH:=N/A}";;
+ "web" ) [ "${INSTALL_WEB_INTERFACE}" = true ] && echo "${WEB_BRANCH:=N/A}";;
+ "FTL" ) echo "${FTL_BRANCH:=N/A}";;
+ "Speedtest" ) echo "${SPEEDTEST_BRANCH:=N/A}";;
+ esac
}
versionOutput() {
- if [[ "$1" == "AdminLTE" && "${INSTALL_WEB_INTERFACE}" != true ]]; then
+ if [ "$1" = "web" ] && [ "${INSTALL_WEB_INTERFACE}" != true ]; then
echo " WebAdmin not installed"
return 1
fi
- [[ "$1" == "pi-hole" ]] && GITDIR=$COREGITDIR
- [[ "$1" == "AdminLTE" ]] && GITDIR=$WEBGITDIR
- [[ "$1" == "FTL" ]] && GITDIR="FTL"
+ [ "$2" = "-c" ] || [ "$2" = "--current" ] || [ -z "$2" ] && current=$(getLocalVersion "${1}") && branch=$(getLocalBranch "${1}")
+ [ "$2" = "-l" ] || [ "$2" = "--latest" ] || [ -z "$2" ] && latest=$(getRemoteVersion "${1}")
+ if [ "$2" = "--hash" ]; then
+ [ "$3" = "-c" ] || [ "$3" = "--current" ] || [ -z "$3" ] && curHash=$(getLocalHash "${1}") && branch=$(getLocalBranch "${1}")
+ [ "$3" = "-l" ] || [ "$3" = "--latest" ] || [ -z "$3" ] && latHash=$(getRemoteHash "${1}") && branch=$(getLocalBranch "${1}")
+ fi
- [[ "$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") && branch=$(getLocalBranch $GITDIR)
- [[ "$3" == "-l" ]] || [[ "$3" == "--latest" ]] || [[ -z "$3" ]] && latHash=$(getRemoteHash "$1" "$(cd "$GITDIR" 2> /dev/null && git rev-parse --abbrev-ref HEAD)")
+ # We do not want to show the branch name when we are on master,
+ # blank out the variable in this case
+ if [ "$branch" = "master" ]; then
+ branch=""
+ else
+ branch="$branch "
fi
- if [[ -n "$current" ]] && [[ -n "$latest" ]]; then
- output="${1^} version is $branch$current (Latest: $latest)"
- elif [[ -n "$current" ]] && [[ -z "$latest" ]]; then
- 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
- output="${1^} hash is not applicable"
- elif [[ -n "$curHash" ]] && [[ -n "$latHash" ]]; then
- output="${1^} hash is $curHash (Latest: $latHash)"
- elif [[ -n "$curHash" ]] && [[ -z "$latHash" ]]; then
- output="Current ${1^} hash is $curHash"
- elif [[ -z "$curHash" ]] && [[ -n "$latHash" ]]; then
- output="Latest ${1^} hash is $latHash"
+
+ if [ -n "$current" ] && [ -n "$latest" ]; then
+ output="${1} version is $branch$current (Latest: $latest)"
+ elif [ -n "$current" ] && [ -z "$latest" ]; then
+ output="Current ${1} version is $branch$current"
+ elif [ -z "$current" ] && [ -n "$latest" ]; then
+ output="Latest ${1} version is $latest"
+ elif [ -n "$curHash" ] && [ -n "$latHash" ]; then
+ output="Local ${1} hash is $curHash (Remote: $latHash)"
+ elif [ -n "$curHash" ] && [ -z "$latHash" ]; then
+ output="Current local ${1} hash is $curHash"
+ elif [ -z "$curHash" ] && [ -n "$latHash" ]; then
+ output="Latest remote ${1} hash is $latHash"
+ elif [ -z "$curHash" ] && [ -z "$latHash" ]; then
+ output="Hashes for ${1} not available"
else
errorOutput
return 1
fi
- [[ -n "$output" ]] && echo " $output"
+ [ -n "$output" ] && echo " $output"
}
errorOutput() {
@@ -193,13 +119,14 @@ errorOutput() {
}
defaultOutput() {
- versionOutput "pi-hole" "$@"
+ versionOutput "Pi-hole" "$@"
- if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
- versionOutput "AdminLTE" "$@"
+ if [ "${INSTALL_WEB_INTERFACE}" = true ]; then
+ versionOutput "web" "$@"
fi
versionOutput "FTL" "$@"
+ versionOutput "Speedtest" "$@"
}
helpFunc() {
@@ -209,8 +136,9 @@ Show Pi-hole, Admin Console & FTL versions
Repositories:
-p, --pihole Only retrieve info regarding Pi-hole repository
- -a, --admin Only retrieve info regarding AdminLTE repository
+ -a, --admin Only retrieve info regarding web repository
-f, --ftl Only retrieve info regarding FTL repository
+ -s, --speedtest Only retrieve info regarding Speedtest repository
Options:
-c, --current Return the current version
@@ -221,9 +149,10 @@ Options:
}
case "${1}" in
- "-p" | "--pihole" ) shift; versionOutput "pi-hole" "$@";;
- "-a" | "--admin" ) shift; versionOutput "AdminLTE" "$@";;
+ "-p" | "--pihole" ) shift; versionOutput "Pi-hole" "$@";;
+ "-a" | "--admin" ) shift; versionOutput "web" "$@";;
"-f" | "--ftl" ) shift; versionOutput "FTL" "$@";;
+ "-s" | "--speedtest" ) shift; versionOutput "Speedtest" "$@";;
"-h" | "--help" ) helpFunc;;
* ) defaultOutput "$@";;
esac
diff --git a/advanced/Scripts/webpage.sh b/advanced/Scripts/webpage.sh
index a032da720a..aa4c9c6373 100755
--- a/advanced/Scripts/webpage.sh
+++ b/advanced/Scripts/webpage.sh
@@ -19,17 +19,19 @@ readonly FTLconf="/etc/pihole/pihole-FTL.conf"
readonly dhcpstaticconfig="/etc/dnsmasq.d/04-pihole-static-dhcp.conf"
readonly dnscustomfile="/etc/pihole/custom.list"
readonly dnscustomcnamefile="/etc/dnsmasq.d/05-pihole-custom-cname.conf"
-readonly speedtestfile="/var/www/html/admin/scripts/pi-hole/speedtest/speedtest.sh"
-readonly speedtestdb="/etc/pihole/speedtest.db"
-
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"
+# speedtest mod
+readonly speedtestmod="/opt/pihole/speedtestmod/mod.sh"
+readonly speedtestfile="/opt/pihole/speedtestmod/speedtest.sh"
+
+readonly setupVars="/etc/pihole/setupVars.conf"
+readonly PI_HOLE_BIN_DIR="/usr/local/bin"
+# Root of the web server
+readonly webroot="/var/www/html"
+
+# Source utils script
utilsfile="/opt/pihole/utils.sh"
source "${utilsfile}"
@@ -48,15 +50,14 @@ 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
- -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
- -s, speedtest Set speedtest intevel , user 0 to disable Speedtests use -sn to prevent logging to results list
+ -s, speedtest Set speedtest interval, add 0 to disable
-sd Set speedtest display range
- -sn Run speedtest now
- -sm Speedtest Mode
- -sc Clear speedtest data
+ -sm [options] Run the Mod Script in the background (tmux a -t pimod)
+ -sn [options] Run the Test Script in the background (tmux a -t pimodtest)
-ss Set custom server
+ -st Set default speedtest chart type (line, bar)
-l, privacylevel Set privacy level (0 = lowest, 3 = highest)
-t, teleporter Backup configuration as an archive
-t, teleporter myname.tar.gz Backup configuration to archive with name myname.tar.gz as specified"
@@ -107,6 +108,47 @@ HashPassword() {
echo "${return}"
}
+# Check an IP address to see if it is a valid one
+valid_ip() {
+ # Local, named variables
+ local ip=${1}
+ local stat=1
+
+ # Regex matching one IPv4 component, i.e. an integer from 0 to 255.
+ # See https://tools.ietf.org/html/rfc1340
+ local ipv4elem="(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)";
+ # Regex matching an optional port (starting with '#') range of 1-65536
+ local portelem="(#(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{0,3}|0))?";
+ # Build a full IPv4 regex from the above subexpressions
+ local regex="^${ipv4elem}\\.${ipv4elem}\\.${ipv4elem}\\.${ipv4elem}${portelem}$"
+
+ # Evaluate the regex, and return the result
+ [[ $ip =~ ${regex} ]]
+
+ stat=$?
+ return "${stat}"
+}
+
+valid_ip6() {
+ local ip=${1}
+ local stat=1
+
+ # Regex matching one IPv6 element, i.e. a hex value from 0000 to FFFF
+ local ipv6elem="[0-9a-fA-F]{1,4}"
+ # Regex matching an IPv6 CIDR, i.e. 1 to 128
+ local v6cidr="(\\/([1-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])){0,1}"
+ # Regex matching an optional port (starting with '#') range of 1-65536
+ local portelem="(#(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{0,3}|0))?";
+ # Build a full IPv6 regex from the above subexpressions
+ local regex="^(((${ipv6elem}))*((:${ipv6elem}))*::((${ipv6elem}))*((:${ipv6elem}))*|((${ipv6elem}))((:${ipv6elem})){7})${v6cidr}${portelem}$"
+
+ # Evaluate the regex, and return the result
+ [[ ${ip} =~ ${regex} ]]
+
+ stat=$?
+ return "${stat}"
+}
+
SetWebPassword() {
if [ "${SUDO_USER}" == "www-data" ]; then
echo "Security measure: user www-data is not allowed to change webUI password!"
@@ -304,7 +346,7 @@ trust-anchor=.,20326,8,2,E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC68345710423
# changes in the non-FQDN forwarding. This cannot be done in 01-pihole.conf
# as we don't want to delete all local=/.../ lines so it's much safer to
# simply rewrite the entire corresponding config file (which is what the
- # DHCP settings subroutie is doing)
+ # DHCP settings subroutine is doing)
ProcessDHCPSettings
}
@@ -314,7 +356,7 @@ SetDNSServers() {
IFS=',' read -r -a array <<< "${args[2]}"
for index in "${!array[@]}"
do
- # Replace possible "\#" by "#". This fixes AdminLTE#1427
+ # Replace possible "\#" by "#". This fixes web#1427
local ip
ip="${array[index]//\\#/#}"
@@ -402,13 +444,8 @@ ProcessDHCPSettings() {
if [[ "${DHCP_LEASETIME}" == "0" ]]; then
leasetime="infinite"
elif [[ "${DHCP_LEASETIME}" == "" ]]; then
- leasetime="24"
- addOrEditKeyValPair "${setupVars}" "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"
- addOrEditKeyValPair "${setupVars}" "DHCP_LEASETIME" "${leasetime}"
+ leasetime="24h"
+ addOrEditKeyValPair "${setupVars}" "DHCP_LEASETIME" "24"
else
leasetime="${DHCP_LEASETIME}h"
fi
@@ -448,8 +485,8 @@ dhcp-leasefile=/etc/pihole/dhcp.leases
echo "#quiet-dhcp6
#enable-ra
dhcp-option=option6:dns-server,[::]
-dhcp-range=::100,::1ff,constructor:${interface},ra-names,slaac,64,3600
-ra-param=*,0,0
+dhcp-range=::,constructor:${interface},ra-names,ra-stateless,64
+
" >> "${dhcpconfig}"
fi
@@ -503,94 +540,86 @@ SetWebUILayout() {
addOrEditKeyValPair "${setupVars}" "WEBUIBOXEDLAYOUT" "${args[2]}"
}
+ChangeSpeedTestSchedule() {
+ local interval="${args[2]%\.}"
-ClearSpeedtestData(){
- mv $speedtestdb $speedtestdb"_old"
- cp /var/www/html/admin/scripts/pi-hole/speedtest/speedtest.db $speedtestdb
-}
-
-ChageSpeedTestSchedule(){
- if [[ "${args[2]}" =~ ^[0-9]+$ ]]; then
- if [ "${args[2]}" -ge 0 -a "${args[2]}" -le 24 ]; then
- change_setting "SPEEDTESTSCHEDULE" "${args[2]}"
- SetCronTab ${args[2]}
- fi
- fi
-}
-
-SpeedtestServer(){
- if [[ "${args[2]}" =~ ^[0-9]+$ ]]; then
- change_setting "SPEEDTEST_SERVER" "${args[2]}"
- # SetCronTab ${args[2]}
- else
- # Autoselect for invalid data
- change_setting "SPEEDTEST_SERVER" ""
- fi
-
-}
+ if [[ "${interval-}" =~ ^-?([0-9]+(\.[0-9]*)?|\.[0-9]+)$ ]]; then
+ if (($(echo "$interval < 0" | bc -l))); then
+ interval="0"
+ else
+ addOrEditKeyValPair "${setupVars}" "SPEEDTESTSCHEDULE" "$interval"
+ fi
+ else
+ interval=$(grep "SPEEDTESTSCHEDULE" "${setupVars}" | cut -f2 -d"=")
+ if [[ ! "${interval-}" =~ ^([0-9]+(\.[0-9]*)?|\.[0-9]+)$ ]]; then
+ interval="nan"
+ fi
+ fi
-RunSpeedtestNow(){
- mkdir -p /tmp/speedtest
- lockfile="/tmp/speedtest/lock"
- if [ -f $speedtestdb ]
- then
- echo ""
- else
- cp /var/www/html/admin/scripts/pi-hole/speedtest/speedtest.db $speedtestdb
- sleep 2
- fi
- if [ -f "$lockfile" ]
- then
- echo "Speedtest is already in progress, is something went wrong delete this file - "$lockfile
- else
- touch $lockfile
- if [[ "${args[2]}" == "-n" ]]; then
- speedtest-cli
+ if [[ ! -d /run/systemd/system ]]; then
+ # shellcheck disable=SC1091
+ source "/opt/pihole/speedtestmod/lib.sh"
+ generate_cron_job "$interval"
+ elif [[ "$interval" == "0" ]] || [[ "$interval" == "nan" ]]; then
+ systemctl disable --now pihole-speedtest.timer &>/dev/null
else
- echo "Testing Speed"
- result=`$speedtestfile`
- echo $result
- rm $lockfile
+ # shellcheck disable=SC1091
+ source "/opt/pihole/speedtestmod/lib.sh"
+ generate_systemd_service "$interval"
fi
- fi
}
-SpeedtestMode(){
- if [[ "${args[2]}" ]]; then
- change_setting "SPEEDTEST_MODE" "${args[2]}"
- else
- # Autoselect for invalid data
- change_setting "SPEEDTEST_MODE" "python"
- fi
+UpdateSpeedTestRange() {
+ [[ ! "${args[2]}" =~ ^-?[0-9]+$ || "${args[2]}" -lt -1 ]] || addOrEditKeyValPair "${setupVars}" "SPEEDTEST_CHART_DAYS" "${args[2]}"
+}
+UpdateSpeedTestChartType() {
+ local chart_type="line"
+ [[ ! "${args[2]}" =~ ^(bar|line)$ ]] || chart_type="${args[2]}"
+ addOrEditKeyValPair "${setupVars}" "SPEEDTEST_CHART_TYPE" "$chart_type"
}
+SpeedtestServer() {
+ local test_server=""
+ [[ ! "${args[2]}" =~ ^[0-9]+$ ]] || test_server="${args[2]}"
+ addOrEditKeyValPair "${setupVars}" "SPEEDTEST_SERVER" "$test_server"
+}
-SetCronTab()
-{
- # Remove OLD
- crontab -l > crontab.tmp || true
+RunSpeedtestMod() {
+ # if there is a running session, wait for it to finish
+ while [[ $(tmux list-sessions 2>/dev/null | grep -c pimod) -gt 0 ]]; do
+ sleep 1
+ ((counter++))
- if [[ "$1" == "0" ]]; then
- sed -i '/speedtest/d' crontab.tmp
- crontab crontab.tmp && rm -f crontab.tmp
- else
- sed -i '/speedtest/d' crontab.tmp
+ if [[ $counter -gt 300 ]]; then
+ tmux kill-session -t pimod
+ break
+ fi
+ done
+
+ # discard indexes 0 and 1 from args
+ args=("${args[@]:2}")
+ tmux new-session -d -s pimod "sudo bash $speedtestmod ${args[*]}"
+}
- mode=$(sed -n -e '/SPEEDTEST_MODE/ s/.*\= *//p' $setupVars)
+RunSpeedtestNow() {
+ # if the session is still running after 5 minutes, kill it
+ while [[ $(tmux list-sessions 2>/dev/null | grep -c pimodtest) -gt 0 ]]; do
+ sleep 1
+ ((counter++))
- if [[ "$mode" =~ "official" ]]; then
- speedtest_file="/var/www/html/admin/scripts/pi-hole/speedtest/speedtest-official.sh"
- else
- speedtest_file="/var/www/html/admin/scripts/pi-hole/speedtest/speedtest.sh"
- fi
+ if [[ $counter -gt 300 ]]; then
+ tmux kill-session -t pimodtest
+ break
+ fi
+ done
- newtab="0 */"${1}" * * * sudo \""${speedtest_file}"\" > /dev/null 2>&1"
- printf '%s\n' "$newtab" >>crontab.tmp
- crontab crontab.tmp && rm -f crontab.tmp
- fi
+ # discard indexes 0 and 1 from args
+ args=("${args[@]:2}")
+ tmux new-session -d -s pimodtest "sudo bash $speedtestfile ${args[*]}"
}
+
SetWebUITheme() {
addOrEditKeyValPair "${setupVars}" "WEBTHEME" "${args[2]}"
}
@@ -619,13 +648,13 @@ CustomizeAdLists() {
if CheckUrl "${address}"; then
if [[ "${args[2]}" == "enable" ]]; then
- pihole-FTL sqlite3 "${gravityDBfile}" "UPDATE adlist SET enabled = 1 WHERE address = '${address}'"
+ pihole-FTL sqlite3 -ni "${gravityDBfile}" "UPDATE adlist SET enabled = 1 WHERE address = '${address}'"
elif [[ "${args[2]}" == "disable" ]]; then
- pihole-FTL sqlite3 "${gravityDBfile}" "UPDATE adlist SET enabled = 0 WHERE address = '${address}'"
+ pihole-FTL sqlite3 -ni "${gravityDBfile}" "UPDATE adlist SET enabled = 0 WHERE address = '${address}'"
elif [[ "${args[2]}" == "add" ]]; then
- pihole-FTL sqlite3 "${gravityDBfile}" "INSERT OR IGNORE INTO adlist (address, comment) VALUES ('${address}', '${comment}')"
+ pihole-FTL sqlite3 -ni "${gravityDBfile}" "INSERT OR IGNORE INTO adlist (address, comment) VALUES ('${address}', '${comment}')"
elif [[ "${args[2]}" == "del" ]]; then
- pihole-FTL sqlite3 "${gravityDBfile}" "DELETE FROM adlist WHERE address = '${address}'"
+ pihole-FTL sqlite3 -ni "${gravityDBfile}" "DELETE FROM adlist WHERE address = '${address}'"
else
echo "Not permitted"
return 1
@@ -636,33 +665,6 @@ CustomizeAdLists() {
fi
}
-function UpdateSpeedTestRange(){
- if [[ "${args[2]}" =~ ^[0-9]+$ ]]; then
- if [ "${args[2]}" -ge 0 -a "${args[2]}" -le 30 ]; then
- change_setting "SPEEDTEST_CHART_DAYS" "${args[2]}"
- fi
- fi
-}
-
-SetPrivacyMode() {
- if [[ "${args[2]}" == "true" ]]; then
- change_setting "API_PRIVACY_MODE" "true"
- else
- change_setting "API_PRIVACY_MODE" "false"
- fi
-}
-
-ResolutionSettings() {
- typ="${args[2]}"
- state="${args[3]}"
-
- if [[ "${typ}" == "forward" ]]; then
- change_setting "API_GET_UPSTREAM_DNS_HOSTNAME" "${state}"
- elif [[ "${typ}" == "clients" ]]; then
- change_setting "API_GET_CLIENT_HOSTNAME" "${state}"
- fi
-}
-
AddDHCPStaticAddress() {
mac="${args[2]}"
ip="${args[3]}"
@@ -691,37 +693,6 @@ RemoveDHCPStaticAddress() {
}
-SetAdminEmail() {
- if [[ "${1}" == "-h" ]] || [[ "${1}" == "--help" ]]; then
- echo "Usage: pihole -a email
-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
-
- # 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
-
- addOrEditKeyValPair "${setupVars}" "ADMIN_EMAIL" "${args[2]}"
- echo -e " ${TICK} Setting admin contact to ${args[2]}"
- else
- addOrEditKeyValPair "${setupVars}" "ADMIN_EMAIL" ""
- echo -e " ${TICK} Removing admin contact"
- fi
-}
-
SetListeningMode() {
source "${setupVars}"
@@ -773,7 +744,7 @@ Teleporter() {
host="${host//./_}"
filename="pi-hole-${host:-noname}-teleporter_${datetimestamp}.tar.gz"
fi
- php /var/www/html/admin/scripts/pi-hole/php/teleporter.php > "${filename}"
+ php "${webroot}/admin/scripts/pi-hole/php/teleporter.php" > "${filename}"
}
checkDomain()
@@ -781,11 +752,19 @@ checkDomain()
local domain validDomain
# Convert to lowercase
domain="${1,,}"
- validDomain=$(grep -P "^((-|_)*[a-z\\d]((-|_)*[a-z\\d])*(-|_)*)(\\.(-|_)*([a-z\\d]((-|_)*[a-z\\d])*))*$" <<< "${domain}") # Valid chars check
+ validDomain=$(grep -P "^((-|_)*[a-z0-9]((-|_)*[a-z0-9])*(-|_)*)(\\.(-|_)*([a-z0-9]((-|_)*[a-z0-9])*))*$" <<< "${domain}") # Valid chars check
validDomain=$(grep -P "^[^\\.]{1,63}(\\.[^\\.]{1,63})*$" <<< "${validDomain}") # Length of each label
echo "${validDomain}"
}
+escapeDots()
+{
+ # SC suggest bashism ${variable//search/replace}
+ # shellcheck disable=SC2001
+ escaped=$(echo "$1" | sed 's/\./\\./g')
+ echo "${escaped}"
+}
+
addAudit()
{
shift # skip "-a"
@@ -809,12 +788,12 @@ addAudit()
done
# Insert only the domain here. The date_added field will be
# filled with its default value (date_added = current timestamp)
- pihole-FTL sqlite3 "${gravityDBfile}" "INSERT INTO domain_audit (domain) VALUES ${domains};"
+ pihole-FTL sqlite3 -ni "${gravityDBfile}" "INSERT INTO domain_audit (domain) VALUES ${domains};"
}
clearAudit()
{
- pihole-FTL sqlite3 "${gravityDBfile}" "DELETE FROM domain_audit;"
+ pihole-FTL sqlite3 -ni "${gravityDBfile}" "DELETE FROM domain_audit;"
}
SetPrivacyLevel() {
@@ -861,6 +840,7 @@ RemoveCustomDNSAddress() {
validHost="$(checkDomain "${host}")"
if [[ -n "${validHost}" ]]; then
if valid_ip "${ip}" || valid_ip6 "${ip}" ; then
+ validHost=$(escapeDots "${validHost}")
sed -i "/^${ip} ${validHost}$/Id" "${dnscustomfile}"
else
echo -e " ${CROSS} Invalid IP has been passed"
@@ -888,7 +868,12 @@ AddCustomCNAMERecord() {
if [[ -n "${validDomain}" ]]; then
validTarget="$(checkDomain "${target}")"
if [[ -n "${validTarget}" ]]; then
- echo "cname=${validDomain},${validTarget}" >> "${dnscustomcnamefile}"
+ if [ "${validDomain}" = "${validTarget}" ]; then
+ echo " ${CROSS} Domain and target are the same. This would cause a DNS loop."
+ exit 1
+ else
+ echo "cname=${validDomain},${validTarget}" >> "${dnscustomcnamefile}"
+ fi
else
echo " ${CROSS} Invalid Target Passed!"
exit 1
@@ -914,7 +899,9 @@ RemoveCustomCNAMERecord() {
if [[ -n "${validDomain}" ]]; then
validTarget="$(checkDomain "${target}")"
if [[ -n "${validTarget}" ]]; then
- sed -i "/cname=${validDomain},${validTarget}$/Id" "${dnscustomcnamefile}"
+ validDomain=$(escapeDots "${validDomain}")
+ validTarget=$(escapeDots "${validTarget}")
+ sed -i "/^cname=${validDomain},${validTarget}$/Id" "${dnscustomcnamefile}"
else
echo " ${CROSS} Invalid Target Passed!"
exit 1
@@ -969,19 +956,18 @@ main() {
"-h" | "--help" ) helpFunc;;
"addstaticdhcp" ) AddDHCPStaticAddress;;
"removestaticdhcp" ) RemoveDHCPStaticAddress;;
- "-e" | "email" ) SetAdminEmail "$3";;
"-i" | "interface" ) SetListeningMode "$@";;
"-t" | "teleporter" ) Teleporter;;
"adlist" ) CustomizeAdLists;;
"audit" ) addAudit "$@";;
"clearaudit" ) clearAudit;;
"-l" | "privacylevel" ) SetPrivacyLevel;;
- "-s" | "speedtest" ) ChageSpeedTestSchedule;;
+ "-s" | "speedtest" ) ChangeSpeedTestSchedule;;
"-sd" ) UpdateSpeedTestRange;;
- "-sn" ) RunSpeedtestNow;;
- "-sm" ) SpeedtestMode;;
- "-sc" ) ClearSpeedtestData;;
+ "-sm" ) RunSpeedtestMod ;;
+ "-sn" ) RunSpeedtestNow ;;
"-ss" ) SpeedtestServer;;
+ "-st" ) UpdateSpeedTestChartType;;
"addcustomdns" ) AddCustomDNSAddress;;
"removecustomdns" ) RemoveCustomDNSAddress;;
"addcustomcname" ) AddCustomCNAMERecord;;
@@ -989,8 +975,10 @@ main() {
"ratelimit" ) SetRateLimit;;
* ) helpFunc;;
esac
+
shift
- if [[ $# = 0 ]]; then
+
+ if [[ $# = 0 ]]; then
helpFunc
fi
}
diff --git a/advanced/Templates/gravity_copy.sql b/advanced/Templates/gravity_copy.sql
index 3bea731d1b..ed11b61a78 100644
--- a/advanced/Templates/gravity_copy.sql
+++ b/advanced/Templates/gravity_copy.sql
@@ -19,8 +19,6 @@ INSERT OR REPLACE INTO adlist SELECT * FROM OLD.adlist;
DELETE FROM OLD.adlist_by_group WHERE adlist_id NOT IN (SELECT id FROM OLD.adlist);
INSERT OR REPLACE INTO adlist_by_group SELECT * FROM OLD.adlist_by_group;
-INSERT OR REPLACE INTO info SELECT * FROM OLD.info;
-
INSERT OR REPLACE INTO client SELECT * FROM OLD.client;
DELETE FROM OLD.client_by_group WHERE client_id NOT IN (SELECT id FROM OLD.client);
INSERT OR REPLACE INTO client_by_group SELECT * FROM OLD.client_by_group;
diff --git a/advanced/Templates/logrotate b/advanced/Templates/logrotate
index ffed910b9d..9a56b55297 100644
--- a/advanced/Templates/logrotate
+++ b/advanced/Templates/logrotate
@@ -1,4 +1,4 @@
-/var/log/pihole.log {
+/var/log/pihole/pihole.log {
# su #
daily
copytruncate
@@ -9,7 +9,7 @@
nomail
}
-/var/log/pihole-FTL.log {
+/var/log/pihole/FTL.log {
# su #
weekly
copytruncate
diff --git a/advanced/Templates/pihole-FTL-poststop.sh b/advanced/Templates/pihole-FTL-poststop.sh
new file mode 100755
index 0000000000..ac3898d2f0
--- /dev/null
+++ b/advanced/Templates/pihole-FTL-poststop.sh
@@ -0,0 +1,13 @@
+#!/usr/bin/env sh
+
+# Source utils.sh for getFTLPIDFile()
+PI_HOLE_SCRIPT_DIR='/opt/pihole'
+utilsfile="${PI_HOLE_SCRIPT_DIR}/utils.sh"
+# shellcheck disable=SC1090
+. "${utilsfile}"
+
+# Get file paths
+FTL_PID_FILE="$(getFTLPIDFile)"
+
+# Cleanup
+rm -f /run/pihole/FTL.sock /dev/shm/FTL-* "${FTL_PID_FILE}"
diff --git a/advanced/Templates/pihole-FTL-prestart.sh b/advanced/Templates/pihole-FTL-prestart.sh
new file mode 100755
index 0000000000..ff4abf3abe
--- /dev/null
+++ b/advanced/Templates/pihole-FTL-prestart.sh
@@ -0,0 +1,38 @@
+#!/usr/bin/env sh
+
+# Source utils.sh for getFTLPIDFile()
+PI_HOLE_SCRIPT_DIR='/opt/pihole'
+utilsfile="${PI_HOLE_SCRIPT_DIR}/utils.sh"
+# shellcheck disable=SC1090
+. "${utilsfile}"
+
+# Get file paths
+FTL_PID_FILE="$(getFTLPIDFile)"
+
+# Touch files to ensure they exist (create if non-existing, preserve if existing)
+# shellcheck disable=SC2174
+mkdir -pm 0755 /run/pihole /var/log/pihole
+[ -f "${FTL_PID_FILE}" ] || install -D -m 644 -o pihole -g pihole /dev/null "${FTL_PID_FILE}"
+[ -f /var/log/pihole/FTL.log ] || install -m 644 -o pihole -g pihole /dev/null /var/log/pihole/FTL.log
+[ -f /var/log/pihole/pihole.log ] || install -m 640 -o pihole -g pihole /dev/null /var/log/pihole/pihole.log
+[ -f /etc/pihole/dhcp.leases ] || install -m 644 -o pihole -g pihole /dev/null /etc/pihole/dhcp.leases
+# Ensure that permissions are set so that pihole-FTL can edit all necessary files
+chown pihole:pihole /run/pihole /etc/pihole /var/log/pihole /var/log/pihole/FTL.log /var/log/pihole/pihole.log /etc/pihole/dhcp.leases
+# Ensure that permissions are set so that pihole-FTL can edit the files. We ignore errors as the file may not (yet) exist
+chmod -f 0644 /etc/pihole/macvendor.db /etc/pihole/dhcp.leases /var/log/pihole/FTL.log
+chmod -f 0640 /var/log/pihole/pihole.log
+# Chown database files to the user FTL runs as. We ignore errors as the files may not (yet) exist
+chown -f pihole:pihole /etc/pihole/pihole-FTL.db /etc/pihole/gravity.db /etc/pihole/macvendor.db
+# Chmod database file permissions so that the pihole group (web interface) can edit the file. We ignore errors as the files may not (yet) exist
+chmod -f 0664 /etc/pihole/pihole-FTL.db
+
+# Backward compatibility for user-scripts that still expect log files in /var/log instead of /var/log/pihole
+# Should be removed with Pi-hole v6.0
+if [ ! -f /var/log/pihole.log ]; then
+ ln -sf /var/log/pihole/pihole.log /var/log/pihole.log
+ chown -h pihole:pihole /var/log/pihole.log
+fi
+if [ ! -f /var/log/pihole-FTL.log ]; then
+ ln -sf /var/log/pihole/FTL.log /var/log/pihole-FTL.log
+ chown -h pihole:pihole /var/log/pihole-FTL.log
+fi
diff --git a/advanced/Templates/pihole-FTL.service b/advanced/Templates/pihole-FTL.service
index 41ab801811..460339ae71 100644
--- a/advanced/Templates/pihole-FTL.service
+++ b/advanced/Templates/pihole-FTL.service
@@ -9,8 +9,23 @@
# Description: Enable service provided by pihole-FTL daemon
### END INIT INFO
+# Source utils.sh for getFTLPIDFile(), getFTLPID()
+PI_HOLE_SCRIPT_DIR="/opt/pihole"
+utilsfile="${PI_HOLE_SCRIPT_DIR}/utils.sh"
+# shellcheck disable=SC1090
+. "${utilsfile}"
+
+
is_running() {
- pgrep -xo "pihole-FTL" > /dev/null
+ if [ -d "/proc/${FTL_PID}" ]; then
+ return 0
+ fi
+ return 1
+}
+
+cleanup() {
+ # Run post-stop script, which does cleanup among runtime files
+ sh "${PI_HOLE_SCRIPT_DIR}/pihole-FTL-poststop.sh"
}
@@ -19,27 +34,21 @@ start() {
if is_running; then
echo "pihole-FTL is already running"
else
- # Touch files to ensure they exist (create if non-existing, preserve if existing)
- mkdir -pm 0755 /run/pihole
- [ ! -f /run/pihole-FTL.pid ] && install -m 644 -o pihole -g pihole /dev/null /run/pihole-FTL.pid
- [ ! -f /run/pihole-FTL.port ] && install -m 644 -o pihole -g pihole /dev/null /run/pihole-FTL.port
- [ ! -f /var/log/pihole-FTL.log ] && install -m 644 -o pihole -g pihole /dev/null /var/log/pihole-FTL.log
- [ ! -f /var/log/pihole.log ] && install -m 644 -o pihole -g pihole /dev/null /var/log/pihole.log
- [ ! -f /etc/pihole/dhcp.leases ] && install -m 644 -o pihole -g pihole /dev/null /etc/pihole/dhcp.leases
- # Ensure that permissions are set so that pihole-FTL can edit all necessary files
- chown pihole:pihole /run/pihole /etc/pihole /var/log/pihole.log /var/log/pihole.log /etc/pihole/dhcp.leases
- # Ensure that permissions are set so that pihole-FTL can edit the files. We ignore errors as the file may not (yet) exist
- chmod -f 0644 /etc/pihole/macvendor.db /etc/pihole/dhcp.leases /var/log/pihole-FTL.log /var/log/pihole.log
- # Chown database files to the user FTL runs as. We ignore errors as the files may not (yet) exist
- chown -f pihole:pihole /etc/pihole/pihole-FTL.db /etc/pihole/gravity.db /etc/pihole/macvendor.db
- # Chown database file permissions so that the pihole group (web interface) can edit the file. We ignore errors as the files may not (yet) exist
- chmod -f 0664 /etc/pihole/pihole-FTL.db
+ # Run pre-start script, which pre-creates all expected files with correct permissions
+ sh "${PI_HOLE_SCRIPT_DIR}/pihole-FTL-prestart.sh"
+
if setcap CAP_NET_BIND_SERVICE,CAP_NET_RAW,CAP_NET_ADMIN,CAP_SYS_NICE,CAP_IPC_LOCK,CAP_CHOWN+eip "/usr/bin/pihole-FTL"; then
su -s /bin/sh -c "/usr/bin/pihole-FTL" pihole
else
echo "Warning: Starting pihole-FTL as root because setting capabilities is not supported on this system"
/usr/bin/pihole-FTL
fi
+ rc=$?
+ # Cleanup if startup failed
+ if [ "${rc}" != 0 ]; then
+ cleanup
+ exit $rc
+ fi
echo
fi
}
@@ -47,7 +56,7 @@ start() {
# Stop the service
stop() {
if is_running; then
- pkill -xo "pihole-FTL"
+ kill "${FTL_PID}"
for i in 1 2 3 4 5; do
if ! is_running; then
break
@@ -60,16 +69,14 @@ stop() {
if is_running; then
echo "Not stopped; may still be shutting down or shutdown may have failed, killing now"
- pkill -xo -9 "pihole-FTL"
- exit 1
+ kill -9 "${FTL_PID}"
else
echo "Stopped"
fi
else
echo "Not running"
fi
- # Cleanup
- rm -f /run/pihole/FTL.sock /dev/shm/FTL-*
+ cleanup
echo
}
@@ -86,6 +93,16 @@ status() {
### main logic ###
+
+# catch sudden termination
+trap 'cleanup; exit 1' INT HUP TERM ABRT
+
+# Get FTL's PID file path
+FTL_PID_FILE="$(getFTLPIDFile)"
+
+# Get FTL's current PID
+FTL_PID="$(getFTLPID "${FTL_PID_FILE}")"
+
case "$1" in
stop)
stop
diff --git a/advanced/Templates/pihole-FTL.systemd b/advanced/Templates/pihole-FTL.systemd
new file mode 100644
index 0000000000..2a11419906
--- /dev/null
+++ b/advanced/Templates/pihole-FTL.systemd
@@ -0,0 +1,41 @@
+[Unit]
+Description=Pi-hole FTL
+# This unit is supposed to indicate when network functionality is available, but it is only
+# very weakly defined what that is supposed to mean, with one exception: at shutdown, a unit
+# that is ordered after network-online.target will be stopped before the network
+Wants=network-online.target
+After=network-online.target
+# A target that should be used as synchronization point for all host/network name service lookups.
+# All services for which the availability of full host/network name resolution is essential should
+# be ordered after this target, but not pull it in.
+Wants=nss-lookup.target
+Before=nss-lookup.target
+
+# Limit (re)start loop to 5 within 1 minute
+StartLimitBurst=5
+StartLimitIntervalSec=60s
+
+[Service]
+User=pihole
+PermissionsStartOnly=true
+AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_NET_ADMIN CAP_SYS_NICE CAP_IPC_LOCK CAP_CHOWN
+
+ExecStartPre=/opt/pihole/pihole-FTL-prestart.sh
+ExecStart=/usr/bin/pihole-FTL -f
+Restart=on-failure
+RestartSec=5s
+ExecReload=/bin/kill -HUP $MAINPID
+ExecStopPost=/opt/pihole/pihole-FTL-poststop.sh
+
+# Use graceful shutdown with a reasonable timeout
+TimeoutStopSec=10s
+
+# Make /usr, /boot, /etc and possibly some more folders read-only...
+ProtectSystem=full
+# ... except /etc/pihole
+# This merely retains r/w access rights, it does not add any new.
+# Must still be writable on the host!
+ReadWriteDirectories=/etc/pihole
+
+[Install]
+WantedBy=multi-user.target
diff --git a/advanced/Templates/pihole.cron b/advanced/Templates/pihole.cron
index 37724d2e81..c62d31ab3b 100644
--- a/advanced/Templates/pihole.cron
+++ b/advanced/Templates/pihole.cron
@@ -18,7 +18,7 @@
# early morning. Download any updates from the adlists
# Squash output to log, then splat the log to stdout on error to allow for
# standard crontab job error handling.
-59 1 * * 7 root PATH="$PATH:/usr/sbin:/usr/local/bin/" pihole updateGravity >/var/log/pihole_updateGravity.log || cat /var/log/pihole_updateGravity.log
+59 1 * * 7 root PATH="$PATH:/usr/sbin:/usr/local/bin/" pihole updateGravity >/var/log/pihole/pihole_updateGravity.log || cat /var/log/pihole/pihole_updateGravity.log
# Pi-hole: Flush the log daily at 00:00
# The flush script will use logrotate if available
@@ -28,9 +28,6 @@
@reboot root /usr/sbin/logrotate --state /var/lib/logrotate/pihole /etc/pihole/logrotate
-# Pi-hole: Grab local version and branch every 10 minutes
-*/10 * * * * root PATH="$PATH:/usr/sbin:/usr/local/bin/" pihole updatechecker local
-
-# Pi-hole: Grab remote version every 24 hours
-59 17 * * * root PATH="$PATH:/usr/sbin:/usr/local/bin/" pihole updatechecker remote
-@reboot root PATH="$PATH:/usr/sbin:/usr/local/bin/" pihole updatechecker remote reboot
+# Pi-hole: Grab remote and local version every 24 hours
+59 17 * * * root PATH="$PATH:/usr/sbin:/usr/local/bin/" pihole updatechecker
+@reboot root PATH="$PATH:/usr/sbin:/usr/local/bin/" pihole updatechecker reboot
diff --git a/advanced/bash-completion/pihole b/advanced/bash-completion/pihole
index 25208a3577..29a3270de1 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 interface kelvin password privacylevel"
+ opts_admin="celsius fahrenheit interface kelvin password privacylevel"
COMPREPLY=( $(compgen -W "${opts_admin}" -- ${cur}) )
;;
"checkout")
diff --git a/advanced/blockingpage.css b/advanced/blockingpage.css
deleted file mode 100644
index 0cc7a65cb3..0000000000
--- a/advanced/blockingpage.css
+++ /dev/null
@@ -1,455 +0,0 @@
-/* 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 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."; }
-
-.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;
- 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;
- 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 {
- 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;
- width: 100%;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: 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 {
- 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: 400;
- 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;
-}
-
-html, body {
- height: 100%;
-}
-
-#pihole_card {
- width: 400px;
- height: auto;
- max-width: 400px;
-}
-
- #pihole_card p, #pihole_card a {
- font-size: 13pt;
- text-align: center;
- }
-
-#pihole_logo_splash {
- height: auto;
- width: 100%;
-}
-
-/* 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 {
- 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 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;
-}
-
-#bpOutput.hidden { display: none; }
-#bpOutput.success { background: #00a65a; }
-#bpOutput.error { background: #dd4b39; }
-
-.blockMsg, .flagMsg {
- font: 700 1.8rem Consolas, Courier, monospace;
- padding: 5px 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 color */
-.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; }
-
-@-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; -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;
- 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;
- }
-}
-
-@media only screen and (max-width: 400px) {
- #pihole_card {
- width: 100%;
- height: auto;
- }
-
- #pihole_card p, #pihole_card a {
- font-size: 100%;
- }
-}
-
-@media only screen and (max-width: 256px) {
- #pihole_logo_splash {
- width: 90% !important;
- height: auto;
- }
-}
diff --git a/advanced/cmdline.txt b/advanced/cmdline.txt
deleted file mode 100644
index 84d52b79b0..0000000000
--- a/advanced/cmdline.txt
+++ /dev/null
@@ -1 +0,0 @@
-dwc_otg.lpm_enable=0 console=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait fbcon=map:10 fbcon=font:VGA8x8 consoleblank=0
diff --git a/advanced/console-setup b/advanced/console-setup
deleted file mode 100644
index f12be6eb44..0000000000
--- a/advanced/console-setup
+++ /dev/null
@@ -1,17 +0,0 @@
-# CONFIGURATION FILE FOR SETUPCON
-
-# Consult the console-setup(5) manual page.
-
-ACTIVE_CONSOLES="/dev/tty[1-6]"
-
-CHARMAP="UTF-8"
-
-# For best results with the Adafruit 2.8 LCD and Pi-hole's chronometer
-CODESET="guess"
-FONTFACE="Terminus"
-FONTSIZE="10x20"
-
-VIDEOMODE=
-
-# The following is an example how to use a braille font
-# FONT='lat9w-08.psf.gz brl-8x8.psf'
diff --git a/advanced/dnsmasq.conf.original b/advanced/dnsmasq.conf.original
index 6758f0b8e8..4aa5a8bfc8 100644
--- a/advanced/dnsmasq.conf.original
+++ b/advanced/dnsmasq.conf.original
@@ -507,7 +507,7 @@
# (using /etc/hosts) then that name can be specified as the
# tftp_servername (the third option to dhcp-boot) and in that
# case dnsmasq resolves this name and returns the resultant IP
-# addresses in round robin fasion. This facility can be used to
+# addresses in round robin fashion. This facility can be used to
# load balance the tftp load among a set of servers.
#dhcp-boot=/var/ftpd/pxelinux.0,boothost,tftp_server_name
diff --git a/advanced/index.php b/advanced/index.php
deleted file mode 100644
index cf0ab854b0..0000000000
--- a/advanced/index.php
+++ /dev/null
@@ -1,417 +0,0 @@
-/etc/pihole/setupVars.conf");
-
-// Get values from setupVars.conf
-$setupVars = parse_ini_file("/etc/pihole/setupVars.conf");
-$svPasswd = !empty($setupVars["WEBPASSWORD"]);
-$svEmail = (!empty($setupVars["ADMIN_EMAIL"]) && filter_var($setupVars["ADMIN_EMAIL"], FILTER_VALIDATE_EMAIL)) ? $setupVars["ADMIN_EMAIL"] : "";
-unset($setupVars);
-
-// Set landing page location, found within /var/www/html/
-$landPage = "../landing.php";
-
-// Define array for hostnames to be accepted as self address for splash page
-$authorizedHosts = [ "localhost" ];
-if (!empty($_SERVER["FQDN"])) {
- // If setenv.add-environment = ("fqdn" => "true") is configured in lighttpd,
- // append $serverName to $authorizedHosts
- array_push($authorizedHosts, $serverName);
-} else if (!empty($_SERVER["VIRTUAL_HOST"])) {
- // Append virtual hostname to $authorizedHosts
- array_push($authorizedHosts, $_SERVER["VIRTUAL_HOST"]);
-}
-
-// Set which extension types render as Block Page (Including "" for index.ext)
-$validExtTypes = array("asp", "htm", "html", "php", "rss", "xml", "");
-
-// Get extension of current URL
-$currentUrlExt = pathinfo($_SERVER["REQUEST_URI"], PATHINFO_EXTENSION);
-
-// Set mobile friendly viewport
-$viewPort = '';
-
-// Set response header
-function setHeader($type = "x") {
- header("X-Pi-hole: A black hole for Internet advertisements.");
- if (isset($type) && $type === "js") header("Content-Type: application/javascript");
-}
-
-// Determine block page type
-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)) {
- // When directly browsing via IP or authorized hostname
- // Render splash/landing page based off presence of $landPage file
- // Unset variables so as to not be included in $landPage or $splashPage
- unset($svPasswd, $svEmail, $authorizedHosts, $validExtTypes, $currentUrlExt);
- // If $landPage file is present
- if (is_file(getcwd()."/$landPage")) {
- unset($serverName, $viewPort); // unset extra variables not to be included in $landpage
- include $landPage;
- exit();
- }
- // If $landPage file was not present, Set Splash Page output
- $splashPage = <<
-
-
-
- $viewPort
- ● $serverName
-
-
-
-
-
-
-
Pi-hole: Your black hole for Internet advertisements