Merge branch 'development-v6' into fix/gravity_domain

Signed-off-by: DL6ER <dl6er@dl6er.de>
pull/5571/head
DL6ER 2 months ago
commit 8fb3a594eb
No known key found for this signature in database
GPG Key ID: 00135ACBD90B28DD

@ -29,12 +29,12 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- -
name: Initialize CodeQL name: Initialize CodeQL
uses: github/codeql-action/init@v2 uses: github/codeql-action/init@v3
with: with:
languages: 'python' languages: 'python'
- -
name: Autobuild name: Autobuild
uses: github/codeql-action/autobuild@v2 uses: github/codeql-action/autobuild@v3
- -
name: Perform CodeQL Analysis name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2 uses: github/codeql-action/analyze@v3

@ -17,20 +17,23 @@ jobs:
issues: write issues: write
steps: steps:
- uses: actions/stale@v8.0.0 - uses: actions/stale@v9.0.0
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 30 days-before-stale: 30
days-before-close: 5 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-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_label stale-issue-label: '${{ env.stale_label }}'
exempt-issue-labels: 'Internal, Fixed in next release, Bug: Confirmed, Documentation Needed' exempt-issue-labels: 'Internal, Fixed in next release, Bug: Confirmed, Documentation Needed'
exempt-all-issue-assignees: true exempt-all-issue-assignees: true
operations-per-run: 300 operations-per-run: 300
close-issue-reason: 'not_planned' close-issue-reason: 'not_planned'
remove_stale: # trigger "stale" removal immediately when stale issues are commented on remove_stale:
if: github.event_name == 'issue_comment' # 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: permissions:
contents: read # for actions/checkout contents: read # for actions/checkout
issues: write # to edit issues label issues: write # to edit issues label
@ -39,7 +42,7 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.1.1
- name: Remove 'stale' label - name: Remove 'stale' label
run: gh issue edit ${{ github.event.issue.number }} --remove-label $stale_label run: gh issue edit ${{ github.event.issue.number }} --remove-label ${{ env.stale_label }}
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

@ -17,7 +17,7 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- uses: actions/stale@v8.0.0 - uses: actions/stale@v9.0.0
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
# Do not automatically mark PR/issue as stale # Do not automatically mark PR/issue as stale

@ -64,9 +64,8 @@ jobs:
ubuntu_23, ubuntu_23,
centos_8, centos_8,
centos_9, centos_9,
fedora_36, fedora_38,
fedora_37, fedora_39,
fedora_38,
] ]
env: env:
DISTRO: ${{matrix.distro}} DISTRO: ${{matrix.distro}}
@ -75,7 +74,7 @@ jobs:
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.1.1
- name: Set up Python 3.10 - name: Set up Python 3.10
uses: actions/setup-python@v4.7.1 uses: actions/setup-python@v5.0.0
with: with:
python-version: "3.10" python-version: "3.10"

@ -21,20 +21,60 @@
TestAPIAvailability() { TestAPIAvailability() {
# as we are running locally, we can get the port value from FTL directly # as we are running locally, we can get the port value from FTL directly
PORT="$(pihole-FTL --config webserver.port)" local chaos_api_list availabilityResonse
PORT="${PORT%%,*}"
availabilityResonse=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${PORT}/api/auth") # Query the API URLs from FTL using CHAOS TXT local.api.ftl
# The result is a space-separated enumeration of full URLs
# e.g., "http://localhost:80/api/" "https://localhost:443/api/"
chaos_api_list="$(dig +short chaos txt local.api.ftl @127.0.0.1)"
# test if http status code was 200 (OK) or 401 (authentication required) # If the query was not successful, the variable is empty
if [ ! "${availabilityResonse}" = 200 ] && [ ! "${availabilityResonse}" = 401 ]; then if [ -z "${chaos_api_list}" ]; then
echo "API not available at: http://localhost:${PORT}/api" echo "API not available. Please check connectivity"
exit 1
fi
# Iterate over space-separated list of URLs
while [ -n "${chaos_api_list}" ]; do
# Get the first URL
API_URL="${chaos_api_list%% *}"
# Strip leading and trailing quotes
API_URL="${API_URL%\"}"
API_URL="${API_URL#\"}"
# Test if the API is available at this URL
availabilityResonse=$(curl -skS -o /dev/null -w "%{http_code}" "${API_URL}auth")
# Test if http status code was 200 (OK) or 401 (authentication required)
if [ ! "${availabilityResonse}" = 200 ] && [ ! "${availabilityResonse}" = 401 ]; then
# API is not available at this port/protocol combination
API_PORT=""
else
# API is available at this URL combination
break
fi
# Remove the first URL from the list
local last_api_list
last_api_list="${chaos_api_list}"
chaos_api_list="${chaos_api_list#* }"
# If the list did not change, we are at the last element
if [ "${last_api_list}" = "${chaos_api_list}" ]; then
# Remove the last element
chaos_api_list=""
fi
done
# if API_PORT is empty, no working API port was found
if [ -n "${API_PORT}" ]; then
echo "API not available at: ${API_URL}"
echo "Exiting." echo "Exiting."
exit 1 exit 1
fi fi
} }
Authenthication() { Authentication() {
# Try to authenticate # Try to authenticate
LoginAPI LoginAPI
@ -54,28 +94,27 @@ Authenthication() {
} }
LoginAPI() { LoginAPI() {
sessionResponse="$(curl --silent -X POST "http://localhost:${PORT}/api/auth" --user-agent "Pi-hole cli " --data "{\"password\":\"${password}\"}" )" sessionResponse="$(curl -skS -X POST "${API_URL}auth" --user-agent "Pi-hole cli " --data "{\"password\":\"${password}\"}" )"
if [ -z "${sessionResponse}" ]; then if [ -z "${sessionResponse}" ]; then
echo "No response from FTL server. Please check connectivity" echo "No response from FTL server. Please check connectivity"
exit 1 exit 1
fi fi
# obtain validity and session ID from session response # obtain validity and session ID from session response
validSession=$(echo "${sessionResponse}"| jq .session.valid 2>/dev/null) validSession=$(echo "${sessionResponse}"| jq .session.valid 2>/dev/null)
SID=$(echo "${sessionResponse}"| jq --raw-output .session.sid 2>/dev/null) SID=$(echo "${sessionResponse}"| jq --raw-output .session.sid 2>/dev/null)
} }
DeleteSession() { DeleteSession() {
# if a valid Session exists (no password required or successful authenthication) and # if a valid Session exists (no password required or successful Authentication) and
# SID is not null (successful authenthication only), delete the session # SID is not null (successful Authentication only), delete the session
if [ "${validSession}" = true ] && [ ! "${SID}" = null ]; then if [ "${validSession}" = true ] && [ ! "${SID}" = null ]; then
# Try to delete the session. Omit the output, but get the http status code # Try to delete the session. Omit the output, but get the http status code
deleteResponse=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE "http://localhost:${PORT}/api/auth" -H "Accept: application/json" -H "sid: ${SID}") deleteResponse=$(curl -skS -o /dev/null -w "%{http_code}" -X DELETE "${API_URL}auth" -H "Accept: application/json" -H "sid: ${SID}")
case "${deleteResponse}" in case "${deleteResponse}" in
"200") printf "%b" "A session that was not created cannot be deleted (e.g., empty API password).\n";; "204") printf "%b" "Session successfully deleted.\n";;
"401") printf "%b" "Logout attempt without a valid session. Unauthorized!\n";; "401") printf "%b" "Logout attempt without a valid session. Unauthorized!\n";;
"410") printf "%b" "Session successfully deleted.\n";;
esac; esac;
fi fi
@ -84,7 +123,7 @@ DeleteSession() {
GetFTLData() { GetFTLData() {
local data response status local data response status
# get the data from querying the API as well as the http status code # get the data from querying the API as well as the http status code
response=$(curl -s -w "%{http_code}" -X GET "http://localhost:${PORT}/api$1" -H "Accept: application/json" -H "sid: ${SID}" ) response=$(curl -skS -w "%{http_code}" -X GET "${API_URL}$1" -H "Accept: application/json" -H "sid: ${SID}" )
# status are the last 3 characters # status are the last 3 characters
status=$(printf %s "${response#"${response%???}"}") status=$(printf %s "${response#"${response%???}"}")
@ -93,7 +132,7 @@ GetFTLData() {
if [ "${status}" = 200 ]; then if [ "${status}" = 200 ]; then
# response OK # response OK
echo "${data}" printf %s "${data}"
elif [ "${status}" = 000 ]; then elif [ "${status}" = 000 ]; then
# connection lost # connection lost
echo "000" echo "000"

@ -18,14 +18,19 @@ upgrade_gravityDB(){
piholeDir="${2}" piholeDir="${2}"
auditFile="${piholeDir}/auditlog.list" auditFile="${piholeDir}/auditlog.list"
# Exit early if the database does not exist (e.g. in CI tests)
if [[ ! -f "${database}" ]]; then
return
fi
# Get database version # 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 if [[ "$version" == "1" ]]; then
# This migration script upgrades the gravity.db file by # This migration script upgrades the gravity.db file by
# adding the domain_audit table # adding the domain_audit table
echo -e " ${INFO} Upgrading gravity database from version 1 to 2" 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 version=2
# Store audit domains in database table # Store audit domains in database table
@ -40,28 +45,28 @@ upgrade_gravityDB(){
# renaming the regex table to regex_blacklist, and # renaming the regex table to regex_blacklist, and
# creating a new regex_whitelist table + corresponding linking table and views # creating a new regex_whitelist table + corresponding linking table and views
echo -e " ${INFO} Upgrading gravity database from version 2 to 3" 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 version=3
fi fi
if [[ "$version" == "3" ]]; then if [[ "$version" == "3" ]]; then
# This migration script unifies the formally separated domain # This migration script unifies the formally separated domain
# lists into a single table with a UNIQUE domain constraint # lists into a single table with a UNIQUE domain constraint
echo -e " ${INFO} Upgrading gravity database from version 3 to 4" 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 version=4
fi fi
if [[ "$version" == "4" ]]; then if [[ "$version" == "4" ]]; then
# This migration script upgrades the gravity and list views # This migration script upgrades the gravity and list views
# implementing necessary changes for per-client blocking # implementing necessary changes for per-client blocking
echo -e " ${INFO} Upgrading gravity database from version 4 to 5" 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 version=5
fi fi
if [[ "$version" == "5" ]]; then if [[ "$version" == "5" ]]; then
# This migration script upgrades the adlist view # This migration script upgrades the adlist view
# to return an ID used in gravity.sh # to return an ID used in gravity.sh
echo -e " ${INFO} Upgrading gravity database from version 5 to 6" 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 version=6
fi fi
if [[ "$version" == "6" ]]; then if [[ "$version" == "6" ]]; then
@ -69,7 +74,7 @@ upgrade_gravityDB(){
# which is automatically associated to all clients not # which is automatically associated to all clients not
# having their own group assignments # having their own group assignments
echo -e " ${INFO} Upgrading gravity database from version 6 to 7" 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 version=7
fi fi
if [[ "$version" == "7" ]]; then if [[ "$version" == "7" ]]; then
@ -77,21 +82,21 @@ upgrade_gravityDB(){
# to ensure uniqueness on the group name # to ensure uniqueness on the group name
# We also add date_added and date_modified columns # We also add date_added and date_modified columns
echo -e " ${INFO} Upgrading gravity database from version 7 to 8" 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 version=8
fi fi
if [[ "$version" == "8" ]]; then if [[ "$version" == "8" ]]; then
# This migration fixes some issues that were introduced # This migration fixes some issues that were introduced
# in the previous migration script. # in the previous migration script.
echo -e " ${INFO} Upgrading gravity database from version 8 to 9" 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 version=9
fi fi
if [[ "$version" == "9" ]]; then if [[ "$version" == "9" ]]; then
# This migration drops unused tables and creates triggers to remove # This migration drops unused tables and creates triggers to remove
# obsolete groups assignments when the linked items are deleted # obsolete groups assignments when the linked items are deleted
echo -e " ${INFO} Upgrading gravity database from version 9 to 10" 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 version=10
fi fi
if [[ "$version" == "10" ]]; then if [[ "$version" == "10" ]]; then
@ -101,44 +106,57 @@ upgrade_gravityDB(){
# to keep the copying process generic (needs the same columns in both the # to keep the copying process generic (needs the same columns in both the
# source and the destination databases). # source and the destination databases).
echo -e " ${INFO} Upgrading gravity database from version 10 to 11" 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 version=11
fi fi
if [[ "$version" == "11" ]]; then if [[ "$version" == "11" ]]; then
# Rename group 0 from "Unassociated" to "Default" # Rename group 0 from "Unassociated" to "Default"
echo -e " ${INFO} Upgrading gravity database from version 11 to 12" 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 version=12
fi fi
if [[ "$version" == "12" ]]; then if [[ "$version" == "12" ]]; then
# Add column date_updated to adlist table # Add column date_updated to adlist table
echo -e " ${INFO} Upgrading gravity database from version 12 to 13" 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 version=13
fi fi
if [[ "$version" == "13" ]]; then if [[ "$version" == "13" ]]; then
# Add columns number and status to adlist table # Add columns number and status to adlist table
echo -e " ${INFO} Upgrading gravity database from version 13 to 14" 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 version=14
fi fi
if [[ "$version" == "14" ]]; then if [[ "$version" == "14" ]]; then
# Changes the vw_adlist created in 5_to_6 # Changes the vw_adlist created in 5_to_6
echo -e " ${INFO} Upgrading gravity database from version 14 to 15" 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 version=15
fi fi
if [[ "$version" == "15" ]]; then if [[ "$version" == "15" ]]; then
# Add column abp_entries to adlist table # Add column abp_entries to adlist table
echo -e " ${INFO} Upgrading gravity database from version 15 to 16" echo -e " ${INFO} Upgrading gravity database from version 15 to 16"
pihole-FTL sqlite3 "${database}" < "${scriptPath}/15_to_16.sql" pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/15_to_16.sql"
version=16 version=16
fi fi
if [[ "$version" == "16" ]]; then if [[ "$version" == "16" ]]; then
# Add antigravity table # Add antigravity table
# Add column type to adlist table (to support adlist types) # Add column type to adlist table (to support adlist types)
echo -e " ${INFO} Upgrading gravity database from version 16 to 17" echo -e " ${INFO} Upgrading gravity database from version 16 to 17"
pihole-FTL sqlite3 "${database}" < "${scriptPath}/16_to_17.sql" pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/16_to_17.sql"
version=17 version=17
fi fi
if [[ "$version" == "17" ]]; then
# Add adlist.id to vw_gravity and vw_antigravity
echo -e " ${INFO} Upgrading gravity database from version 17 to 18"
pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/17_to_18.sql"
version=18
fi
if [[ "$version" == "18" ]]; then
# Modify DELETE triggers to delete BEFORE instead of AFTER to prevent
# foreign key constraint violations
echo -e " ${INFO} Upgrading gravity database from version 18 to 19"
pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/18_to_19.sql"
version=19
fi
} }

@ -0,0 +1,25 @@
.timeout 30000
PRAGMA FOREIGN_KEYS=OFF;
BEGIN TRANSACTION;
DROP VIEW vw_gravity;
CREATE VIEW vw_gravity AS SELECT domain, adlist.id AS adlist_id, adlist_by_group.group_id AS group_id
FROM gravity
LEFT JOIN adlist_by_group ON adlist_by_group.adlist_id = gravity.adlist_id
LEFT JOIN adlist ON adlist.id = gravity.adlist_id
LEFT JOIN "group" ON "group".id = adlist_by_group.group_id
WHERE adlist.enabled = 1 AND (adlist_by_group.group_id IS NULL OR "group".enabled = 1);
DROP VIEW vw_antigravity;
CREATE VIEW vw_antigravity AS SELECT domain, adlist.id AS adlist_id, adlist_by_group.group_id AS group_id
FROM antigravity
LEFT JOIN adlist_by_group ON adlist_by_group.adlist_id = antigravity.adlist_id
LEFT JOIN adlist ON adlist.id = antigravity.adlist_id
LEFT JOIN "group" ON "group".id = adlist_by_group.group_id
WHERE adlist.enabled = 1 AND (adlist_by_group.group_id IS NULL OR "group".enabled = 1) AND adlist.type = 1;
UPDATE info SET value = 18 WHERE property = 'version';
COMMIT;

@ -0,0 +1,27 @@
.timeout 30000
PRAGMA FOREIGN_KEYS=OFF;
BEGIN TRANSACTION;
DROP TRIGGER tr_domainlist_delete;
CREATE TRIGGER tr_domainlist_delete BEFORE DELETE ON domainlist
BEGIN
DELETE FROM domainlist_by_group WHERE domainlist_id = OLD.id;
END;
DROP TRIGGER tr_adlist_delete;
CREATE TRIGGER tr_adlist_delete BEFORE DELETE ON adlist
BEGIN
DELETE FROM adlist_by_group WHERE adlist_id = OLD.id;
END;
DROP TRIGGER tr_client_delete;
CREATE TRIGGER tr_client_delete BEFORE DELETE ON client
BEGIN
DELETE FROM client_by_group WHERE client_id = OLD.id;
END;
UPDATE info SET value = 19 WHERE property = 'version';
COMMIT;

@ -150,18 +150,18 @@ AddDomain() {
domain="$1" domain="$1"
# Is the domain in the list we want to add it to? # 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}")" requestedListname="$(GetListnameFromTypeId "${typeId}")"
if [[ "${num}" -ne 0 ]]; then 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 [[ "${existingTypeId}" == "${typeId}" ]]; then
if [[ "${verbose}" == true ]]; then if [[ "${verbose}" == true ]]; then
echo -e " ${INFO} ${1} already exists in ${requestedListname}, no need to add!" echo -e " ${INFO} ${1} already exists in ${requestedListname}, no need to add!"
fi fi
else else
existingListname="$(GetListnameFromTypeId "${existingTypeId}")" 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 if [[ "${verbose}" == true ]]; then
echo -e " ${INFO} ${1} already exists in ${existingListname}, it has been moved to ${requestedListname}!" echo -e " ${INFO} ${1} already exists in ${existingListname}, it has been moved to ${requestedListname}!"
fi fi
@ -177,10 +177,10 @@ AddDomain() {
# Insert only the domain here. The enabled and date_added fields will be filled # Insert only the domain here. The enabled and date_added fields will be filled
# with their default values (enabled = true, date_added = current timestamp) # with their default values (enabled = true, date_added = current timestamp)
if [[ -z "${comment}" ]]; then 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 else
# also add comment when variable has been set through the "--comment" option # 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 fi
} }
@ -189,7 +189,7 @@ RemoveDomain() {
domain="$1" domain="$1"
# Is the domain in the list we want to remove it from? # 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}")" requestedListname="$(GetListnameFromTypeId "${typeId}")"
@ -206,14 +206,14 @@ RemoveDomain() {
fi fi
reload=true reload=true
# Remove it from the current list # 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() { Displaylist() {
local count num_pipes domain enabled status nicedate requestedListname local count num_pipes domain enabled status nicedate requestedListname
requestedListname="$(GetListnameFromTypeId "${typeId}")" 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 if [[ -z $data ]]; then
echo -e "Not showing empty list" echo -e "Not showing empty list"
@ -251,10 +251,10 @@ Displaylist() {
} }
NukeList() { 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}")" listname="$(GetListnameFromTypeId "${typeId}")"
if [ "$count" -gt 0 ];then 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}" echo " ${TICK} Removed ${count} domain(s) from the ${listname}"
else else
echo " ${INFO} ${listname} already empty. Nothing to do!" echo " ${INFO} ${listname} already empty. Nothing to do!"

@ -39,7 +39,7 @@ flushARP(){
# Truncate network_addresses table in pihole-FTL.db # Truncate network_addresses table in pihole-FTL.db
# This needs to be done before we can truncate the network table due to # This needs to be done before we can truncate the network table due to
# foreign key constraints # 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 -e "${OVER} ${CROSS} Failed to truncate network_addresses table"
echo " Database location: ${DBFILE}" echo " Database location: ${DBFILE}"
echo " Output: ${output}" echo " Output: ${output}"
@ -47,7 +47,7 @@ flushARP(){
fi fi
# Truncate network table in pihole-FTL.db # 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 -e "${OVER} ${CROSS} Failed to truncate network table"
echo " Database location: ${DBFILE}" echo " Database location: ${DBFILE}"
echo " Output: ${output}" echo " Output: ${output}"

@ -164,7 +164,9 @@ checkout() {
path="${2}/${binary}" path="${2}/${binary}"
oldbranch="$(pihole-FTL -b)" oldbranch="$(pihole-FTL -b)"
if check_download_exists "$path"; then check_download_exists "$path"
local ret=$?
if [ $ret -eq 0 ]; then
echo " ${TICK} Branch ${2} exists" echo " ${TICK} Branch ${2} exists"
echo "${2}" > /etc/pihole/ftlbranch echo "${2}" > /etc/pihole/ftlbranch
chmod 644 /etc/pihole/ftlbranch chmod 644 /etc/pihole/ftlbranch
@ -175,11 +177,19 @@ checkout() {
# Update local and remote versions via updatechecker # Update local and remote versions via updatechecker
/opt/pihole/updatecheck.sh /opt/pihole/updatecheck.sh
else else
echo " ${CROSS} Requested branch \"${2}\" is not available" if [[ $ret -eq 1 ]]; then
ftlbranches=( $(git ls-remote https://github.com/pi-hole/ftl | grep 'heads' | sed 's/refs\/heads\///;s/ //g' | awk '{print $2}') ) echo " ${CROSS} Requested branch \"${2}\" is not available"
echo -e " ${INFO} Available branches for FTL are:" ftlbranches=( $(git ls-remote https://github.com/pi-hole/ftl | grep 'heads' | sed 's/refs\/heads\///;s/ //g' | awk '{print $2}') )
for e in "${ftlbranches[@]}"; do echo " - $e"; done echo -e " ${INFO} Available branches for FTL are:"
exit 1 for e in "${ftlbranches[@]}"; do echo " - $e"; done
exit 1
elif [[ $ret -eq 2 ]]; then
printf " %b Unable to download from ftl.pi-hole.net. Please check your Internet connection and try again later.\\n" "${CROSS}"
exit 1
else
printf " %b Unknown error. Please contact Pi-hole Support\\n" "${CROSS}"
exit 1
fi
fi fi
else else

@ -74,7 +74,6 @@ PIHOLE_CRON_FILE="${CRON_D_DIRECTORY}/pihole"
PIHOLE_INSTALL_LOG_FILE="${PIHOLE_DIRECTORY}/install.log" PIHOLE_INSTALL_LOG_FILE="${PIHOLE_DIRECTORY}/install.log"
PIHOLE_RAW_BLOCKLIST_FILES="${PIHOLE_DIRECTORY}/list.*" PIHOLE_RAW_BLOCKLIST_FILES="${PIHOLE_DIRECTORY}/list.*"
PIHOLE_LOCAL_HOSTS_FILE="${PIHOLE_DIRECTORY}/local.list"
PIHOLE_LOGROTATE_FILE="${PIHOLE_DIRECTORY}/logrotate" PIHOLE_LOGROTATE_FILE="${PIHOLE_DIRECTORY}/logrotate"
PIHOLE_FTL_CONF_FILE="${PIHOLE_DIRECTORY}/pihole.toml" PIHOLE_FTL_CONF_FILE="${PIHOLE_DIRECTORY}/pihole.toml"
PIHOLE_VERSIONS_FILE="${PIHOLE_DIRECTORY}/versions" PIHOLE_VERSIONS_FILE="${PIHOLE_DIRECTORY}/versions"
@ -547,17 +546,24 @@ ping_gateway() {
ping_ipv4_or_ipv6 "${protocol}" ping_ipv4_or_ipv6 "${protocol}"
# Check if we are using IPv4 or IPv6 # Check if we are using IPv4 or IPv6
# Find the default gateways using IPv4 or IPv6 # Find the default gateways using IPv4 or IPv6
local gateway local gateway gateway_addr gateway_iface
log_write "${INFO} Default IPv${protocol} gateway(s):" log_write "${INFO} Default IPv${protocol} gateway(s):"
while IFS= read -r gateway; do while IFS= read -r gateway; do
log_write " ${gateway}" log_write " $(cut -d ' ' -f 3 <<< "${gateway}")%$(cut -d ' ' -f 5 <<< "${gateway}")"
done < <(ip -"${protocol}" route | grep default | cut -d ' ' -f 3) done < <(ip -"${protocol}" route | grep default)
gateway=$(ip -"${protocol}" route | grep default | cut -d ' ' -f 3 | head -n 1) gateway_addr=$(ip -"${protocol}" route | grep default | cut -d ' ' -f 3 | head -n 1)
gateway_iface=$(ip -"${protocol}" route | grep default | cut -d ' ' -f 5 | head -n 1)
# If there was at least one gateway # If there was at least one gateway
if [ -n "${gateway}" ]; then if [ -n "${gateway_addr}" ]; then
# Append the interface to the gateway address if it is a link-local address
if [[ "${gateway_addr}" =~ ^fe80 ]]; then
gateway="${gateway_addr}%${gateway_iface}"
else
gateway="${gateway_addr}"
fi
# Let the user know we will ping the gateway for a response # Let the user know we will ping the gateway for a response
log_write " * Pinging first gateway ${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, # Try to quietly ping the gateway 3 times, with a timeout of 3 seconds, using numeric output only,
@ -718,7 +724,7 @@ dig_at() {
# This helps emulate queries to different domains that a user might query # 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 # It will also give extra assurance that Pi-hole is correctly resolving and blocking domains
local random_url local random_url
random_url=$(pihole-FTL sqlite3 "${PIHOLE_GRAVITY_DB_FILE}" "SELECT domain FROM vw_gravity WHERE domain not like '||%^' 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 # Fallback if no non-ABP style domains were found
if [ -z "${random_url}" ]; then if [ -z "${random_url}" ]; then
random_url="flurry.com" random_url="flurry.com"
@ -757,24 +763,29 @@ dig_at() {
# Removes CIDR and everything thereafter (e.g., scope properties) # Removes CIDR and everything thereafter (e.g., scope properties)
addresses="$(ip address show dev "${iface}" | sed "/${sed_selector} /!d;s/^.*${sed_selector} //g;s/\/.*$//g;")" addresses="$(ip address show dev "${iface}" | sed "/${sed_selector} /!d;s/^.*${sed_selector} //g;s/\/.*$//g;")"
if [ -n "${addresses}" ]; then if [ -n "${addresses}" ]; then
while IFS= read -r local_address ; do while IFS= read -r local_address ; do
# If ${local_address} is an IPv6 link-local address, append the interface name to it
if [[ "${local_address}" =~ ^fe80 ]]; then
local_address="${local_address}%${iface}"
fi
# Check if Pi-hole can use itself to block a domain # Check if Pi-hole can use itself to block a domain
if local_dig="$(dig +tries=1 +time=2 -"${protocol}" "${random_url}" @"${local_address}" "${record_type}")"; then if local_dig="$(dig +tries=1 +time=2 -"${protocol}" "${random_url}" @"${local_address}" "${record_type}")"; then
# If it can, show success # If it can, show success
if [[ "${local_dig}" == *"status: NOERROR"* ]]; then if [[ "${local_dig}" == *"status: NOERROR"* ]]; then
local_dig="NOERROR" local_dig="NOERROR"
elif [[ "${local_dig}" == *"status: NXDOMAIN"* ]]; then elif [[ "${local_dig}" == *"status: NXDOMAIN"* ]]; then
local_dig="NXDOMAIN" local_dig="NXDOMAIN"
else else
# Extract the first entry in the answer section from dig's output, # Extract the first entry in the answer section from dig's output,
# replacing any multiple spaces and tabs with a single space # replacing any multiple spaces and tabs with a single space
local_dig="$(echo "${local_dig}" | grep -A1 "ANSWER SECTION" | grep -v "ANSWER SECTION" | tr -s " \t" " ")" local_dig="$(echo "${local_dig}" | grep -A1 "ANSWER SECTION" | grep -v "ANSWER SECTION" | tr -s " \t" " ")"
fi fi
log_write "${TICK} ${random_url} ${COL_GREEN}is ${local_dig}${COL_NC} on ${COL_CYAN}${iface}${COL_NC} (${COL_CYAN}${local_address}${COL_NC})" log_write "${TICK} ${random_url} ${COL_GREEN}is ${local_dig}${COL_NC} on ${COL_CYAN}${iface}${COL_NC} (${COL_CYAN}${local_address}${COL_NC})"
else else
# Otherwise, show a failure # Otherwise, show a failure
log_write "${CROSS} ${COL_RED}Failed to resolve${COL_NC} ${random_url} on ${COL_RED}${iface}${COL_NC} (${COL_RED}${local_address}${COL_NC})" log_write "${CROSS} ${COL_RED}Failed to resolve${COL_NC} ${random_url} on ${COL_RED}${iface}${COL_NC} (${COL_RED}${local_address}${COL_NC})"
fi fi
done <<< "${addresses}" done <<< "${addresses}"
else else
log_write "${TICK} No IPv${protocol} address available on ${COL_CYAN}${iface}${COL_NC}" log_write "${TICK} No IPv${protocol} address available on ${COL_CYAN}${iface}${COL_NC}"
@ -1064,7 +1075,7 @@ show_db_entries() {
IFS=$'\r\n' IFS=$'\r\n'
local entries=() local entries=()
mapfile -t entries < <(\ mapfile -t entries < <(\
pihole-FTL sqlite3 "${PIHOLE_GRAVITY_DB_FILE}" \ pihole-FTL sqlite3 -ni "${PIHOLE_GRAVITY_DB_FILE}" \
-cmd ".headers on" \ -cmd ".headers on" \
-cmd ".mode column" \ -cmd ".mode column" \
-cmd ".width ${widths}" \ -cmd ".width ${widths}" \
@ -1089,7 +1100,7 @@ show_FTL_db_entries() {
IFS=$'\r\n' IFS=$'\r\n'
local entries=() local entries=()
mapfile -t entries < <(\ mapfile -t entries < <(\
pihole-FTL sqlite3 "${PIHOLE_FTL_DB_FILE}" \ pihole-FTL sqlite3 -ni "${PIHOLE_FTL_DB_FILE}" \
-cmd ".headers on" \ -cmd ".headers on" \
-cmd ".mode column" \ -cmd ".mode column" \
-cmd ".width ${widths}" \ -cmd ".width ${widths}" \
@ -1155,7 +1166,7 @@ analyze_gravity_list() {
fi fi
show_db_entries "Info table" "SELECT property,value FROM info" "20 40" 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}")" gravity_updated="$(date -d @"${gravity_updated_raw}")"
log_write " Last gravity run finished at: ${COL_CYAN}${gravity_updated}${COL_NC}" log_write " Last gravity run finished at: ${COL_CYAN}${gravity_updated}${COL_NC}"
log_write "" log_write ""
@ -1163,7 +1174,7 @@ analyze_gravity_list() {
OLD_IFS="$IFS" OLD_IFS="$IFS"
IFS=$'\r\n' IFS=$'\r\n'
local gravity_sample=() 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}" log_write " ${COL_CYAN}----- First 10 Gravity Domains -----${COL_NC}"
for line in "${gravity_sample[@]}"; do for line in "${gravity_sample[@]}"; do
@ -1195,7 +1206,7 @@ database_integrity_check(){
log_write "${INFO} Checking foreign key constraints of ${database} ... (this can take several minutes)" log_write "${INFO} Checking foreign key constraints of ${database} ... (this can take several minutes)"
unset result unset result
result="$(pihole-FTL sqlite3 "${database}" -cmd ".headers on" -cmd ".mode column" "PRAGMA foreign_key_check" 2>&1 & spinner)" result="$(pihole-FTL sqlite3 -ni "${database}" -cmd ".headers on" -cmd ".mode column" "PRAGMA foreign_key_check" 2>&1 & spinner)"
if [[ -z ${result} ]]; then if [[ -z ${result} ]]; then
log_write "${TICK} No foreign key errors in ${database}" log_write "${TICK} No foreign key errors in ${database}"
else else

@ -63,7 +63,7 @@ else
fi fi
fi fi
# Delete most recent 24 hours from FTL's database, leave even older data intact (don't wipe out all history) # 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 # Restart pihole-FTL to force reloading history
sudo pihole restartdns sudo pihole restartdns

@ -27,7 +27,7 @@ colfile="/opt/pihole/COL_TABLE"
# Source api functions # Source api functions
. "${PI_HOLE_INSTALL_DIR}/api.sh" . "${PI_HOLE_INSTALL_DIR}/api.sh"
Help(){ Help() {
echo "Usage: pihole -q [option] <domain> echo "Usage: pihole -q [option] <domain>
Example: 'pihole -q --partial domain.com' Example: 'pihole -q --partial domain.com'
Query the adlists for a specified domain Query the adlists for a specified domain
@ -36,29 +36,28 @@ Options:
--partial Search the adlists for partially matching domains --partial Search the adlists for partially matching domains
--all Return all query matches within the adlists --all Return all query matches within the adlists
-h, --help Show this help dialog" -h, --help Show this help dialog"
exit 0 exit 0
} }
GenerateOutput() {
GenerateOutput(){
local data gravity_data lists_data num_gravity num_lists search_type_str local data gravity_data lists_data num_gravity num_lists search_type_str
local gravity_data_csv lists_data_csv line current_domain local gravity_data_csv lists_data_csv line current_domain url type color
data="${1}" data="${1}"
# construct a new json for the list results where each object contains the domain and the related type # construct a new json for the list results where each object contains the domain and the related type
lists_data=$(echo "${data}" | jq '.search.domains | [.[] | {domain: .domain, type: .type}]') lists_data=$(printf %s "${data}" | jq '.search.domains | [.[] | {domain: .domain, type: .type}]')
# construct a new json for the gravity results where each object contains the adlist URL and the related domains # construct a new json for the gravity results where each object contains the adlist URL and the related domains
gravity_data=$(echo "${data}" | jq '.search.gravity | group_by(.address) | map({ address: (.[0].address), domains: [.[] | .domain] })') gravity_data=$(printf %s "${data}" | jq '.search.gravity | group_by(.address,.type) | map({ address: (.[0].address), type: (.[0].type), domains: [.[] | .domain] })')
# number of objects in each json # number of objects in each json
num_gravity=$(echo "${gravity_data}" | jq length ) num_gravity=$(printf %s "${gravity_data}" | jq length)
num_lists=$(echo "${lists_data}" | jq length ) num_lists=$(printf %s "${lists_data}" | jq length)
if [ "${partial}" = true ]; then if [ "${partial}" = true ]; then
search_type_str="partially" search_type_str="partially"
else else
search_type_str="exactly" search_type_str="exactly"
fi fi
# Results from allow/deny list # Results from allow/deny list
@ -66,7 +65,7 @@ GenerateOutput(){
if [ "${num_lists}" -gt 0 ]; then if [ "${num_lists}" -gt 0 ]; then
# Convert the data to a csv, each line is a "domain,type" string # Convert the data to a csv, each line is a "domain,type" string
# not using jq's @csv here as it quotes each value individually # not using jq's @csv here as it quotes each value individually
lists_data_csv=$(echo "${lists_data}" | jq --raw-output '.[] | [.domain, .type] | join(",")' ) lists_data_csv=$(printf %s "${lists_data}" | jq --raw-output '.[] | [.domain, .type] | join(",")')
# Generate output for each csv line, separating line in a domain and type substring at the ',' # Generate output for each csv line, separating line in a domain and type substring at the ','
echo "${lists_data_csv}" | while read -r line; do echo "${lists_data_csv}" | while read -r line; do
@ -79,18 +78,30 @@ GenerateOutput(){
if [ "${num_gravity}" -gt 0 ]; then if [ "${num_gravity}" -gt 0 ]; then
# Convert the data to a csv, each line is a "URL,domain,domain,...." string # Convert the data to a csv, each line is a "URL,domain,domain,...." string
# not using jq's @csv here as it quotes each value individually # not using jq's @csv here as it quotes each value individually
gravity_data_csv=$(echo "${gravity_data}" | jq --raw-output '.[] | [.address, .domains[]] | join(",")' ) gravity_data_csv=$(printf %s "${gravity_data}" | jq --raw-output '.[] | [.address, .type, .domains[]] | join(",")')
# Generate line-by-line output for each csv line # Generate line-by-line output for each csv line
echo "${gravity_data_csv}" | while read -r line; do echo "${gravity_data_csv}" | while read -r line; do
# Get first part of the line, the URL
url=${line%%,*}
# cut off URL, leaving "type,domain,domain,...."
line=${line#*,}
type=${line%%,*}
# type == "block" -> red, type == "allow" -> green
if [ "${type}" = "block" ]; then
color="${COL_RED}"
else
color="${COL_GREEN}"
fi
# print adlist URL # print adlist URL
printf "%s\n\n" " - ${COL_BLUE}${line%%,*}${COL_NC}" printf "%s (%s)\n\n" " - ${COL_BLUE}${url}${COL_NC}" "${color}${type}${COL_NC}"
# cut off URL, leaving "domain,domain,...." # cut off type, leaving "domain,domain,...."
line=${line#*,} line=${line#*,}
# print each domain and remove it from the string until nothing is left # print each domain and remove it from the string until nothing is left
while [ ${#line} -gt 0 ]; do while [ ${#line} -gt 0 ]; do
current_domain=${line%%,*} current_domain=${line%%,*}
printf ' - %s\n' "${COL_GREEN}${current_domain}${COL_NC}" printf ' - %s\n' "${COL_GREEN}${current_domain}${COL_NC}"
# we need to remove the current_domain and the comma in two steps because # we need to remove the current_domain and the comma in two steps because
@ -103,17 +114,17 @@ GenerateOutput(){
fi fi
} }
Main(){ Main() {
local data local data
if [ -z "${domain}" ]; then if [ -z "${domain}" ]; then
echo "No domain specified"; exit 1 echo "No domain specified"
exit 1
fi fi
# domains are lowercased and converted to punycode by FTL since # domains are lowercased and converted to punycode by FTL since
# https://github.com/pi-hole/FTL/pull/1715 # https://github.com/pi-hole/FTL/pull/1715
# no need to do it here # no need to do it here
# Test if the authentication endpoint is available # Test if the authentication endpoint is available
TestAPIAvailability TestAPIAvailability
@ -121,14 +132,14 @@ Main(){
# or b) for the /search endpoint (webserver.api.searchAPIauth) no authentication is required. # or b) for the /search endpoint (webserver.api.searchAPIauth) no authentication is required.
# Therefore, we try to query directly without authentication but do authenticat if 401 is returned # Therefore, we try to query directly without authentication but do authenticat if 401 is returned
data=$(GetFTLData "/search/${domain}?N=${max_results}&partial=${partial}") data=$(GetFTLData "search/${domain}?N=${max_results}&partial=${partial}")
if [ "${data}" = 401 ]; then if [ "${data}" = 401 ]; then
# Unauthenticated, so authenticate with the FTL server required # Unauthenticated, so authenticate with the FTL server required
Authenthication Authentication
# send query again # send query again
data=$(GetFTLData "/search/${domain}?N=${max_results}&partial=${partial}") data=$(GetFTLData "search/${domain}?N=${max_results}&partial=${partial}")
fi fi
GenerateOutput "${data}" GenerateOutput "${data}"
@ -137,13 +148,13 @@ Main(){
# Process all options (if present) # Process all options (if present)
while [ "$#" -gt 0 ]; do while [ "$#" -gt 0 ]; do
case "$1" in case "$1" in
"-h" | "--help" ) Help;; "-h" | "--help") Help ;;
"--partial" ) partial="true";; "--partial") partial="true" ;;
"--all" ) max_results=10000;; # hard-coded FTL limit "--all") max_results=10000 ;; # hard-coded FTL limit
* ) domain=$1;; *) domain=$1 ;;
esac esac
shift shift
done done
Main "${domain}" Main "${domain}"

@ -144,7 +144,7 @@ main() {
local binary local binary
binary="pihole-FTL${funcOutput##*pihole-FTL}" #binary name will be the last line of the output of get_binary_name (it always begins with pihole-FTL) binary="pihole-FTL${funcOutput##*pihole-FTL}" #binary name will be the last line of the output of get_binary_name (it always begins with pihole-FTL)
if FTLcheckUpdate "${binary}" > /dev/null; then if FTLcheckUpdate "${binary}"; then
FTL_update=true FTL_update=true
echo -e " ${INFO} FTL:\\t\\t${COL_YELLOW}update available${COL_NC}" echo -e " ${INFO} FTL:\\t\\t${COL_YELLOW}update available${COL_NC}"
else else
@ -155,8 +155,13 @@ main() {
2) 2)
echo -e " ${INFO} FTL:\\t\\t${COL_LIGHT_RED}Branch is not available.${COL_NC}\\n\\t\\t\\tUse ${COL_LIGHT_GREEN}pihole checkout ftl [branchname]${COL_NC} to switch to a valid branch." echo -e " ${INFO} FTL:\\t\\t${COL_LIGHT_RED}Branch is not available.${COL_NC}\\n\\t\\t\\tUse ${COL_LIGHT_GREEN}pihole checkout ftl [branchname]${COL_NC} to switch to a valid branch."
;; ;;
3)
echo -e " ${INFO} FTL:\\t\\t${COL_LIGHT_RED}Something has gone wrong, cannot reach download server${COL_NC}"
exit 1
;;
*) *)
echo -e " ${INFO} FTL:\\t\\t${COL_LIGHT_RED}Something has gone wrong, contact support${COL_NC}" echo -e " ${INFO} FTL:\\t\\t${COL_LIGHT_RED}Something has gone wrong, contact support${COL_NC}"
exit 1
esac esac
FTL_update=false FTL_update=false
fi fi

@ -10,27 +10,31 @@
function get_local_branch() { function get_local_branch() {
# Return active branch # Return active branch
cd "${1}" 2> /dev/null || return 1 cd "${1}" 2>/dev/null || return 1
git rev-parse --abbrev-ref HEAD || return 1 git rev-parse --abbrev-ref HEAD || return 1
} }
function get_local_version() { function get_local_version() {
# Return active version # Return active version
cd "${1}" 2> /dev/null || return 1 cd "${1}" 2>/dev/null || return 1
git describe --tags --always 2> /dev/null || return 1 git describe --tags --always 2>/dev/null || return 1
} }
function get_local_hash() { function get_local_hash() {
cd "${1}" 2> /dev/null || return 1 cd "${1}" 2>/dev/null || return 1
git rev-parse --short=8 HEAD || return 1 git rev-parse --short=8 HEAD || return 1
} }
function get_remote_version() { function get_remote_version() {
curl -s "https://api.github.com/repos/pi-hole/${1}/releases/latest" 2> /dev/null | jq --raw-output .tag_name || return 1 # if ${2} is = "master" we need to use the "latest" endpoint, otherwise, we simply return null
if [[ "${2}" == "master" ]]; then
curl -s "https://api.github.com/repos/pi-hole/${1}/releases/latest" 2>/dev/null | jq --raw-output .tag_name || return 1
else
echo "null"
fi
} }
function get_remote_hash() {
function get_remote_hash(){
git ls-remote "https://github.com/pi-hole/${1}" --tags "${2}" | awk '{print substr($0, 1,8);}' || return 1 git ls-remote "https://github.com/pi-hole/${1}" --tags "${2}" | awk '{print substr($0, 1,8);}' || return 1
} }
@ -52,16 +56,15 @@ chmod 644 "${VERSION_FILE}"
DOCKER_TAG=$(cat /pihole.docker.tag 2>/dev/null) DOCKER_TAG=$(cat /pihole.docker.tag 2>/dev/null)
regex='^([0-9]+\.){1,2}(\*|[0-9]+)(-.*)?$|(^nightly$)|(^dev.*$)' regex='^([0-9]+\.){1,2}(\*|[0-9]+)(-.*)?$|(^nightly$)|(^dev.*$)'
if [[ ! "${DOCKER_TAG}" =~ $regex ]]; then if [[ ! "${DOCKER_TAG}" =~ $regex ]]; then
# DOCKER_TAG does not match the pattern (see https://regex101.com/r/RsENuz/1), so unset it. # DOCKER_TAG does not match the pattern (see https://regex101.com/r/RsENuz/1), so unset it.
unset DOCKER_TAG unset DOCKER_TAG
fi fi
# used in cronjob # used in cronjob
if [[ "$1" == "reboot" ]]; then if [[ "$1" == "reboot" ]]; then
sleep 30 sleep 30
fi fi
# get Core versions # get Core versions
CORE_VERSION="$(get_local_version /etc/.pihole)" CORE_VERSION="$(get_local_version /etc/.pihole)"
@ -73,13 +76,12 @@ addOrEditKeyValPair "${VERSION_FILE}" "CORE_BRANCH" "${CORE_BRANCH}"
CORE_HASH="$(get_local_hash /etc/.pihole)" CORE_HASH="$(get_local_hash /etc/.pihole)"
addOrEditKeyValPair "${VERSION_FILE}" "CORE_HASH" "${CORE_HASH}" addOrEditKeyValPair "${VERSION_FILE}" "CORE_HASH" "${CORE_HASH}"
GITHUB_CORE_VERSION="$(get_remote_version pi-hole)" GITHUB_CORE_VERSION="$(get_remote_version pi-hole "${CORE_BRANCH}")"
addOrEditKeyValPair "${VERSION_FILE}" "GITHUB_CORE_VERSION" "${GITHUB_CORE_VERSION}" addOrEditKeyValPair "${VERSION_FILE}" "GITHUB_CORE_VERSION" "${GITHUB_CORE_VERSION}"
GITHUB_CORE_HASH="$(get_remote_hash pi-hole "${CORE_BRANCH}")" GITHUB_CORE_HASH="$(get_remote_hash pi-hole "${CORE_BRANCH}")"
addOrEditKeyValPair "${VERSION_FILE}" "GITHUB_CORE_HASH" "${GITHUB_CORE_HASH}" addOrEditKeyValPair "${VERSION_FILE}" "GITHUB_CORE_HASH" "${GITHUB_CORE_HASH}"
# get Web versions # get Web versions
WEB_VERSION="$(get_local_version /var/www/html/admin)" WEB_VERSION="$(get_local_version /var/www/html/admin)"
@ -91,7 +93,7 @@ addOrEditKeyValPair "${VERSION_FILE}" "WEB_BRANCH" "${WEB_BRANCH}"
WEB_HASH="$(get_local_hash /var/www/html/admin)" WEB_HASH="$(get_local_hash /var/www/html/admin)"
addOrEditKeyValPair "${VERSION_FILE}" "WEB_HASH" "${WEB_HASH}" addOrEditKeyValPair "${VERSION_FILE}" "WEB_HASH" "${WEB_HASH}"
GITHUB_WEB_VERSION="$(get_remote_version web)" GITHUB_WEB_VERSION="$(get_remote_version web "${WEB_BRANCH}")"
addOrEditKeyValPair "${VERSION_FILE}" "GITHUB_WEB_VERSION" "${GITHUB_WEB_VERSION}" addOrEditKeyValPair "${VERSION_FILE}" "GITHUB_WEB_VERSION" "${GITHUB_WEB_VERSION}"
GITHUB_WEB_HASH="$(get_remote_hash web "${WEB_BRANCH}")" GITHUB_WEB_HASH="$(get_remote_hash web "${WEB_BRANCH}")"
@ -108,13 +110,12 @@ addOrEditKeyValPair "${VERSION_FILE}" "FTL_BRANCH" "${FTL_BRANCH}"
FTL_HASH="$(pihole-FTL --hash)" FTL_HASH="$(pihole-FTL --hash)"
addOrEditKeyValPair "${VERSION_FILE}" "FTL_HASH" "${FTL_HASH}" addOrEditKeyValPair "${VERSION_FILE}" "FTL_HASH" "${FTL_HASH}"
GITHUB_FTL_VERSION="$(get_remote_version FTL)" GITHUB_FTL_VERSION="$(get_remote_version FTL "${FTL_BRANCH}")"
addOrEditKeyValPair "${VERSION_FILE}" "GITHUB_FTL_VERSION" "${GITHUB_FTL_VERSION}" addOrEditKeyValPair "${VERSION_FILE}" "GITHUB_FTL_VERSION" "${GITHUB_FTL_VERSION}"
GITHUB_FTL_HASH="$(get_remote_hash FTL "${FTL_BRANCH}")" GITHUB_FTL_HASH="$(get_remote_hash FTL "${FTL_BRANCH}")"
addOrEditKeyValPair "${VERSION_FILE}" "GITHUB_FTL_HASH" "${GITHUB_FTL_HASH}" addOrEditKeyValPair "${VERSION_FILE}" "GITHUB_FTL_HASH" "${GITHUB_FTL_HASH}"
# get Docker versions # get Docker versions
if [[ "${DOCKER_TAG}" ]]; then if [[ "${DOCKER_TAG}" ]]; then

@ -8,6 +8,10 @@
# This file is copyright under the latest version of the EUPL. # This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license. # Please see LICENSE file for your rights under this license.
# Ignore warning about `local` being undefinded in POSIX
# shellcheck disable=SC3043
# https://github.com/koalaman/shellcheck/wiki/SC3043#exceptions
# Source the versions file poupulated by updatechecker.sh # Source the versions file poupulated by updatechecker.sh
cachedVersions="/etc/pihole/versions" cachedVersions="/etc/pihole/versions"
@ -21,118 +25,34 @@ else
. "$cachedVersions" . "$cachedVersions"
fi fi
getLocalVersion() { main() {
case ${1} in local details
"Pi-hole" ) echo "${CORE_VERSION:=N/A}";; details=false
"web" ) echo "${WEB_VERSION:=N/A}";;
"FTL" ) echo "${FTL_VERSION:=N/A}";;
esac
}
getLocalHash() {
case ${1} in
"Pi-hole" ) echo "${CORE_HASH:=N/A}";;
"web" ) echo "${WEB_HASH:=N/A}";;
"FTL" ) echo "${FTL_HASH:=N/A}";;
esac
}
getRemoteHash(){
case ${1} in
"Pi-hole" ) echo "${GITHUB_CORE_HASH:=N/A}";;
"web" ) echo "${GITHUB_WEB_HASH:=N/A}";;
"FTL" ) echo "${GITHUB_FTL_HASH:=N/A}";;
esac
}
getRemoteVersion(){
case ${1} in
"Pi-hole" ) echo "${GITHUB_CORE_VERSION:=N/A}";;
"web" ) echo "${GITHUB_WEB_VERSION:=N/A}";;
"FTL" ) echo "${GITHUB_FTL_VERSION:=N/A}";;
esac
}
getLocalBranch(){
case ${1} in
"Pi-hole" ) echo "${CORE_BRANCH:=N/A}";;
"web" ) echo "${WEB_BRANCH:=N/A}";;
"FTL" ) echo "${FTL_BRANCH:=N/A}";;
esac
}
versionOutput() {
[ "$2" = "-c" ] || [ "$2" = "--current" ] || [ -z "$2" ] && current=$(getLocalVersion "${1}") && branch=$(getLocalBranch "${1}") # Automatically show detailed information if
[ "$2" = "-l" ] || [ "$2" = "--latest" ] || [ -z "$2" ] && latest=$(getRemoteVersion "${1}") # at least one of the components is not on master branch
if [ "$2" = "--hash" ]; then if [ ! "${CORE_BRANCH}" = "master" ] || [ ! "${WEB_BRANCH}" = "master" ] || [ ! "${FTL_BRANCH}" = "master" ]; then
[ "$3" = "-c" ] || [ "$3" = "--current" ] || [ -z "$3" ] && curHash=$(getLocalHash "${1}") && branch=$(getLocalBranch "${1}") details=true
[ "$3" = "-l" ] || [ "$3" = "--latest" ] || [ -z "$3" ] && latHash=$(getRemoteHash "${1}") && branch=$(getLocalBranch "${1}")
fi fi
# We do not want to show the branch name when we are on master, if [ "${details}" = true ]; then
# blank out the variable in this case echo "Core"
if [ "$branch" = "master" ]; then echo " Version is ${CORE_VERSION:=N/A} (Latest: ${GITHUB_CORE_VERSION:=N/A})"
branch="" echo " Branch is ${CORE_BRANCH:=N/A}"
echo " Hash is ${CORE_HASH:=N/A} (Latest: ${GITHUB_CORE_HASH:=N/A})"
echo "Web"
echo " Version is ${WEB_VERSION:=N/A} (Latest: ${GITHUB_WEB_VERSION:=N/A})"
echo " Branch is ${WEB_BRANCH:=N/A}"
echo " Hash is ${WEB_HASH:=N/A} (Latest: ${GITHUB_WEB_HASH:=N/A})"
echo "FTL"
echo " Version is ${FTL_VERSION:=N/A} (Latest: ${GITHUB_FTL_VERSION:=N/A})"
echo " Branch is ${FTL_BRANCH:=N/A}"
echo " Hash is ${FTL_HASH:=N/A} (Latest: ${GITHUB_FTL_HASH:=N/A})"
else else
branch="$branch " echo "Core version is ${CORE_VERSION:=N/A} (Latest: ${GITHUB_CORE_VERSION:=N/A})"
echo "Web version is ${WEB_VERSION:=N/A} (Latest: ${GITHUB_WEB_VERSION:=N/A})"
echo "FTL version is ${FTL_VERSION:=N/A} (Latest: ${GITHUB_FTL_VERSION:=N/A})"
fi 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 [ -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"
}
errorOutput() {
echo " Invalid Option! Try 'pihole -v --help' for more information."
exit 1
}
defaultOutput() {
versionOutput "Pi-hole" "$@"
versionOutput "web" "$@"
versionOutput "FTL" "$@"
}
helpFunc() {
echo "Usage: pihole -v [repo | option] [option]
Example: 'pihole -v -p -l'
Show Pi-hole, Admin Console & FTL versions
Repositories:
-p, --pihole Only retrieve info regarding Pi-hole repository
-a, --admin Only retrieve info regarding web repository
-f, --ftl Only retrieve info regarding FTL repository
Options:
-c, --current Return the current version
-l, --latest Return the latest version
--hash Return the GitHub hash from your local repositories
-h, --help Show this help dialog"
exit 0
} }
case "${1}" in main
"-p" | "--pihole" ) shift; versionOutput "Pi-hole" "$@";;
"-a" | "--admin" ) shift; versionOutput "web" "$@";;
"-f" | "--ftl" ) shift; versionOutput "FTL" "$@";;
"-h" | "--help" ) helpFunc;;
* ) defaultOutput "$@";;
esac

@ -27,7 +27,7 @@ CREATE TABLE domainlist
CREATE TABLE adlist CREATE TABLE adlist
( (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
address TEXT UNIQUE NOT NULL, address TEXT NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT 1, enabled BOOLEAN NOT NULL DEFAULT 1,
date_added INTEGER NOT NULL DEFAULT (cast(strftime('%s', 'now') as int)), date_added INTEGER NOT NULL DEFAULT (cast(strftime('%s', 'now') as int)),
date_modified INTEGER NOT NULL DEFAULT (cast(strftime('%s', 'now') as int)), date_modified INTEGER NOT NULL DEFAULT (cast(strftime('%s', 'now') as int)),
@ -37,7 +37,8 @@ CREATE TABLE adlist
invalid_domains INTEGER NOT NULL DEFAULT 0, invalid_domains INTEGER NOT NULL DEFAULT 0,
status INTEGER NOT NULL DEFAULT 0, status INTEGER NOT NULL DEFAULT 0,
abp_entries INTEGER NOT NULL DEFAULT 0, abp_entries INTEGER NOT NULL DEFAULT 0,
type INTEGER NOT NULL DEFAULT 0 type INTEGER NOT NULL DEFAULT 0,
UNIQUE(address, type)
); );
CREATE TABLE adlist_by_group CREATE TABLE adlist_by_group
@ -65,7 +66,7 @@ CREATE TABLE info
value TEXT NOT NULL value TEXT NOT NULL
); );
INSERT INTO "info" VALUES('version','17'); INSERT INTO "info" VALUES('version','18');
CREATE TABLE domain_audit CREATE TABLE domain_audit
( (
@ -144,14 +145,14 @@ CREATE VIEW vw_regex_blacklist AS SELECT domain, domainlist.id AS id, domainlist
AND domainlist.type = 3 AND domainlist.type = 3
ORDER BY domainlist.id; ORDER BY domainlist.id;
CREATE VIEW vw_gravity AS SELECT domain, adlist_by_group.group_id AS group_id CREATE VIEW vw_gravity AS SELECT domain, adlist.id AS adlist_id, adlist_by_group.group_id AS group_id
FROM gravity FROM gravity
LEFT JOIN adlist_by_group ON adlist_by_group.adlist_id = gravity.adlist_id LEFT JOIN adlist_by_group ON adlist_by_group.adlist_id = gravity.adlist_id
LEFT JOIN adlist ON adlist.id = gravity.adlist_id LEFT JOIN adlist ON adlist.id = gravity.adlist_id
LEFT JOIN "group" ON "group".id = adlist_by_group.group_id LEFT JOIN "group" ON "group".id = adlist_by_group.group_id
WHERE adlist.enabled = 1 AND (adlist_by_group.group_id IS NULL OR "group".enabled = 1); WHERE adlist.enabled = 1 AND (adlist_by_group.group_id IS NULL OR "group".enabled = 1);
CREATE VIEW vw_antigravity AS SELECT domain, adlist_by_group.group_id AS group_id CREATE VIEW vw_antigravity AS SELECT domain, adlist.id AS adlist_id, adlist_by_group.group_id AS group_id
FROM antigravity FROM antigravity
LEFT JOIN adlist_by_group ON adlist_by_group.adlist_id = antigravity.adlist_id LEFT JOIN adlist_by_group ON adlist_by_group.adlist_id = antigravity.adlist_id
LEFT JOIN adlist ON adlist.id = antigravity.adlist_id LEFT JOIN adlist ON adlist.id = antigravity.adlist_id

@ -1,9 +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.
#
# Allows the WebUI to use Pi-hole commands
#
# This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license.
#

@ -1,5 +1,5 @@
_pihole() { _pihole() {
local cur prev opts opts_admin opts_checkout opts_debug opts_interface opts_logging opts_privacy opts_query opts_update opts_version local cur prev opts opts_checkout opts_debug opts_logging opts_query opts_update opts_version
COMPREPLY=() COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}" prev="${COMP_WORDS[COMP_CWORD-1]}"
@ -7,17 +7,13 @@ _pihole() {
case "${prev}" in case "${prev}" in
"pihole") "pihole")
opts="admin blacklist checkout debug disable enable flush help logging query reconfigure regex restartdns status tail uninstall updateGravity updatePihole version wildcard whitelist arpflush" opts="blacklist checkout debug disable enable flush help logging query reconfigure regex restartdns status tail uninstall updateGravity updatePihole version wildcard whitelist arpflush"
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
;; ;;
"whitelist"|"blacklist"|"wildcard"|"regex") "whitelist"|"blacklist"|"wildcard"|"regex")
opts_lists="\--delmode \--noreload \--quiet \--list \--nuke" opts_lists="\--delmode \--noreload \--quiet \--list \--nuke"
COMPREPLY=( $(compgen -W "${opts_lists}" -- ${cur}) ) COMPREPLY=( $(compgen -W "${opts_lists}" -- ${cur}) )
;; ;;
"admin")
opts_admin="celsius fahrenheit interface kelvin password privacylevel"
COMPREPLY=( $(compgen -W "${opts_admin}" -- ${cur}) )
;;
"checkout") "checkout")
opts_checkout="core ftl web master dev" opts_checkout="core ftl web master dev"
COMPREPLY=( $(compgen -W "${opts_checkout}" -- ${cur}) ) COMPREPLY=( $(compgen -W "${opts_checkout}" -- ${cur}) )
@ -31,33 +27,13 @@ _pihole() {
COMPREPLY=( $(compgen -W "${opts_logging}" -- ${cur}) ) COMPREPLY=( $(compgen -W "${opts_logging}" -- ${cur}) )
;; ;;
"query") "query")
opts_query="-adlist -all -exact" opts_query="--partial --all"
COMPREPLY=( $(compgen -W "${opts_query}" -- ${cur}) ) COMPREPLY=( $(compgen -W "${opts_query}" -- ${cur}) )
;; ;;
"updatePihole"|"-up") "updatePihole"|"-up")
opts_update="--check-only" opts_update="--check-only"
COMPREPLY=( $(compgen -W "${opts_update}" -- ${cur}) ) COMPREPLY=( $(compgen -W "${opts_update}" -- ${cur}) )
;; ;;
"version")
opts_version="\--admin \--current \--ftl \--hash \--latest \--pihole"
COMPREPLY=( $(compgen -W "${opts_version}" -- ${cur}) )
;;
"interface")
if ( [[ "$prev2" == "admin" ]] || [[ "$prev2" == "-a" ]] ); then
opts_interface="$(cat /proc/net/dev | cut -d: -s -f1)"
COMPREPLY=( $(compgen -W "${opts_interface}" -- ${cur}) )
else
return 1
fi
;;
"privacylevel")
if ( [[ "$prev2" == "admin" ]] || [[ "$prev2" == "-a" ]] ); then
opts_privacy="0 1 2 3"
COMPREPLY=( $(compgen -W "${opts_privacy}" -- ${cur}) )
else
return 1
fi
;;
"core"|"admin"|"ftl") "core"|"admin"|"ftl")
if [[ "$prev2" == "checkout" ]]; then if [[ "$prev2" == "checkout" ]]; then
opts_checkout="master dev" opts_checkout="master dev"

File diff suppressed because it is too large Load Diff

@ -36,21 +36,16 @@ blacklistFile="${piholeDir}/blacklist.txt"
regexFile="${piholeDir}/regex.list" regexFile="${piholeDir}/regex.list"
adListFile="${piholeDir}/adlists.list" adListFile="${piholeDir}/adlists.list"
localList="${piholeDir}/local.list"
VPNList="/etc/openvpn/ipp.txt"
piholeGitDir="/etc/.pihole" piholeGitDir="/etc/.pihole"
GRAVITYDB=$(getFTLConfigValue files.gravity) GRAVITYDB=$(getFTLConfigValue files.gravity)
GRAVITY_TMPDIR=$(getFTLConfigValue files.gravity_tmp)
gravityDBschema="${piholeGitDir}/advanced/Templates/gravity.db.sql" gravityDBschema="${piholeGitDir}/advanced/Templates/gravity.db.sql"
gravityDBcopy="${piholeGitDir}/advanced/Templates/gravity_copy.sql" gravityDBcopy="${piholeGitDir}/advanced/Templates/gravity_copy.sql"
domainsExtension="domains" domainsExtension="domains"
curl_connect_timeout=10 curl_connect_timeout=10
# Check gravity temp directory
# Set up tmp dir variable in case it's not configured
: "${GRAVITY_TMPDIR:=/tmp}"
if [ ! -d "${GRAVITY_TMPDIR}" ] || [ ! -w "${GRAVITY_TMPDIR}" ]; then if [ ! -d "${GRAVITY_TMPDIR}" ] || [ ! -w "${GRAVITY_TMPDIR}" ]; then
echo -e " ${COL_LIGHT_RED}Gravity temporary directory does not exist or is not a writeable directory, falling back to /tmp. ${COL_NC}" echo -e " ${COL_LIGHT_RED}Gravity temporary directory does not exist or is not a writeable directory, falling back to /tmp. ${COL_NC}"
GRAVITY_TMPDIR="/tmp" GRAVITY_TMPDIR="/tmp"
@ -66,7 +61,7 @@ gravityOLDfile="${gravityDIR}/gravity_old.db"
# Generate new SQLite3 file from schema template # Generate new SQLite3 file from schema template
generate_gravity_database() { generate_gravity_database() {
if ! pihole-FTL sqlite3 "${gravityDBfile}" < "${gravityDBschema}"; then if ! pihole-FTL sqlite3 -ni "${gravityDBfile}" <"${gravityDBschema}"; then
echo -e " ${CROSS} Unable to create ${gravityDBfile}" echo -e " ${CROSS} Unable to create ${gravityDBfile}"
return 1 return 1
fi fi
@ -81,7 +76,7 @@ gravity_build_tree() {
echo -ne " ${INFO} ${str}..." echo -ne " ${INFO} ${str}..."
# The index is intentionally not UNIQUE as poor quality adlists may contain domains more than once # The index is intentionally not UNIQUE as poor quality adlists may contain domains more than once
output=$( { pihole-FTL sqlite3 "${gravityTEMPfile}" "CREATE INDEX idx_gravity ON gravity (domain, adlist_id);"; } 2>&1 ) output=$({ pihole-FTL sqlite3 -ni "${gravityTEMPfile}" "CREATE INDEX idx_gravity ON gravity (domain, adlist_id);"; } 2>&1)
status="$?" status="$?"
if [[ "${status}" -ne 0 ]]; then if [[ "${status}" -ne 0 ]]; then
@ -120,7 +115,7 @@ gravity_swap_databases() {
# Update timestamp when the gravity table was last updated successfully # Update timestamp when the gravity table was last updated successfully
update_gravity_timestamp() { update_gravity_timestamp() {
output=$( { printf ".timeout 30000\\nINSERT OR REPLACE INTO info (property,value) values ('updated',cast(strftime('%%s', 'now') as int));" | pihole-FTL sqlite3 "${gravityTEMPfile}"; } 2>&1 ) output=$({ printf ".timeout 30000\\nINSERT OR REPLACE INTO info (property,value) values ('updated',cast(strftime('%%s', 'now') as int));" | pihole-FTL sqlite3 -ni "${gravityTEMPfile}"; } 2>&1)
status="$?" status="$?"
if [[ "${status}" -ne 0 ]]; then if [[ "${status}" -ne 0 ]]; then
@ -165,7 +160,7 @@ database_table_from_file() {
# Get MAX(id) from domainlist when INSERTing into this table # Get MAX(id) from domainlist when INSERTing into this table
if [[ "${table}" == "domainlist" ]]; then if [[ "${table}" == "domainlist" ]]; then
rowid="$(pihole-FTL sqlite3 "${gravityDBfile}" "SELECT MAX(id) FROM domainlist;")" rowid="$(pihole-FTL sqlite3 -ni "${gravityDBfile}" "SELECT MAX(id) FROM domainlist;")"
if [[ -z "$rowid" ]]; then if [[ -z "$rowid" ]]; then
rowid=0 rowid=0
fi fi
@ -174,19 +169,18 @@ database_table_from_file() {
# Loop over all domains in ${src} file # Loop over all domains in ${src} file
# Read file line by line # Read file line by line
grep -v '^ *#' < "${src}" | while IFS= read -r domain grep -v '^ *#' <"${src}" | while IFS= read -r domain; do
do
# Only add non-empty lines # Only add non-empty lines
if [[ -n "${domain}" ]]; then if [[ -n "${domain}" ]]; then
if [[ "${table}" == "domain_audit" ]]; then if [[ "${table}" == "domain_audit" ]]; then
# domain_audit table format (no enable or modified fields) # domain_audit table format (no enable or modified fields)
echo "${rowid},\"${domain}\",${timestamp}" >> "${tmpFile}" echo "${rowid},\"${domain}\",${timestamp}" >>"${tmpFile}"
elif [[ "${table}" == "adlist" ]]; then elif [[ "${table}" == "adlist" ]]; then
# Adlist table format # Adlist table format
echo "${rowid},\"${domain}\",1,${timestamp},${timestamp},\"Migrated from ${src}\",,0,0,0,0,0" >> "${tmpFile}" echo "${rowid},\"${domain}\",1,${timestamp},${timestamp},\"Migrated from ${src}\",,0,0,0,0,0" >>"${tmpFile}"
else else
# White-, black-, and regexlist table format # White-, black-, and regexlist table format
echo "${rowid},${list_type},\"${domain}\",1,${timestamp},${timestamp},\"Migrated from ${src}\"" >> "${tmpFile}" echo "${rowid},${list_type},\"${domain}\",1,${timestamp},${timestamp},\"Migrated from ${src}\"" >>"${tmpFile}"
fi fi
rowid+=1 rowid+=1
fi fi
@ -195,7 +189,7 @@ database_table_from_file() {
# Store domains in database table specified by ${table} # Store domains in database table specified by ${table}
# Use printf as .mode and .import need to be on separate lines # Use printf as .mode and .import need to be on separate lines
# see https://unix.stackexchange.com/a/445615/83260 # see https://unix.stackexchange.com/a/445615/83260
output=$( { printf ".timeout 30000\\n.mode csv\\n.import \"%s\" %s\\n" "${tmpFile}" "${table}" | pihole-FTL sqlite3 "${gravityDBfile}"; } 2>&1 ) output=$({ printf ".timeout 30000\\n.mode csv\\n.import \"%s\" %s\\n" "${tmpFile}" "${table}" | pihole-FTL sqlite3 -ni "${gravityDBfile}"; } 2>&1)
status="$?" status="$?"
if [[ "${status}" -ne 0 ]]; then if [[ "${status}" -ne 0 ]]; then
@ -205,17 +199,17 @@ database_table_from_file() {
# Move source file to backup directory, create directory if not existing # Move source file to backup directory, create directory if not existing
mkdir -p "${backup_path}" mkdir -p "${backup_path}"
mv "${src}" "${backup_file}" 2> /dev/null || \ mv "${src}" "${backup_file}" 2>/dev/null ||
echo -e " ${CROSS} Unable to backup ${src} to ${backup_path}" echo -e " ${CROSS} Unable to backup ${src} to ${backup_path}"
# Delete tmpFile # Delete tmpFile
rm "${tmpFile}" > /dev/null 2>&1 || \ rm "${tmpFile}" >/dev/null 2>&1 ||
echo -e " ${CROSS} Unable to remove ${tmpFile}" echo -e " ${CROSS} Unable to remove ${tmpFile}"
} }
# Check if a column with name ${2} exists in gravity table with name ${1} # Check if a column with name ${2} exists in gravity table with name ${1}
gravity_column_exists() { gravity_column_exists() {
output=$( { printf ".timeout 30000\\nSELECT EXISTS(SELECT * FROM pragma_table_info('%s') WHERE name='%s');\\n" "${1}" "${2}" | pihole-FTL sqlite3 "${gravityTEMPfile}"; } 2>&1 ) output=$({ printf ".timeout 30000\\nSELECT EXISTS(SELECT * FROM pragma_table_info('%s') WHERE name='%s');\\n" "${1}" "${2}" | pihole-FTL sqlite3 -ni "${gravityTEMPfile}"; } 2>&1)
if [[ "${output}" == "1" ]]; then if [[ "${output}" == "1" ]]; then
return 0 # Bash 0 is success return 0 # Bash 0 is success
fi fi
@ -227,10 +221,10 @@ gravity_column_exists() {
database_adlist_number() { database_adlist_number() {
# Only try to set number of domains when this field exists in the gravity database # Only try to set number of domains when this field exists in the gravity database
if ! gravity_column_exists "adlist" "number"; then if ! gravity_column_exists "adlist" "number"; then
return; return
fi fi
output=$( { printf ".timeout 30000\\nUPDATE adlist SET number = %i, invalid_domains = %i WHERE id = %i;\\n" "${2}" "${3}" "${1}" | pihole-FTL sqlite3 "${gravityTEMPfile}"; } 2>&1 ) output=$({ printf ".timeout 30000\\nUPDATE adlist SET number = %i, invalid_domains = %i WHERE id = %i;\\n" "${2}" "${3}" "${1}" | pihole-FTL sqlite3 -ni "${gravityTEMPfile}"; } 2>&1)
status="$?" status="$?"
if [[ "${status}" -ne 0 ]]; then if [[ "${status}" -ne 0 ]]; then
@ -243,10 +237,10 @@ database_adlist_number() {
database_adlist_status() { database_adlist_status() {
# Only try to set the status when this field exists in the gravity database # Only try to set the status when this field exists in the gravity database
if ! gravity_column_exists "adlist" "status"; then if ! gravity_column_exists "adlist" "status"; then
return; return
fi fi
output=$( { printf ".timeout 30000\\nUPDATE adlist SET status = %i WHERE id = %i;\\n" "${2}" "${1}" | pihole-FTL sqlite3 "${gravityTEMPfile}"; } 2>&1 ) output=$({ printf ".timeout 30000\\nUPDATE adlist SET status = %i WHERE id = %i;\\n" "${2}" "${1}" | pihole-FTL sqlite3 -ni "${gravityTEMPfile}"; } 2>&1)
status="$?" status="$?"
if [[ "${status}" -ne 0 ]]; then if [[ "${status}" -ne 0 ]]; then
@ -300,15 +294,10 @@ migrate_to_database() {
# Determine if DNS resolution is available before proceeding # Determine if DNS resolution is available before proceeding
gravity_CheckDNSResolutionAvailable() { gravity_CheckDNSResolutionAvailable() {
local lookupDomain="pi.hole" local lookupDomain="raw.githubusercontent.com"
# Determine if $localList does not exist, and ensure it is not empty
if [[ ! -e "${localList}" ]] || [[ -s "${localList}" ]]; then
lookupDomain="raw.githubusercontent.com"
fi
# Determine if $lookupDomain is resolvable # Determine if $lookupDomain is resolvable
if timeout 4 getent hosts "${lookupDomain}" &> /dev/null; then if timeout 4 getent hosts "${lookupDomain}" &>/dev/null; then
# Print confirmation of resolvability if it had previously failed # Print confirmation of resolvability if it had previously failed
if [[ -n "${secs:-}" ]]; then if [[ -n "${secs:-}" ]]; then
echo -e "${OVER} ${TICK} DNS resolution is now available\\n" echo -e "${OVER} ${TICK} DNS resolution is now available\\n"
@ -322,7 +311,7 @@ gravity_CheckDNSResolutionAvailable() {
# If the /etc/resolv.conf contains resolvers other than 127.0.0.1 then the local dnsmasq will not be queried and pi.hole is NXDOMAIN. # If the /etc/resolv.conf contains resolvers other than 127.0.0.1 then the local dnsmasq will not be queried and pi.hole is NXDOMAIN.
# This means that even though name resolution is working, the getent hosts check fails and the holddown timer keeps ticking and eventually fails # This means that even though name resolution is working, the getent hosts check fails and the holddown timer keeps ticking and eventually fails
# So we check the output of the last command and if it failed, attempt to use dig +short as a fallback # So we check the output of the last command and if it failed, attempt to use dig +short as a fallback
if timeout 4 dig +short "${lookupDomain}" &> /dev/null; then if timeout 4 dig +short "${lookupDomain}" &>/dev/null; then
if [[ -n "${secs:-}" ]]; then if [[ -n "${secs:-}" ]]; then
echo -e "${OVER} ${TICK} DNS resolution is now available\\n" echo -e "${OVER} ${TICK} DNS resolution is now available\\n"
fi fi
@ -333,7 +322,7 @@ gravity_CheckDNSResolutionAvailable() {
fi fi
# Determine error output message # Determine error output message
if pgrep pihole-FTL &> /dev/null; then if pgrep pihole-FTL &>/dev/null; then
echo -e " ${CROSS} DNS resolution is currently unavailable" echo -e " ${CROSS} DNS resolution is currently unavailable"
else else
echo -e " ${CROSS} DNS service is not running" echo -e " ${CROSS} DNS service is not running"
@ -343,7 +332,7 @@ gravity_CheckDNSResolutionAvailable() {
# Ensure DNS server is given time to be resolvable # Ensure DNS server is given time to be resolvable
secs="120" secs="120"
echo -ne " ${INFO} Time until retry: ${secs}" echo -ne " ${INFO} Time until retry: ${secs}"
until timeout 1 getent hosts "${lookupDomain}" &> /dev/null; do until timeout 1 getent hosts "${lookupDomain}" &>/dev/null; do
[[ "${secs:-}" -eq 0 ]] && break [[ "${secs:-}" -eq 0 ]] && break
echo -ne "${OVER} ${INFO} Time until retry: ${secs}" echo -ne "${OVER} ${INFO} Time until retry: ${secs}"
: $((secs--)) : $((secs--))
@ -364,19 +353,19 @@ gravity_DownloadBlocklists() {
# Retrieve source URLs from gravity database # Retrieve source URLs from gravity database
# We source only enabled adlists, SQLite3 stores boolean values as 0 (false) or 1 (true) # We source only enabled adlists, SQLite3 stores boolean values as 0 (false) or 1 (true)
mapfile -t sources <<< "$(pihole-FTL sqlite3 "${gravityDBfile}" "SELECT address FROM vw_adlist;" 2> /dev/null)" mapfile -t sources <<<"$(pihole-FTL sqlite3 -ni "${gravityDBfile}" "SELECT address FROM vw_adlist;" 2>/dev/null)"
mapfile -t sourceIDs <<< "$(pihole-FTL sqlite3 "${gravityDBfile}" "SELECT id FROM vw_adlist;" 2> /dev/null)" mapfile -t sourceIDs <<<"$(pihole-FTL sqlite3 -ni "${gravityDBfile}" "SELECT id FROM vw_adlist;" 2>/dev/null)"
mapfile -t sourceTypes <<< "$(pihole-FTL sqlite3 "${gravityDBfile}" "SELECT type FROM vw_adlist;" 2> /dev/null)" mapfile -t sourceTypes <<<"$(pihole-FTL sqlite3 -ni "${gravityDBfile}" "SELECT type FROM vw_adlist;" 2>/dev/null)"
# Parse source domains from $sources # Parse source domains from $sources
mapfile -t sourceDomains <<< "$( mapfile -t sourceDomains <<<"$(
# Logic: Split by folder/port # Logic: Split by folder/port
awk -F '[/:]' '{ awk -F '[/:]' '{
# Remove URL protocol & optional username:password@ # Remove URL protocol & optional username:password@
gsub(/(.*:\/\/|.*:.*@)/, "", $0) gsub(/(.*:\/\/|.*:.*@)/, "", $0)
if(length($1)>0){print $1} if(length($1)>0){print $1}
else {print "local"} else {print "local"}
}' <<< "$(printf '%s\n' "${sources[@]}")" 2> /dev/null }' <<<"$(printf '%s\n' "${sources[@]}")" 2>/dev/null
)" )"
local str="Pulling blocklist source list into range" local str="Pulling blocklist source list into range"
@ -394,8 +383,8 @@ gravity_DownloadBlocklists() {
# Prepare new gravity database # Prepare new gravity database
str="Preparing new gravity database" str="Preparing new gravity database"
echo -ne " ${INFO} ${str}..." echo -ne " ${INFO} ${str}..."
rm "${gravityTEMPfile}" > /dev/null 2>&1 rm "${gravityTEMPfile}" >/dev/null 2>&1
output=$( { pihole-FTL sqlite3 "${gravityTEMPfile}" < "${gravityDBschema}"; } 2>&1 ) output=$({ pihole-FTL sqlite3 -ni "${gravityTEMPfile}" <"${gravityDBschema}"; } 2>&1)
status="$?" status="$?"
if [[ "${status}" -ne 0 ]]; then if [[ "${status}" -ne 0 ]]; then
@ -415,7 +404,7 @@ gravity_DownloadBlocklists() {
copyGravity="${copyGravity//"${gravityDBfile_default}"/"${gravityDBfile}"}" copyGravity="${copyGravity//"${gravityDBfile_default}"/"${gravityDBfile}"}"
fi fi
output=$( { pihole-FTL sqlite3 "${gravityTEMPfile}" <<< "${copyGravity}"; } 2>&1 ) output=$({ pihole-FTL sqlite3 -ni "${gravityTEMPfile}" <<<"${copyGravity}"; } 2>&1)
status="$?" status="$?"
if [[ "${status}" -ne 0 ]]; then if [[ "${status}" -ne 0 ]]; then
@ -460,7 +449,7 @@ gravity_DownloadBlocklists() {
# this will remove first @ that is after schema and before domain # this will remove first @ that is after schema and before domain
# \1 is optional schema, \2 is userinfo # \1 is optional schema, \2 is userinfo
check_url="$( sed -re 's#([^:/]*://)?([^/]+)@#\1\2#' <<< "$url" )" check_url="$(sed -re 's#([^:/]*://)?([^/]+)@#\1\2#' <<<"$url")"
if [[ "${check_url}" =~ ${regex} ]]; then if [[ "${check_url}" =~ ${regex} ]]; then
echo -e " ${CROSS} Invalid Target" echo -e " ${CROSS} Invalid Target"
@ -480,7 +469,7 @@ compareLists() {
if [[ -s "${target}.sha1" ]]; then if [[ -s "${target}.sha1" ]]; then
if ! sha1sum --check --status --strict "${target}.sha1"; then if ! sha1sum --check --status --strict "${target}.sha1"; then
# The list changed upstream, we need to update the checksum # The list changed upstream, we need to update the checksum
sha1sum "${target}" > "${target}.sha1" sha1sum "${target}" >"${target}.sha1"
echo " ${INFO} List has been updated" echo " ${INFO} List has been updated"
database_adlist_status "${adlistID}" "1" database_adlist_status "${adlistID}" "1"
else else
@ -489,7 +478,7 @@ compareLists() {
fi fi
else else
# No checksum available, create one for comparing on the next run # No checksum available, create one for comparing on the next run
sha1sum "${target}" > "${target}.sha1" sha1sum "${target}" >"${target}.sha1"
# We assume here it was changed upstream # We assume here it was changed upstream
database_adlist_status "${adlistID}" "1" database_adlist_status "${adlistID}" "1"
fi fi
@ -517,6 +506,31 @@ gravity_DownloadBlocklistFromUrl() {
str="Status:" str="Status:"
echo -ne " ${INFO} ${str} Pending..." echo -ne " ${INFO} ${str} Pending..."
blocked=false blocked=false
case $(getFTLConfigValue dns.blocking.mode) in
"IP-NODATA-AAAA" | "IP")
# Get IP address of this domain
ip="$(dig "${domain}" +short)"
# Check if this IP matches any IP of the system
if [[ -n "${ip}" && $(grep -Ec "inet(|6) ${ip}" <<<"$(ip a)") -gt 0 ]]; then
blocked=true
fi
;;
"NXDOMAIN")
if [[ $(dig "${domain}" | grep "NXDOMAIN" -c) -ge 1 ]]; then
blocked=true
fi
;;
"NODATA")
if [[ $(dig "${domain}" | grep "NOERROR" -c) -ge 1 ]] && [[ -z $(dig +short "${domain}") ]]; then
blocked=true
fi
;;
"NULL" | *)
if [[ $(dig "${domain}" +short | grep "0.0.0.0" -c) -ge 1 ]]; then
blocked=true
fi
;;
esac
# Check if this domain is blocked by Pi-hole but only if the domain is not a # Check if this domain is blocked by Pi-hole but only if the domain is not a
# local file or empty # local file or empty
@ -561,14 +575,15 @@ gravity_DownloadBlocklistFromUrl() {
local ip_addr port local ip_addr port
printf -v ip_addr "%s" "${upstream%#*}" printf -v ip_addr "%s" "${upstream%#*}"
if [[ ${upstream} != *"#"* ]]; then if [[ ${upstream} != *"#"* ]]; then
port=53 port=53
else else
printf -v port "%s" "${upstream#*#}" printf -v port "%s" "${upstream#*#}"
fi fi
ip=$(dig "@${ip_addr}" -p "${port}" +short "${domain}" | tail -1) ip=$(dig "@${ip_addr}" -p "${port}" +short "${domain}" | tail -1)
if [[ $(echo "${url}" | awk -F '://' '{print $1}') = "https" ]]; then if [[ $(echo "${url}" | awk -F '://' '{print $1}') = "https" ]]; then
port=443; port=443
else port=80 else
port=80
fi fi
echo -e "${OVER} ${CROSS} ${str} ${domain} is blocked by one of your lists. Using DNS server ${upstream} instead"; echo -e "${OVER} ${CROSS} ${str} ${domain} is blocked by one of your lists. Using DNS server ${upstream} instead";
echo -ne " ${INFO} ${str} Pending..." echo -ne " ${INFO} ${str} Pending..."
@ -577,33 +592,42 @@ gravity_DownloadBlocklistFromUrl() {
fi fi
# shellcheck disable=SC2086 # shellcheck disable=SC2086
httpCode=$(curl --connect-timeout ${curl_connect_timeout} -s -L ${compression} ${cmd_ext} ${heisenbergCompensator} -w "%{http_code}" "${url}" -o "${listCurlBuffer}" 2> /dev/null) httpCode=$(curl --connect-timeout ${curl_connect_timeout} -s -L ${compression} ${cmd_ext} ${heisenbergCompensator} -w "%{http_code}" "${url}" -o "${listCurlBuffer}" 2>/dev/null)
case $url in case $url in
# Did we "download" a local file? # Did we "download" a local file?
"file"*) "file"*)
if [[ -s "${listCurlBuffer}" ]]; then if [[ -s "${listCurlBuffer}" ]]; then
echo -e "${OVER} ${TICK} ${str} Retrieval successful"; success=true echo -e "${OVER} ${TICK} ${str} Retrieval successful"
else success=true
echo -e "${OVER} ${CROSS} ${str} Not found / empty list" else
fi;; echo -e "${OVER} ${CROSS} ${str} Not found / empty list"
# Did we "download" a remote file? fi
*) ;;
# Determine "Status:" output based on HTTP response # Did we "download" a remote file?
case "${httpCode}" in *)
"200") echo -e "${OVER} ${TICK} ${str} Retrieval successful"; success=true;; # Determine "Status:" output based on HTTP response
"304") echo -e "${OVER} ${TICK} ${str} No changes detected"; success=true;; case "${httpCode}" in
"000") echo -e "${OVER} ${CROSS} ${str} Connection Refused";; "200")
"403") echo -e "${OVER} ${CROSS} ${str} Forbidden";; echo -e "${OVER} ${TICK} ${str} Retrieval successful"
"404") echo -e "${OVER} ${CROSS} ${str} Not found";; success=true
"408") echo -e "${OVER} ${CROSS} ${str} Time-out";; ;;
"451") echo -e "${OVER} ${CROSS} ${str} Unavailable For Legal Reasons";; "304")
"500") echo -e "${OVER} ${CROSS} ${str} Internal Server Error";; echo -e "${OVER} ${TICK} ${str} No changes detected"
"504") echo -e "${OVER} ${CROSS} ${str} Connection Timed Out (Gateway)";; success=true
"521") echo -e "${OVER} ${CROSS} ${str} Web Server Is Down (Cloudflare)";; ;;
"522") echo -e "${OVER} ${CROSS} ${str} Connection Timed Out (Cloudflare)";; "000") echo -e "${OVER} ${CROSS} ${str} Connection Refused" ;;
* ) echo -e "${OVER} ${CROSS} ${str} ${url} (${httpCode})";; "403") echo -e "${OVER} ${CROSS} ${str} Forbidden" ;;
esac;; "404") echo -e "${OVER} ${CROSS} ${str} Not found" ;;
"408") echo -e "${OVER} ${CROSS} ${str} Time-out" ;;
"451") echo -e "${OVER} ${CROSS} ${str} Unavailable For Legal Reasons" ;;
"500") echo -e "${OVER} ${CROSS} ${str} Internal Server Error" ;;
"504") echo -e "${OVER} ${CROSS} ${str} Connection Timed Out (Gateway)" ;;
"521") echo -e "${OVER} ${CROSS} ${str} Web Server Is Down (Cloudflare)" ;;
"522") echo -e "${OVER} ${CROSS} ${str} Connection Timed Out (Cloudflare)" ;;
*) echo -e "${OVER} ${CROSS} ${str} ${url} (${httpCode})" ;;
esac
;;
esac esac
local done="false" local done="false"
@ -657,7 +681,7 @@ gravity_ParseFileIntoDomains() {
# This helps with that and makes it easier to read # This helps with that and makes it easier to read
# It also helps with debugging so each stage of the script can be researched more in depth # It also helps with debugging so each stage of the script can be researched more in depth
# 1) Convert all characters to lowercase # 1) Convert all characters to lowercase
tr '[:upper:]' '[:lower:]' < "${src}" > "${destination}" tr '[:upper:]' '[:lower:]' <"${src}" >"${destination}"
# 2) Remove carriage returns # 2) Remove carriage returns
# 3) Remove lines starting with ! (ABP Comments) # 3) Remove lines starting with ! (ABP Comments)
@ -667,7 +691,7 @@ gravity_ParseFileIntoDomains() {
# 7) Remove leading tabs, spaces, etc. (Also removes leading IP addresses) # 7) Remove leading tabs, spaces, etc. (Also removes leading IP addresses)
# 8) Remove empty lines # 8) Remove empty lines
sed -i -r \ sed -i -r \
-e 's/\r$//' \ -e 's/\r$//' \
-e 's/\s*!.*//g' \ -e 's/\s*!.*//g' \
-e 's/\s*\[.*//g' \ -e 's/\s*\[.*//g' \
@ -684,12 +708,12 @@ gravity_Table_Count() {
local table="${1}" local table="${1}"
local str="${2}" local str="${2}"
local num local num
num="$(pihole-FTL sqlite3 "${gravityTEMPfile}" "SELECT COUNT(*) FROM ${table};")" num="$(pihole-FTL sqlite3 -ni "${gravityTEMPfile}" "SELECT COUNT(*) FROM ${table};")"
if [[ "${table}" == "gravity" ]]; then if [[ "${table}" == "gravity" ]]; then
local unique local unique
unique="$(pihole-FTL sqlite3 "${gravityTEMPfile}" "SELECT COUNT(*) FROM (SELECT DISTINCT domain FROM ${table});")" unique="$(pihole-FTL sqlite3 -ni "${gravityTEMPfile}" "SELECT COUNT(*) FROM (SELECT DISTINCT domain FROM ${table});")"
echo -e " ${INFO} Number of ${str}: ${num} (${COL_BOLD}${unique} unique domains${COL_NC})" echo -e " ${INFO} Number of ${str}: ${num} (${COL_BOLD}${unique} unique domains${COL_NC})"
pihole-FTL sqlite3 "${gravityTEMPfile}" "INSERT OR REPLACE INTO info (property,value) VALUES ('gravity_count',${unique});" pihole-FTL sqlite3 -ni "${gravityTEMPfile}" "INSERT OR REPLACE INTO info (property,value) VALUES ('gravity_count',${unique});"
else else
echo -e " ${INFO} Number of ${str}: ${num}" echo -e " ${INFO} Number of ${str}: ${num}"
fi fi
@ -706,18 +730,6 @@ gravity_ShowCount() {
gravity_Table_Count "vw_regex_whitelist" "regex allowed filters" gravity_Table_Count "vw_regex_whitelist" "regex allowed filters"
} }
# Create "localhost" entries into hosts format
gravity_generateLocalList() {
# Empty $localList if it already exists, otherwise, create it
echo "### Do not modify this file, it will be overwritten by pihole -g" > "${localList}"
chmod 644 "${localList}"
# Add additional LAN hosts provided by OpenVPN (if available)
if [[ -f "${VPNList}" ]]; then
awk -F, '{printf $2"\t"$1".vpn\n"}' "${VPNList}" >> "${localList}"
fi
}
# Trap Ctrl-C # Trap Ctrl-C
gravity_Trap() { gravity_Trap() {
trap '{ echo -e "\\n\\n ${INFO} ${COL_LIGHT_RED}User-abort detected${COL_NC}"; gravity_Cleanup "error"; }' INT trap '{ echo -e "\\n\\n ${INFO} ${COL_LIGHT_RED}User-abort detected${COL_NC}"; gravity_Cleanup "error"; }' INT
@ -731,12 +743,12 @@ gravity_Cleanup() {
echo -ne " ${INFO} ${str}..." echo -ne " ${INFO} ${str}..."
# Delete tmp content generated by Gravity # Delete tmp content generated by Gravity
rm ${piholeDir}/pihole.*.txt 2> /dev/null rm ${piholeDir}/pihole.*.txt 2>/dev/null
rm ${piholeDir}/*.tmp 2> /dev/null rm ${piholeDir}/*.tmp 2>/dev/null
# listCurlBuffer location # listCurlBuffer location
rm "${GRAVITY_TMPDIR}"/*.phgpb 2> /dev/null rm "${GRAVITY_TMPDIR}"/*.phgpb 2>/dev/null
# invalid_domains location # invalid_domains location
rm "${GRAVITY_TMPDIR}"/*.ph-non-domains 2> /dev/null rm "${GRAVITY_TMPDIR}"/*.ph-non-domains 2>/dev/null
# Ensure this function only runs when gravity_SetDownloadOptions() has completed # Ensure this function only runs when gravity_SetDownloadOptions() has completed
if [[ "${gravity_Blackbody:-}" == true ]]; then if [[ "${gravity_Blackbody:-}" == true ]]; then
@ -744,7 +756,7 @@ gravity_Cleanup() {
for file in "${piholeDir}"/*."${domainsExtension}"; do for file in "${piholeDir}"/*."${domainsExtension}"; do
# If list is not in active array, then remove it # If list is not in active array, then remove it
if [[ ! "${activeDomains[*]}" == *"${file}"* ]]; then if [[ ! "${activeDomains[*]}" == *"${file}"* ]]; then
rm -f "${file}" 2> /dev/null || \ rm -f "${file}" 2>/dev/null ||
echo -e " ${CROSS} Failed to remove ${file##*/}" echo -e " ${CROSS} Failed to remove ${file##*/}"
fi fi
done done
@ -770,7 +782,7 @@ database_recovery() {
local str="Checking integrity of existing gravity database (this can take a while)" local str="Checking integrity of existing gravity database (this can take a while)"
local option="${1}" local option="${1}"
echo -ne " ${INFO} ${str}..." echo -ne " ${INFO} ${str}..."
result="$(pihole-FTL sqlite3 "${gravityDBfile}" "PRAGMA integrity_check" 2>&1)" result="$(pihole-FTL sqlite3 -ni "${gravityDBfile}" "PRAGMA integrity_check" 2>&1)"
if [[ ${result} = "ok" ]]; then if [[ ${result} = "ok" ]]; then
echo -e "${OVER} ${TICK} ${str} - no errors found" echo -e "${OVER} ${TICK} ${str} - no errors found"
@ -778,7 +790,7 @@ database_recovery() {
str="Checking foreign keys of existing gravity database (this can take a while)" str="Checking foreign keys of existing gravity database (this can take a while)"
echo -ne " ${INFO} ${str}..." echo -ne " ${INFO} ${str}..."
unset result unset result
result="$(pihole-FTL sqlite3 "${gravityDBfile}" "PRAGMA foreign_key_check" 2>&1)" result="$(pihole-FTL sqlite3 -ni "${gravityDBfile}" "PRAGMA foreign_key_check" 2>&1)"
if [[ -z ${result} ]]; then if [[ -z ${result} ]]; then
echo -e "${OVER} ${TICK} ${str} - no errors found" echo -e "${OVER} ${TICK} ${str} - no errors found"
if [[ "${option}" != "force" ]]; then if [[ "${option}" != "force" ]]; then
@ -786,18 +798,18 @@ database_recovery() {
fi fi
else else
echo -e "${OVER} ${CROSS} ${str} - errors found:" echo -e "${OVER} ${CROSS} ${str} - errors found:"
while IFS= read -r line ; do echo " - $line"; done <<< "$result" while IFS= read -r line; do echo " - $line"; done <<<"$result"
fi fi
else else
echo -e "${OVER} ${CROSS} ${str} - errors found:" echo -e "${OVER} ${CROSS} ${str} - errors found:"
while IFS= read -r line ; do echo " - $line"; done <<< "$result" while IFS= read -r line; do echo " - $line"; done <<<"$result"
fi fi
str="Trying to recover existing gravity database" str="Trying to recover existing gravity database"
echo -ne " ${INFO} ${str}..." echo -ne " ${INFO} ${str}..."
# We have to remove any possibly existing recovery database or this will fail # We have to remove any possibly existing recovery database or this will fail
rm -f "${gravityDBfile}.recovered" > /dev/null 2>&1 rm -f "${gravityDBfile}.recovered" >/dev/null 2>&1
if result="$(pihole-FTL sqlite3 "${gravityDBfile}" ".recover" | pihole-FTL sqlite3 "${gravityDBfile}.recovered" 2>&1)"; then if result="$(pihole-FTL sqlite3 -ni "${gravityDBfile}" ".recover" | pihole-FTL sqlite3 -ni "${gravityDBfile}.recovered" 2>&1)"; then
echo -e "${OVER} ${TICK} ${str} - success" echo -e "${OVER} ${TICK} ${str} - success"
mv "${gravityDBfile}" "${gravityDBfile}.old" mv "${gravityDBfile}" "${gravityDBfile}.old"
mv "${gravityDBfile}.recovered" "${gravityDBfile}" mv "${gravityDBfile}.recovered" "${gravityDBfile}"
@ -805,7 +817,7 @@ database_recovery() {
echo -ne " ${INFO} The old ${gravityDBfile} has been moved to ${gravityDBfile}.old" echo -ne " ${INFO} The old ${gravityDBfile} has been moved to ${gravityDBfile}.old"
else else
echo -e "${OVER} ${CROSS} ${str} - the following errors happened:" echo -e "${OVER} ${CROSS} ${str} - the following errors happened:"
while IFS= read -r line ; do echo " - $line"; done <<< "$result" while IFS= read -r line; do echo " - $line"; done <<<"$result"
echo -e " ${CROSS} Recovery failed. Try \"pihole -r recreate\" instead." echo -e " ${CROSS} Recovery failed. Try \"pihole -r recreate\" instead."
exit 1 exit 1
fi fi
@ -824,9 +836,10 @@ Options:
repairSelector() { repairSelector() {
case "$1" in case "$1" in
"recover") recover_database=true;; "recover") recover_database=true ;;
"recreate") recreate_database=true;; "recreate") recreate_database=true ;;
*) echo "Usage: pihole -g -r {recover,recreate} *)
echo "Usage: pihole -g -r {recover,recreate}
Attempt to repair gravity database Attempt to repair gravity database
Available options: Available options:
@ -845,15 +858,17 @@ Available options:
and create a new file from scratch. If you still and create a new file from scratch. If you still
have the migration backup created when migrating have the migration backup created when migrating
to Pi-hole v5.0, Pi-hole will import these files." to Pi-hole v5.0, Pi-hole will import these files."
exit 0;; exit 0
;;
esac esac
} }
for var in "$@"; do for var in "$@"; do
case "${var}" in case "${var}" in
"-f" | "--force" ) forceDelete=true;; "-f" | "--force" ) forceDelete=true;;
"-r" | "--repair" ) repairSelector "$3";; "-r" | "--repair" ) repairSelector "$3";;
"-h" | "--help" ) helpFunc;; "-u" | "--upgrade" ) upgrade_gravityDB "${gravityDBfile}" "${piholeDir}"; exit 0;;
"-h" | "--help" ) helpFunc;;
esac esac
done done
@ -869,9 +884,9 @@ if [[ "${recreate_database:-}" == true ]]; then
str="Recreating gravity database from migration backup" str="Recreating gravity database from migration backup"
echo -ne "${INFO} ${str}..." echo -ne "${INFO} ${str}..."
rm "${gravityDBfile}" rm "${gravityDBfile}"
pushd "${piholeDir}" > /dev/null || exit pushd "${piholeDir}" >/dev/null || exit
cp migration_backup/* . cp migration_backup/* .
popd > /dev/null || exit popd >/dev/null || exit
echo -e "${OVER} ${TICK} ${str}" echo -e "${OVER} ${TICK} ${str}"
fi fi
@ -889,7 +904,7 @@ if [[ "${forceDelete:-}" == true ]]; then
str="Deleting existing list cache" str="Deleting existing list cache"
echo -ne "${INFO} ${str}..." echo -ne "${INFO} ${str}..."
rm /etc/pihole/list.* 2> /dev/null || true rm /etc/pihole/list.* 2>/dev/null || true
echo -e "${OVER} ${TICK} ${str}" echo -e "${OVER} ${TICK} ${str}"
fi fi
@ -904,9 +919,6 @@ if ! gravity_DownloadBlocklists; then
exit 1 exit 1
fi fi
# Create local.list
gravity_generateLocalList
# Update gravity timestamp # Update gravity timestamp
update_gravity_timestamp update_gravity_timestamp

@ -140,8 +140,7 @@ uninstallFunc() {
} }
versionFunc() { versionFunc() {
shift exec "${PI_HOLE_SCRIPT_DIR}"/version.sh
exec "${PI_HOLE_SCRIPT_DIR}"/version.sh "$@"
} }
restartDNS() { restartDNS() {
@ -508,7 +507,6 @@ Options:
-up, updatePihole Update Pi-hole subsystems -up, updatePihole Update Pi-hole subsystems
Add '--check-only' to exit script before update is performed. Add '--check-only' to exit script before update is performed.
-v, version Show installed versions of Pi-hole, Web Interface & FTL -v, version Show installed versions of Pi-hole, Web Interface & FTL
Add '-h' for more info on version usage
uninstall Uninstall Pi-hole from your system uninstall Uninstall Pi-hole from your system
status Display the running status of Pi-hole subsystems status Display the running status of Pi-hole subsystems
enable Enable Pi-hole subsystems enable Enable Pi-hole subsystems
@ -531,7 +529,7 @@ fi
need_root=1 need_root=1
case "${1}" in case "${1}" in
"-h" | "help" | "--help" ) helpFunc;; "-h" | "help" | "--help" ) helpFunc;;
"-v" | "version" ) versionFunc "$@";; "-v" | "version" ) versionFunc;;
"-c" | "chronometer" ) chronometerFunc "$@";; "-c" | "chronometer" ) chronometerFunc "$@";;
"-q" | "query" ) queryFunc "$@";; "-q" | "query" ) queryFunc "$@";;
"status" ) statusFunc "$2";; "status" ) statusFunc "$2";;

@ -1,18 +0,0 @@
FROM fedora:37
RUN dnf install -y git initscripts
ENV GITDIR /etc/.pihole
ENV SCRIPTDIR /opt/pihole
RUN mkdir -p $GITDIR $SCRIPTDIR /etc/pihole
ADD . $GITDIR
RUN cp $GITDIR/advanced/Scripts/*.sh $GITDIR/gravity.sh $GITDIR/pihole $GITDIR/automated\ install/*.sh $SCRIPTDIR/
ENV PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$SCRIPTDIR
RUN true && \
chmod +x $SCRIPTDIR/*
ENV SKIP_INSTALL true
ENV OS_CHECK_DOMAIN_NAME dev-supportedos.pi-hole.net
#sed '/# Start the installer/Q' /opt/pihole/basic-install.sh > /opt/pihole/stub_basic-install.sh && \

@ -1,4 +1,4 @@
FROM fedora:36 FROM fedora:39
RUN dnf install -y git initscripts RUN dnf install -y git initscripts
ENV GITDIR /etc/.pihole ENV GITDIR /etc/.pihole

@ -1,6 +1,6 @@
pyyaml == 6.0.1 pyyaml == 6.0.1
pytest == 7.4.3 pytest == 8.0.2
pytest-xdist == 3.4.0 pytest-xdist == 3.5.0
pytest-testinfra == 10.0.0 pytest-testinfra == 10.1.0
tox == 4.11.3 tox == 4.13.0

@ -12,6 +12,8 @@ from .conftest import (
run_script, run_script,
) )
FTL_BRANCH = "development-v6"
def test_supported_package_manager(host): def test_supported_package_manager(host):
""" """
@ -80,11 +82,7 @@ def test_installPihole_fresh_install_readableFiles(host):
host.run("command -v dnf > /dev/null && dnf install -y man") host.run("command -v dnf > /dev/null && dnf install -y man")
host.run("command -v yum > /dev/null && yum install -y man") host.run("command -v yum > /dev/null && yum install -y man")
# Workaround to get FTLv6 installed until it reaches master branch # Workaround to get FTLv6 installed until it reaches master branch
host.run( host.run('echo "' + FTL_BRANCH + '" > /etc/pihole/ftlbranch')
"""
echo "development-v6" > /etc/pihole/ftlbranch
"""
)
install = host.run( install = host.run(
""" """
export TERM=xterm export TERM=xterm
@ -174,10 +172,6 @@ def test_installPihole_fresh_install_readableFiles(host):
) )
actual_rc = host.run(check_man).rc actual_rc = host.run(check_man).rc
assert exit_status_success == actual_rc assert exit_status_success == actual_rc
# check not readable sudoers file
check_sudo = test_cmd.format("r", "/etc/sudoers.d/pihole", piholeuser)
actual_rc = host.run(check_sudo).rc
assert exit_status_success != actual_rc
# check not readable cron file # check not readable cron file
check_sudo = test_cmd.format("x", "/etc/cron.d/", piholeuser) check_sudo = test_cmd.format("x", "/etc/cron.d/", piholeuser)
actual_rc = host.run(check_sudo).rc actual_rc = host.run(check_sudo).rc
@ -235,144 +229,35 @@ def test_update_package_cache_failure_no_errors(host):
assert "Error: Unable to update package cache." in updateCache.stdout assert "Error: Unable to update package cache." in updateCache.stdout
def test_FTL_detect_aarch64_no_errors(host): @pytest.mark.parametrize(
""" "arch,detected_string,supported",
confirms only aarch64 package is downloaded for FTL engine [
""" ("aarch64", "AArch64 (64 Bit ARM)", True),
# mock uname to return aarch64 platform ("armv6", "ARMv6", True),
mock_command("uname", {"-m": ("aarch64", "0")}, host) ("armv7l", "ARMv7 (or newer)", True),
detectPlatform = host.run( ("armv7", "ARMv7 (or newer)", True),
""" ("armv8a", "ARMv7 (or newer)", True),
source /opt/pihole/basic-install.sh ("x86_64", "x86_64", True),
create_pihole_user ("riscv64", "riscv64", True),
funcOutput=$(get_binary_name) ("mips", "mips", False),
binary="pihole-FTL${funcOutput##*pihole-FTL}" ],
theRest="${funcOutput%pihole-FTL*}" )
FTLdetect "${binary}" "${theRest}" def test_FTL_detect_no_errors(host, arch, detected_string, supported):
"""
)
expected_stdout = info_box + " FTL Checks..."
assert expected_stdout in detectPlatform.stdout
expected_stdout = tick_box + " Detected AArch64 (64 Bit ARM) architecture"
assert expected_stdout in detectPlatform.stdout
expected_stdout = tick_box + " Downloading and Installing FTL"
assert expected_stdout in detectPlatform.stdout
def test_FTL_detect_armv6_no_errors(host):
"""
confirms only armv6 package is downloaded for FTL engine
"""
# mock uname to return armv6 platform
mock_command("uname", {"-m": ("armv6", "0")}, host)
# mock readelf to respond with armv6l CPU architecture
mock_command_2(
"readelf",
{
"-A /bin/sh": ("Tag_CPU_arch: armv6", "0"),
"-A /usr/bin/sh": ("Tag_CPU_arch: armv6", "0"),
},
host,
)
detectPlatform = host.run(
"""
source /opt/pihole/basic-install.sh
create_pihole_user
funcOutput=$(get_binary_name)
binary="pihole-FTL${funcOutput##*pihole-FTL}"
theRest="${funcOutput%pihole-FTL*}"
FTLdetect "${binary}" "${theRest}"
"""
)
expected_stdout = info_box + " FTL Checks..."
assert expected_stdout in detectPlatform.stdout
expected_stdout = tick_box + " Detected ARMv6 architecture"
assert expected_stdout in detectPlatform.stdout
expected_stdout = tick_box + " Downloading and Installing FTL"
assert expected_stdout in detectPlatform.stdout
def test_FTL_detect_armv7l_no_errors(host):
"""
confirms only armv7l package is downloaded for FTL engine
"""
# mock uname to return armv7l platform
mock_command("uname", {"-m": ("armv7l", "0")}, host)
# mock readelf to respond with armv7l CPU architecture
mock_command_2(
"readelf",
{
"-A /bin/sh": ("Tag_CPU_arch: armv7l", "0"),
"-A /usr/bin/sh": ("Tag_CPU_arch: armv7l", "0"),
},
host,
)
detectPlatform = host.run(
"""
source /opt/pihole/basic-install.sh
create_pihole_user
funcOutput=$(get_binary_name)
binary="pihole-FTL${funcOutput##*pihole-FTL}"
theRest="${funcOutput%pihole-FTL*}"
FTLdetect "${binary}" "${theRest}"
"""
)
expected_stdout = info_box + " FTL Checks..."
assert expected_stdout in detectPlatform.stdout
expected_stdout = tick_box + (" Detected ARMv7 (or newer) architecture")
assert expected_stdout in detectPlatform.stdout
expected_stdout = tick_box + " Downloading and Installing FTL"
assert expected_stdout in detectPlatform.stdout
def test_FTL_detect_armv7_no_errors(host):
"""
confirms only armv7 package is downloaded for FTL engine
"""
# mock uname to return armv7 platform
mock_command("uname", {"-m": ("armv7", "0")}, host)
# mock readelf to respond with armv7 CPU architecture
mock_command_2(
"readelf",
{
"-A /bin/sh": ("Tag_CPU_arch: armv7", "0"),
"-A /usr/bin/sh": ("Tag_CPU_arch: armv7", "0"),
},
host,
)
detectPlatform = host.run(
"""
source /opt/pihole/basic-install.sh
create_pihole_user
funcOutput=$(get_binary_name)
binary="pihole-FTL${funcOutput##*pihole-FTL}"
theRest="${funcOutput%pihole-FTL*}"
FTLdetect "${binary}" "${theRest}"
"""
)
expected_stdout = info_box + " FTL Checks..."
assert expected_stdout in detectPlatform.stdout
expected_stdout = tick_box + (" Detected ARMv7 (or newer) architecture")
assert expected_stdout in detectPlatform.stdout
expected_stdout = tick_box + " Downloading and Installing FTL"
assert expected_stdout in detectPlatform.stdout
def test_FTL_detect_armv8a_no_errors(host):
""" """
confirms only armv8a package is downloaded for FTL engine confirms only correct package is downloaded for FTL engine
""" """
# mock uname to return armv8a platform # mock uname to return passed platform
mock_command("uname", {"-m": ("armv8a", "0")}, host) mock_command("uname", {"-m": (arch, "0")}, host)
# mock readelf to respond with armv8a CPU architecture # mock readelf to respond with passed CPU architecture
mock_command_2( mock_command_2(
"readelf", "readelf",
{ {
"-A /bin/sh": ("Tag_CPU_arch: armv8a", "0"), "-A /bin/sh": ("Tag_CPU_arch: " + arch, "0"),
"-A /usr/bin/sh": ("Tag_CPU_arch: armv8a", "0"), "-A /usr/bin/sh": ("Tag_CPU_arch: " + arch, "0"),
}, },
host, host,
) )
host.run('echo "' + FTL_BRANCH + '" > /etc/pihole/ftlbranch')
detectPlatform = host.run( detectPlatform = host.run(
""" """
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
@ -383,89 +268,30 @@ def test_FTL_detect_armv8a_no_errors(host):
FTLdetect "${binary}" "${theRest}" FTLdetect "${binary}" "${theRest}"
""" """
) )
expected_stdout = info_box + " FTL Checks..." if supported:
assert expected_stdout in detectPlatform.stdout expected_stdout = info_box + " FTL Checks..."
expected_stdout = tick_box + " Detected ARMv7 (or newer) architecture (armv8a)" assert expected_stdout in detectPlatform.stdout
assert expected_stdout in detectPlatform.stdout expected_stdout = tick_box + " Detected " + detected_string + " architecture"
expected_stdout = tick_box + " Downloading and Installing FTL" assert expected_stdout in detectPlatform.stdout
assert expected_stdout in detectPlatform.stdout expected_stdout = tick_box + " Downloading and Installing FTL"
assert expected_stdout in detectPlatform.stdout
else:
def test_FTL_detect_x86_64_no_errors(host): expected_stdout = (
""" "Not able to detect architecture (unknown: " + detected_string + ")"
confirms only x86_64 package is downloaded for FTL engine )
""" assert expected_stdout in detectPlatform.stdout
detectPlatform = host.run(
"""
source /opt/pihole/basic-install.sh
create_pihole_user
funcOutput=$(get_binary_name)
binary="pihole-FTL${funcOutput##*pihole-FTL}"
theRest="${funcOutput%pihole-FTL*}"
FTLdetect "${binary}" "${theRest}"
"""
)
expected_stdout = info_box + " FTL Checks..."
assert expected_stdout in detectPlatform.stdout
expected_stdout = tick_box + " Detected x86_64 architecture"
assert expected_stdout in detectPlatform.stdout
expected_stdout = tick_box + " Downloading and Installing FTL"
assert expected_stdout in detectPlatform.stdout
def test_FTL_detect_unknown_no_errors(host):
"""confirms only generic package is downloaded for FTL engine"""
# mock uname to return generic platform
mock_command("uname", {"-m": ("mips", "0")}, host)
detectPlatform = host.run(
"""
source /opt/pihole/basic-install.sh
create_pihole_user
funcOutput=$(get_binary_name)
binary="pihole-FTL${funcOutput##*pihole-FTL}"
theRest="${funcOutput%pihole-FTL*}"
FTLdetect "${binary}" "${theRest}"
"""
)
expected_stdout = "Not able to detect architecture (unknown: mips)"
assert expected_stdout in detectPlatform.stdout
def test_FTL_download_aarch64_no_errors(host):
"""
confirms only aarch64 package is downloaded for FTL engine
"""
# mock dialog answers and ensure installer dependencies
mock_command("dialog", {"*": ("", "0")}, host)
host.run(
"""
source /opt/pihole/basic-install.sh
package_manager_detect
install_dependent_packages ${INSTALLER_DEPS[@]}
"""
)
download_binary = host.run(
"""
source /opt/pihole/basic-install.sh
create_pihole_user
FTLinstall "pihole-FTL-aarch64-linux-gnu"
"""
)
expected_stdout = tick_box + " Downloading and Installing FTL"
assert expected_stdout in download_binary.stdout
assert "error" not in download_binary.stdout.lower()
def test_FTL_development_binary_installed_and_responsive_no_errors(host): def test_FTL_development_binary_installed_and_responsive_no_errors(host):
""" """
confirms FTL development binary is copied and functional in installed location confirms FTL development binary is copied and functional in installed location
""" """
host.run('echo "' + FTL_BRANCH + '" > /etc/pihole/ftlbranch')
host.run( host.run(
""" """
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
create_pihole_user create_pihole_user
funcOutput=$(get_binary_name) funcOutput=$(get_binary_name)
echo "development" > /etc/pihole/ftlbranch
binary="pihole-FTL${funcOutput##*pihole-FTL}" binary="pihole-FTL${funcOutput##*pihole-FTL}"
theRest="${funcOutput%pihole-FTL*}" theRest="${funcOutput%pihole-FTL*}"
FTLdetect "${binary}" "${theRest}" FTLdetect "${binary}" "${theRest}"

@ -1,8 +0,0 @@
[tox]
envlist = py3
[testenv:py3]
allowlist_externals = docker
deps = -rrequirements.txt
commands = docker buildx build --load --progress plain -f _fedora_36.Dockerfile -t pytest_pihole:test_container ../
pytest {posargs:-vv -n auto} ./test_any_automated_install.py ./test_any_utils.py ./test_centos_fedora_common_support.py

@ -4,5 +4,5 @@ envlist = py3
[testenv] [testenv]
allowlist_externals = docker allowlist_externals = docker
deps = -rrequirements.txt deps = -rrequirements.txt
commands = docker buildx build --load --progress plain -f _fedora_37.Dockerfile -t pytest_pihole:test_container ../ commands = docker buildx build --load --progress plain -f _fedora_39.Dockerfile -t pytest_pihole:test_container ../
pytest {posargs:-vv -n auto} ./test_any_automated_install.py ./test_any_utils.py ./test_centos_fedora_common_support.py pytest {posargs:-vv -n auto} ./test_any_automated_install.py ./test_any_utils.py ./test_centos_fedora_common_support.py
Loading…
Cancel
Save