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.
-
name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: 'python'
-
name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3
-
name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3

@ -17,20 +17,23 @@ jobs:
issues: write
steps:
- uses: actions/stale@v8.0.0
- uses: actions/stale@v9.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 30
days-before-close: 5
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Please comment or update this issue or it will be closed in 5 days.'
stale-issue-label: $stale_label
stale-issue-label: '${{ env.stale_label }}'
exempt-issue-labels: 'Internal, Fixed in next release, Bug: Confirmed, Documentation Needed'
exempt-all-issue-assignees: true
operations-per-run: 300
close-issue-reason: 'not_planned'
remove_stale: # trigger "stale" removal immediately when stale issues are commented on
if: github.event_name == 'issue_comment'
remove_stale:
# trigger "stale" removal immediately when stale issues are commented on
# we need to explicitly check that the trigger does not run on comment on a PR as
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issue_comment-on-issues-only-or-pull-requests-only
if: ${{ !github.event.issue.pull_request && github.event_name != 'schedule' }}
permissions:
contents: read # for actions/checkout
issues: write # to edit issues label
@ -39,7 +42,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4.1.1
- 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:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

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

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

@ -21,20 +21,60 @@
TestAPIAvailability() {
# as we are running locally, we can get the port value from FTL directly
PORT="$(pihole-FTL --config webserver.port)"
PORT="${PORT%%,*}"
local chaos_api_list availabilityResonse
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 [ -z "${chaos_api_list}" ]; then
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
echo "API not available at: http://localhost:${PORT}/api"
# 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."
exit 1
fi
}
Authenthication() {
Authentication() {
# Try to authenticate
LoginAPI
@ -54,7 +94,7 @@ Authenthication() {
}
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
echo "No response from FTL server. Please check connectivity"
@ -66,16 +106,15 @@ LoginAPI() {
}
DeleteSession() {
# if a valid Session exists (no password required or successful authenthication) and
# SID is not null (successful authenthication only), delete the session
# if a valid Session exists (no password required or successful Authentication) and
# SID is not null (successful Authentication only), delete the session
if [ "${validSession}" = true ] && [ ! "${SID}" = null ]; then
# 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
"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";;
"410") printf "%b" "Session successfully deleted.\n";;
esac;
fi
@ -84,7 +123,7 @@ DeleteSession() {
GetFTLData() {
local data response status
# 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=$(printf %s "${response#"${response%???}"}")
@ -93,7 +132,7 @@ GetFTLData() {
if [ "${status}" = 200 ]; then
# response OK
echo "${data}"
printf %s "${data}"
elif [ "${status}" = 000 ]; then
# connection lost
echo "000"

@ -18,14 +18,19 @@ upgrade_gravityDB(){
piholeDir="${2}"
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
version="$(pihole-FTL sqlite3 "${database}" "SELECT \"value\" FROM \"info\" WHERE \"property\" = 'version';")"
version="$(pihole-FTL sqlite3 -ni "${database}" "SELECT \"value\" FROM \"info\" WHERE \"property\" = 'version';")"
if [[ "$version" == "1" ]]; then
# This migration script upgrades the gravity.db file by
# adding the domain_audit table
echo -e " ${INFO} Upgrading gravity database from version 1 to 2"
pihole-FTL sqlite3 "${database}" < "${scriptPath}/1_to_2.sql"
pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/1_to_2.sql"
version=2
# Store audit domains in database table
@ -40,28 +45,28 @@ upgrade_gravityDB(){
# renaming the regex table to regex_blacklist, and
# creating a new regex_whitelist table + corresponding linking table and views
echo -e " ${INFO} Upgrading gravity database from version 2 to 3"
pihole-FTL sqlite3 "${database}" < "${scriptPath}/2_to_3.sql"
pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/2_to_3.sql"
version=3
fi
if [[ "$version" == "3" ]]; then
# This migration script unifies the formally separated domain
# lists into a single table with a UNIQUE domain constraint
echo -e " ${INFO} Upgrading gravity database from version 3 to 4"
pihole-FTL sqlite3 "${database}" < "${scriptPath}/3_to_4.sql"
pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/3_to_4.sql"
version=4
fi
if [[ "$version" == "4" ]]; then
# This migration script upgrades the gravity and list views
# implementing necessary changes for per-client blocking
echo -e " ${INFO} Upgrading gravity database from version 4 to 5"
pihole-FTL sqlite3 "${database}" < "${scriptPath}/4_to_5.sql"
pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/4_to_5.sql"
version=5
fi
if [[ "$version" == "5" ]]; then
# This migration script upgrades the adlist view
# to return an ID used in gravity.sh
echo -e " ${INFO} Upgrading gravity database from version 5 to 6"
pihole-FTL sqlite3 "${database}" < "${scriptPath}/5_to_6.sql"
pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/5_to_6.sql"
version=6
fi
if [[ "$version" == "6" ]]; then
@ -69,7 +74,7 @@ upgrade_gravityDB(){
# which is automatically associated to all clients not
# having their own group assignments
echo -e " ${INFO} Upgrading gravity database from version 6 to 7"
pihole-FTL sqlite3 "${database}" < "${scriptPath}/6_to_7.sql"
pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/6_to_7.sql"
version=7
fi
if [[ "$version" == "7" ]]; then
@ -77,21 +82,21 @@ upgrade_gravityDB(){
# to ensure uniqueness on the group name
# We also add date_added and date_modified columns
echo -e " ${INFO} Upgrading gravity database from version 7 to 8"
pihole-FTL sqlite3 "${database}" < "${scriptPath}/7_to_8.sql"
pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/7_to_8.sql"
version=8
fi
if [[ "$version" == "8" ]]; then
# This migration fixes some issues that were introduced
# in the previous migration script.
echo -e " ${INFO} Upgrading gravity database from version 8 to 9"
pihole-FTL sqlite3 "${database}" < "${scriptPath}/8_to_9.sql"
pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/8_to_9.sql"
version=9
fi
if [[ "$version" == "9" ]]; then
# This migration drops unused tables and creates triggers to remove
# obsolete groups assignments when the linked items are deleted
echo -e " ${INFO} Upgrading gravity database from version 9 to 10"
pihole-FTL sqlite3 "${database}" < "${scriptPath}/9_to_10.sql"
pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/9_to_10.sql"
version=10
fi
if [[ "$version" == "10" ]]; then
@ -101,44 +106,57 @@ upgrade_gravityDB(){
# to keep the copying process generic (needs the same columns in both the
# source and the destination databases).
echo -e " ${INFO} Upgrading gravity database from version 10 to 11"
pihole-FTL sqlite3 "${database}" < "${scriptPath}/10_to_11.sql"
pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/10_to_11.sql"
version=11
fi
if [[ "$version" == "11" ]]; then
# Rename group 0 from "Unassociated" to "Default"
echo -e " ${INFO} Upgrading gravity database from version 11 to 12"
pihole-FTL sqlite3 "${database}" < "${scriptPath}/11_to_12.sql"
pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/11_to_12.sql"
version=12
fi
if [[ "$version" == "12" ]]; then
# Add column date_updated to adlist table
echo -e " ${INFO} Upgrading gravity database from version 12 to 13"
pihole-FTL sqlite3 "${database}" < "${scriptPath}/12_to_13.sql"
pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/12_to_13.sql"
version=13
fi
if [[ "$version" == "13" ]]; then
# Add columns number and status to adlist table
echo -e " ${INFO} Upgrading gravity database from version 13 to 14"
pihole-FTL sqlite3 "${database}" < "${scriptPath}/13_to_14.sql"
pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/13_to_14.sql"
version=14
fi
if [[ "$version" == "14" ]]; then
# Changes the vw_adlist created in 5_to_6
echo -e " ${INFO} Upgrading gravity database from version 14 to 15"
pihole-FTL sqlite3 "${database}" < "${scriptPath}/14_to_15.sql"
pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/14_to_15.sql"
version=15
fi
if [[ "$version" == "15" ]]; then
# Add column abp_entries to adlist table
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
fi
if [[ "$version" == "16" ]]; then
# Add antigravity table
# Add column type to adlist table (to support adlist types)
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
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"
# Is the domain in the list we want to add it to?
num="$(pihole-FTL sqlite3 "${gravityDBfile}" "SELECT COUNT(*) FROM domainlist WHERE domain = '${domain}';")"
num="$(pihole-FTL sqlite3 -ni "${gravityDBfile}" "SELECT COUNT(*) FROM domainlist WHERE domain = '${domain}';")"
requestedListname="$(GetListnameFromTypeId "${typeId}")"
if [[ "${num}" -ne 0 ]]; then
existingTypeId="$(pihole-FTL sqlite3 "${gravityDBfile}" "SELECT type FROM domainlist WHERE domain = '${domain}';")"
existingTypeId="$(pihole-FTL sqlite3 -ni "${gravityDBfile}" "SELECT type FROM domainlist WHERE domain = '${domain}';")"
if [[ "${existingTypeId}" == "${typeId}" ]]; then
if [[ "${verbose}" == true ]]; then
echo -e " ${INFO} ${1} already exists in ${requestedListname}, no need to add!"
fi
else
existingListname="$(GetListnameFromTypeId "${existingTypeId}")"
pihole-FTL sqlite3 "${gravityDBfile}" "UPDATE domainlist SET type = ${typeId} WHERE domain='${domain}';"
pihole-FTL sqlite3 -ni "${gravityDBfile}" "UPDATE domainlist SET type = ${typeId} WHERE domain='${domain}';"
if [[ "${verbose}" == true ]]; then
echo -e " ${INFO} ${1} already exists in ${existingListname}, it has been moved to ${requestedListname}!"
fi
@ -177,10 +177,10 @@ AddDomain() {
# Insert only the domain here. The enabled and date_added fields will be filled
# with their default values (enabled = true, date_added = current timestamp)
if [[ -z "${comment}" ]]; then
pihole-FTL sqlite3 "${gravityDBfile}" "INSERT INTO domainlist (domain,type) VALUES ('${domain}',${typeId});"
pihole-FTL sqlite3 -ni "${gravityDBfile}" "INSERT INTO domainlist (domain,type) VALUES ('${domain}',${typeId});"
else
# also add comment when variable has been set through the "--comment" option
pihole-FTL sqlite3 "${gravityDBfile}" "INSERT INTO domainlist (domain,type,comment) VALUES ('${domain}',${typeId},'${comment}');"
pihole-FTL sqlite3 -ni "${gravityDBfile}" "INSERT INTO domainlist (domain,type,comment) VALUES ('${domain}',${typeId},'${comment}');"
fi
}
@ -189,7 +189,7 @@ RemoveDomain() {
domain="$1"
# Is the domain in the list we want to remove it from?
num="$(pihole-FTL sqlite3 "${gravityDBfile}" "SELECT COUNT(*) FROM domainlist WHERE domain = '${domain}' AND type = ${typeId};")"
num="$(pihole-FTL sqlite3 -ni "${gravityDBfile}" "SELECT COUNT(*) FROM domainlist WHERE domain = '${domain}' AND type = ${typeId};")"
requestedListname="$(GetListnameFromTypeId "${typeId}")"
@ -206,14 +206,14 @@ RemoveDomain() {
fi
reload=true
# Remove it from the current list
pihole-FTL sqlite3 "${gravityDBfile}" "DELETE FROM domainlist WHERE domain = '${domain}' AND type = ${typeId};"
pihole-FTL sqlite3 -ni "${gravityDBfile}" "DELETE FROM domainlist WHERE domain = '${domain}' AND type = ${typeId};"
}
Displaylist() {
local count num_pipes domain enabled status nicedate requestedListname
requestedListname="$(GetListnameFromTypeId "${typeId}")"
data="$(pihole-FTL sqlite3 "${gravityDBfile}" "SELECT domain,enabled,date_modified FROM domainlist WHERE type = ${typeId};" 2> /dev/null)"
data="$(pihole-FTL sqlite3 -ni "${gravityDBfile}" "SELECT domain,enabled,date_modified FROM domainlist WHERE type = ${typeId};" 2> /dev/null)"
if [[ -z $data ]]; then
echo -e "Not showing empty list"
@ -251,10 +251,10 @@ Displaylist() {
}
NukeList() {
count=$(pihole-FTL sqlite3 "${gravityDBfile}" "SELECT COUNT(1) FROM domainlist WHERE type = ${typeId};")
count=$(pihole-FTL sqlite3 -ni "${gravityDBfile}" "SELECT COUNT(1) FROM domainlist WHERE type = ${typeId};")
listname="$(GetListnameFromTypeId "${typeId}")"
if [ "$count" -gt 0 ];then
pihole-FTL sqlite3 "${gravityDBfile}" "DELETE FROM domainlist WHERE type = ${typeId};"
pihole-FTL sqlite3 -ni "${gravityDBfile}" "DELETE FROM domainlist WHERE type = ${typeId};"
echo " ${TICK} Removed ${count} domain(s) from the ${listname}"
else
echo " ${INFO} ${listname} already empty. Nothing to do!"

@ -39,7 +39,7 @@ flushARP(){
# Truncate network_addresses table in pihole-FTL.db
# This needs to be done before we can truncate the network table due to
# foreign key constraints
if ! output=$(pihole-FTL sqlite3 "${DBFILE}" "DELETE FROM network_addresses" 2>&1); then
if ! output=$(pihole-FTL sqlite3 -ni "${DBFILE}" "DELETE FROM network_addresses" 2>&1); then
echo -e "${OVER} ${CROSS} Failed to truncate network_addresses table"
echo " Database location: ${DBFILE}"
echo " Output: ${output}"
@ -47,7 +47,7 @@ flushARP(){
fi
# Truncate network table in pihole-FTL.db
if ! output=$(pihole-FTL sqlite3 "${DBFILE}" "DELETE FROM network" 2>&1); then
if ! output=$(pihole-FTL sqlite3 -ni "${DBFILE}" "DELETE FROM network" 2>&1); then
echo -e "${OVER} ${CROSS} Failed to truncate network table"
echo " Database location: ${DBFILE}"
echo " Output: ${output}"

@ -164,7 +164,9 @@ checkout() {
path="${2}/${binary}"
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 "${2}" > /etc/pihole/ftlbranch
chmod 644 /etc/pihole/ftlbranch
@ -175,11 +177,19 @@ checkout() {
# Update local and remote versions via updatechecker
/opt/pihole/updatecheck.sh
else
if [[ $ret -eq 1 ]]; then
echo " ${CROSS} Requested branch \"${2}\" is not available"
ftlbranches=( $(git ls-remote https://github.com/pi-hole/ftl | grep 'heads' | sed 's/refs\/heads\///;s/ //g' | awk '{print $2}') )
echo -e " ${INFO} Available branches for FTL are:"
for e in "${ftlbranches[@]}"; do echo " - $e"; done
exit 1
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
else

@ -74,7 +74,6 @@ PIHOLE_CRON_FILE="${CRON_D_DIRECTORY}/pihole"
PIHOLE_INSTALL_LOG_FILE="${PIHOLE_DIRECTORY}/install.log"
PIHOLE_RAW_BLOCKLIST_FILES="${PIHOLE_DIRECTORY}/list.*"
PIHOLE_LOCAL_HOSTS_FILE="${PIHOLE_DIRECTORY}/local.list"
PIHOLE_LOGROTATE_FILE="${PIHOLE_DIRECTORY}/logrotate"
PIHOLE_FTL_CONF_FILE="${PIHOLE_DIRECTORY}/pihole.toml"
PIHOLE_VERSIONS_FILE="${PIHOLE_DIRECTORY}/versions"
@ -547,17 +546,24 @@ ping_gateway() {
ping_ipv4_or_ipv6 "${protocol}"
# Check if we are 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):"
while IFS= read -r gateway; do
log_write " ${gateway}"
done < <(ip -"${protocol}" route | grep default | cut -d ' ' -f 3)
log_write " $(cut -d ' ' -f 3 <<< "${gateway}")%$(cut -d ' ' -f 5 <<< "${gateway}")"
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 [ -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
log_write " * Pinging first gateway ${gateway}..."
# 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
# It will also give extra assurance that Pi-hole is correctly resolving and blocking domains
local random_url
random_url=$(pihole-FTL sqlite3 "${PIHOLE_GRAVITY_DB_FILE}" "SELECT domain FROM vw_gravity 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
if [ -z "${random_url}" ]; then
random_url="flurry.com"
@ -758,6 +764,11 @@ dig_at() {
addresses="$(ip address show dev "${iface}" | sed "/${sed_selector} /!d;s/^.*${sed_selector} //g;s/\/.*$//g;")"
if [ -n "${addresses}" ]; then
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
if local_dig="$(dig +tries=1 +time=2 -"${protocol}" "${random_url}" @"${local_address}" "${record_type}")"; then
# If it can, show success
@ -1064,7 +1075,7 @@ show_db_entries() {
IFS=$'\r\n'
local entries=()
mapfile -t entries < <(\
pihole-FTL sqlite3 "${PIHOLE_GRAVITY_DB_FILE}" \
pihole-FTL sqlite3 -ni "${PIHOLE_GRAVITY_DB_FILE}" \
-cmd ".headers on" \
-cmd ".mode column" \
-cmd ".width ${widths}" \
@ -1089,7 +1100,7 @@ show_FTL_db_entries() {
IFS=$'\r\n'
local entries=()
mapfile -t entries < <(\
pihole-FTL sqlite3 "${PIHOLE_FTL_DB_FILE}" \
pihole-FTL sqlite3 -ni "${PIHOLE_FTL_DB_FILE}" \
-cmd ".headers on" \
-cmd ".mode column" \
-cmd ".width ${widths}" \
@ -1155,7 +1166,7 @@ analyze_gravity_list() {
fi
show_db_entries "Info table" "SELECT property,value FROM info" "20 40"
gravity_updated_raw="$(pihole-FTL sqlite3 "${PIHOLE_GRAVITY_DB_FILE}" "SELECT value FROM info where property = 'updated'")"
gravity_updated_raw="$(pihole-FTL sqlite3 -ni "${PIHOLE_GRAVITY_DB_FILE}" "SELECT value FROM info where property = 'updated'")"
gravity_updated="$(date -d @"${gravity_updated_raw}")"
log_write " Last gravity run finished at: ${COL_CYAN}${gravity_updated}${COL_NC}"
log_write ""
@ -1163,7 +1174,7 @@ analyze_gravity_list() {
OLD_IFS="$IFS"
IFS=$'\r\n'
local gravity_sample=()
mapfile -t gravity_sample < <(pihole-FTL sqlite3 "${PIHOLE_GRAVITY_DB_FILE}" "SELECT domain FROM vw_gravity LIMIT 10")
mapfile -t gravity_sample < <(pihole-FTL sqlite3 -ni "${PIHOLE_GRAVITY_DB_FILE}" "SELECT domain FROM vw_gravity LIMIT 10")
log_write " ${COL_CYAN}----- First 10 Gravity Domains -----${COL_NC}"
for line in "${gravity_sample[@]}"; do
@ -1195,7 +1206,7 @@ database_integrity_check(){
log_write "${INFO} Checking foreign key constraints of ${database} ... (this can take several minutes)"
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
log_write "${TICK} No foreign key errors in ${database}"
else

@ -63,7 +63,7 @@ else
fi
fi
# Delete most recent 24 hours from FTL's database, leave even older data intact (don't wipe out all history)
deleted=$(pihole-FTL sqlite3 "${DBFILE}" "DELETE FROM query_storage WHERE timestamp >= strftime('%s','now')-86400; select changes() from query_storage limit 1")
deleted=$(pihole-FTL sqlite3 -ni "${DBFILE}" "DELETE FROM query_storage WHERE timestamp >= strftime('%s','now')-86400; select changes() from query_storage limit 1")
# Restart pihole-FTL to force reloading history
sudo pihole restartdns

@ -39,21 +39,20 @@ Options:
exit 0
}
GenerateOutput() {
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}"
# 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
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
num_gravity=$(echo "${gravity_data}" | jq length )
num_lists=$(echo "${lists_data}" | jq length )
num_gravity=$(printf %s "${gravity_data}" | jq length)
num_lists=$(printf %s "${lists_data}" | jq length)
if [ "${partial}" = true ]; then
search_type_str="partially"
@ -66,7 +65,7 @@ GenerateOutput(){
if [ "${num_lists}" -gt 0 ]; then
# 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
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 ','
echo "${lists_data_csv}" | while read -r line; do
@ -79,15 +78,27 @@ GenerateOutput(){
if [ "${num_gravity}" -gt 0 ]; then
# 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
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
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
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#*,}
# print each domain and remove it from the string until nothing is left
while [ ${#line} -gt 0 ]; do
@ -107,13 +118,13 @@ Main(){
local data
if [ -z "${domain}" ]; then
echo "No domain specified"; exit 1
echo "No domain specified"
exit 1
fi
# domains are lowercased and converted to punycode by FTL since
# https://github.com/pi-hole/FTL/pull/1715
# no need to do it here
# Test if the authentication endpoint is available
TestAPIAvailability
@ -121,14 +132,14 @@ Main(){
# 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
data=$(GetFTLData "/search/${domain}?N=${max_results}&partial=${partial}")
data=$(GetFTLData "search/${domain}?N=${max_results}&partial=${partial}")
if [ "${data}" = 401 ]; then
# Unauthenticated, so authenticate with the FTL server required
Authenthication
Authentication
# send query again
data=$(GetFTLData "/search/${domain}?N=${max_results}&partial=${partial}")
data=$(GetFTLData "search/${domain}?N=${max_results}&partial=${partial}")
fi
GenerateOutput "${data}"

@ -144,7 +144,7 @@ main() {
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)
if FTLcheckUpdate "${binary}" > /dev/null; then
if FTLcheckUpdate "${binary}"; then
FTL_update=true
echo -e " ${INFO} FTL:\\t\\t${COL_YELLOW}update available${COL_NC}"
else
@ -155,8 +155,13 @@ main() {
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."
;;
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}"
exit 1
esac
FTL_update=false
fi

@ -26,10 +26,14 @@ function get_local_hash() {
}
function get_remote_version() {
# 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() {
git ls-remote "https://github.com/pi-hole/${1}" --tags "${2}" | awk '{print substr($0, 1,8);}' || return 1
}
@ -61,7 +65,6 @@ if [[ "$1" == "reboot" ]]; then
sleep 30
fi
# get Core versions
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)"
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}"
GITHUB_CORE_HASH="$(get_remote_hash pi-hole "${CORE_BRANCH}")"
addOrEditKeyValPair "${VERSION_FILE}" "GITHUB_CORE_HASH" "${GITHUB_CORE_HASH}"
# get Web versions
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)"
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}"
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)"
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}"
GITHUB_FTL_HASH="$(get_remote_hash FTL "${FTL_BRANCH}")"
addOrEditKeyValPair "${VERSION_FILE}" "GITHUB_FTL_HASH" "${GITHUB_FTL_HASH}"
# get Docker versions
if [[ "${DOCKER_TAG}" ]]; then

@ -8,6 +8,10 @@
# This file is copyright under the latest version of the EUPL.
# 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
cachedVersions="/etc/pihole/versions"
@ -21,118 +25,34 @@ else
. "$cachedVersions"
fi
getLocalVersion() {
case ${1} in
"Pi-hole" ) echo "${CORE_VERSION:=N/A}";;
"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() {
main() {
local details
details=false
[ "$2" = "-c" ] || [ "$2" = "--current" ] || [ -z "$2" ] && current=$(getLocalVersion "${1}") && branch=$(getLocalBranch "${1}")
[ "$2" = "-l" ] || [ "$2" = "--latest" ] || [ -z "$2" ] && latest=$(getRemoteVersion "${1}")
if [ "$2" = "--hash" ]; then
[ "$3" = "-c" ] || [ "$3" = "--current" ] || [ -z "$3" ] && curHash=$(getLocalHash "${1}") && branch=$(getLocalBranch "${1}")
[ "$3" = "-l" ] || [ "$3" = "--latest" ] || [ -z "$3" ] && latHash=$(getRemoteHash "${1}") && branch=$(getLocalBranch "${1}")
# Automatically show detailed information if
# at least one of the components is not on master branch
if [ ! "${CORE_BRANCH}" = "master" ] || [ ! "${WEB_BRANCH}" = "master" ] || [ ! "${FTL_BRANCH}" = "master" ]; then
details=true
fi
# We do not want to show the branch name when we are on master,
# blank out the variable in this case
if [ "$branch" = "master" ]; then
branch=""
if [ "${details}" = true ]; then
echo "Core"
echo " Version is ${CORE_VERSION:=N/A} (Latest: ${GITHUB_CORE_VERSION:=N/A})"
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
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
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
"-p" | "--pihole" ) shift; versionOutput "Pi-hole" "$@";;
"-a" | "--admin" ) shift; versionOutput "web" "$@";;
"-f" | "--ftl" ) shift; versionOutput "FTL" "$@";;
"-h" | "--help" ) helpFunc;;
* ) defaultOutput "$@";;
esac
main

@ -27,7 +27,7 @@ CREATE TABLE domainlist
CREATE TABLE adlist
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
address TEXT UNIQUE NOT NULL,
address TEXT NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT 1,
date_added INTEGER NOT NULL DEFAULT (cast(strftime('%s', 'now') as int)),
date_modified INTEGER NOT NULL DEFAULT (cast(strftime('%s', 'now') as int)),
@ -37,7 +37,8 @@ CREATE TABLE adlist
invalid_domains INTEGER NOT NULL DEFAULT 0,
status 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
@ -65,7 +66,7 @@ CREATE TABLE info
value TEXT NOT NULL
);
INSERT INTO "info" VALUES('version','17');
INSERT INTO "info" VALUES('version','18');
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
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
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);
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
LEFT JOIN adlist_by_group ON adlist_by_group.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() {
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=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
@ -7,17 +7,13 @@ _pihole() {
case "${prev}" in
"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}) )
;;
"whitelist"|"blacklist"|"wildcard"|"regex")
opts_lists="\--delmode \--noreload \--quiet \--list \--nuke"
COMPREPLY=( $(compgen -W "${opts_lists}" -- ${cur}) )
;;
"admin")
opts_admin="celsius fahrenheit interface kelvin password privacylevel"
COMPREPLY=( $(compgen -W "${opts_admin}" -- ${cur}) )
;;
"checkout")
opts_checkout="core ftl web master dev"
COMPREPLY=( $(compgen -W "${opts_checkout}" -- ${cur}) )
@ -31,33 +27,13 @@ _pihole() {
COMPREPLY=( $(compgen -W "${opts_logging}" -- ${cur}) )
;;
"query")
opts_query="-adlist -all -exact"
opts_query="--partial --all"
COMPREPLY=( $(compgen -W "${opts_query}" -- ${cur}) )
;;
"updatePihole"|"-up")
opts_update="--check-only"
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")
if [[ "$prev2" == "checkout" ]]; then
opts_checkout="master dev"

@ -39,9 +39,9 @@ export PATH+=':/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
: "${DIALOG_CANCEL:=1}"
: "${DIALOG_ESC:=255}"
# List of supported DNS servers
DNS_SERVERS=$(cat << EOM
DNS_SERVERS=$(
cat <<EOM
Google (ECS, DNSSEC);8.8.8.8;8.8.4.4;2001:4860:4860:0:0:0:0:8888;2001:4860:4860:0:0:0:0:8844
OpenDNS (ECS, DNSSEC);208.67.222.222;208.67.220.220;2620:119:35::35;2620:119:53::53
Level3;4.2.2.1;4.2.2.2;;
@ -62,7 +62,6 @@ coltable="/opt/pihole/COL_TABLE"
# Root of the web server
webroot="/var/www/html"
# We clone (or update) two git repositories during the install. This helps to make sure that we always have the latest versions of the relevant files.
# web is used to set up the Web admin interface.
# Pi-hole contains various setup scripts and files which are critical to the installation.
@ -176,7 +175,10 @@ os_check() {
detected_version=$(grep VERSION_ID /etc/os-release | cut -d '=' -f2 | tr -d '"')
# Test via IPv4
cmdResult="$(dig -4 +short -t txt "${remote_os_domain}" @ns1.pi-hole.net 2>&1; echo $?)"
cmdResult="$(
dig -4 +short -t txt "${remote_os_domain}" @ns1.pi-hole.net 2>&1
echo $?
)"
# Gets the return code of the previous command (last line)
digReturnCode="${cmdResult##*$'\n'}"
@ -197,7 +199,10 @@ os_check() {
if [ "$valid_response" = false ]; then
unset valid_response
cmdResult="$(dig -6 +short -t txt "${remote_os_domain}" @ns1.pi-hole.net 2>&1; echo $?)"
cmdResult="$(
dig -6 +short -t txt "${remote_os_domain}" @ns1.pi-hole.net 2>&1
echo $?
)"
# Gets the return code of the previous command (last line)
digReturnCode="${cmdResult##*$'\n'}"
@ -217,8 +222,7 @@ os_check() {
if [ "$valid_response" = true ]; then
IFS=" " read -r -a supportedOS < <(echo "${response}" | tr -d '"')
for distro_and_versions in "${supportedOS[@]}"
do
for distro_and_versions in "${supportedOS[@]}"; do
distro_part="${distro_and_versions%%=*}"
versions_part="${distro_and_versions##*=}"
@ -226,8 +230,7 @@ os_check() {
if [[ "${detected_os^^}" =~ ${distro_part^^} ]]; then
valid_os=true
IFS="," read -r -a supportedVer <<<"${versions_part}"
for version in "${supportedVer[@]}"
do
for version in "${supportedVer[@]}"; do
if [[ "${detected_version}" =~ $version ]]; then
valid_version=true
break
@ -292,8 +295,7 @@ test_dpkg_lock() {
printf " %b Waiting for package manager to finish (up to 30 seconds)\\n" "${INFO}"
# fuser is a program to show which processes use the named files, sockets, or filesystems
# So while the lock is held,
while fuser /var/lib/dpkg/lock >/dev/null 2>&1
do
while fuser /var/lib/dpkg/lock >/dev/null 2>&1; do
# we wait half a second,
sleep 0.5
# increase the iterator,
@ -302,7 +304,7 @@ test_dpkg_lock() {
if [[ $i -gt 60 ]]; then
printf " %b %bError: Could not verify package manager finished and released lock. %b\\n" "${CROSS}" "${COL_LIGHT_RED}" "${COL_NC}"
printf " Attempt to install packages manually and retry.\\n"
exit 1;
exit 1
fi
done
# and then report success once dpkg is unlocked.
@ -332,7 +334,7 @@ package_manager_detect() {
# Packages required to run this install script
INSTALLER_DEPS=(git iproute2 dialog ca-certificates)
# Packages required to run Pi-hole
PIHOLE_DEPS=(cron curl iputils-ping psmisc sudo unzip libcap2-bin dns-root-data libcap2 netcat-openbsd procps jq lshw)
PIHOLE_DEPS=(cron curl iputils-ping psmisc sudo unzip libcap2-bin dns-root-data libcap2 netcat-openbsd procps jq lshw bash-completion)
# If apt-get is not found, check for rpm.
elif is_command rpm; then
@ -349,7 +351,7 @@ package_manager_detect() {
PKG_COUNT="${PKG_MANAGER} check-update | grep -E '(.i686|.x86|.noarch|.arm|.src|.riscv64)' | wc -l || true"
OS_CHECK_DEPS=(grep bind-utils)
INSTALLER_DEPS=(git dialog iproute newt procps-ng chkconfig ca-certificates binutils)
PIHOLE_DEPS=(cronie curl findutils sudo unzip psmisc libcap nmap-ncat jq lshw)
PIHOLE_DEPS=(cronie curl findutils sudo unzip psmisc libcap nmap-ncat jq lshw bash-completion)
# If neither apt-get or yum/dnf package managers were found
else
@ -475,13 +477,19 @@ getGitFiles() {
# Show that we're checking it
printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}"
# Update the repo, returning an error message on failure
update_repo "${directory}" || { printf "\\n %b: Could not update local repository. Contact support.%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"; exit 1; }
update_repo "${directory}" || {
printf "\\n %b: Could not update local repository. Contact support.%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"
exit 1
}
# If it's not a .git repo,
else
# Show an error
printf "%b %b %s\\n" "${OVER}" "${CROSS}" "${str}"
# Attempt to make the repository, showing an error on failure
make_repo "${directory}" "${remoteRepo}" || { printf "\\n %bError: Could not update local repository. Contact support.%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"; exit 1; }
make_repo "${directory}" "${remoteRepo}" || {
printf "\\n %bError: Could not update local repository. Contact support.%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"
exit 1
}
fi
echo ""
# Success via one of the two branches, as the commands would exit if they failed.
@ -699,9 +707,9 @@ valid_ip() {
# Regex matching one IPv4 component, i.e. an integer from 0 to 255.
# See https://tools.ietf.org/html/rfc1340
local ipv4elem="(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)";
local ipv4elem="(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)"
# Regex matching an optional port (starting with '#') range of 1-65536
local portelem="(#(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{0,3}|0))?";
local portelem="(#(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{0,3}|0))?"
# Build a full IPv4 regex from the above subexpressions
local regex="^${ipv4elem}\\.${ipv4elem}\\.${ipv4elem}\\.${ipv4elem}${portelem}$"
@ -721,7 +729,7 @@ valid_ip6() {
# Regex matching an IPv6 CIDR, i.e. 1 to 128
local v6cidr="(\\/([1-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])){0,1}"
# Regex matching an optional port (starting with '#') range of 1-65536
local portelem="(#(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{0,3}|0))?";
local portelem="(#(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{0,3}|0))?"
# Build a full IPv6 regex from the above subexpressions
local regex="^(((${ipv6elem}))*((:${ipv6elem}))*::((${ipv6elem}))*((:${ipv6elem}))*|((${ipv6elem}))((:${ipv6elem})){7})${v6cidr}${portelem}$"
@ -745,8 +753,7 @@ setDNS() {
# and set the new one to newline
IFS=$'\n'
# Put the DNS Servers into an array
for DNSServer in ${DNS_SERVERS}
do
for DNSServer in ${DNS_SERVERS}; do
DNSName="$(cut -d';' -f1 <<<"${DNSServer}")"
DNSChooseOptions[DNSServerCount]="${DNSName}"
((DNSServerCount = DNSServerCount + 1))
@ -773,8 +780,7 @@ setDNS() {
esac
# Depending on the user's choice, set the GLOBAL variables to the IP of the respective provider
if [[ "${DNSchoices}" == "Custom" ]]
then
if [[ "${DNSchoices}" == "Custom" ]]; then
# Loop until we have a valid DNS setting
until [[ "${DNSSettingsCorrect}" = True ]]; do
# Signal value, to be used if the user inputs an invalid IP address
@ -870,11 +876,9 @@ If you want to specify a port other than 53, separate it with a hash.\
OIFS=$IFS
# and set the new one to newline
IFS=$'\n'
for DNSServer in ${DNS_SERVERS}
do
for DNSServer in ${DNS_SERVERS}; do
DNSName="$(cut -d';' -f1 <<<"${DNSServer}")"
if [[ "${DNSchoices}" == "${DNSName}" ]]
then
if [[ "${DNSchoices}" == "${DNSName}" ]]; then
PIHOLE_DNS_1="$(cut -d';' -f2 <<<"${DNSServer}")"
PIHOLE_DNS_2="$(cut -d';' -f3 <<<"${DNSServer}")"
break
@ -989,7 +993,7 @@ installDefaultBlocklists() {
# In unattended setup, could be useful to use userdefined blocklist.
# If this file exists, we avoid overriding it.
if [[ -f "${adlistFile}" ]]; then
return;
return
fi
echo "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts" >>"${adlistFile}"
}
@ -1033,7 +1037,6 @@ remove_old_pihole_lighttpd_configs() {
local confavailable="/etc/lighttpd/conf-available/15-pihole-admin.conf"
local confenabled="/etc/lighttpd/conf-enabled/15-pihole-admin.conf"
if [[ -f "${lighttpdConfig}" ]]; then
sed -i '/include "\/etc\/lighttpd\/conf.d\/pihole-admin.conf"/d' "${lighttpdConfig}"
fi
@ -1364,9 +1367,9 @@ install_dependent_packages() {
# Running apt-get install with minimal output can cause some issues with
# requiring user input (e.g password for phpmyadmin see #218)
printf " %b Processing %s install(s) for: %s, please wait...\\n" "${INFO}" "${PKG_MANAGER}" "${installArray[*]}"
printf '%*s\n' "${c}" '' | tr " " -;
printf '%*s\n' "${c}" '' | tr " " -
"${PKG_INSTALL[@]}" "${installArray[@]}"
printf '%*s\n' "${c}" '' | tr " " -;
printf '%*s\n' "${c}" '' | tr " " -
return
fi
printf "\\n"
@ -1387,9 +1390,9 @@ install_dependent_packages() {
# If there's anything to install, install everything in the list.
if [[ "${#installArray[@]}" -gt 0 ]]; then
printf " %b Processing %s install(s) for: %s, please wait...\\n" "${INFO}" "${PKG_MANAGER}" "${installArray[*]}"
printf '%*s\n' "${c}" '' | tr " " -;
printf '%*s\n' "${c}" '' | tr " " -
"${PKG_INSTALL[@]}" "${installArray[@]}"
printf '%*s\n' "${c}" '' | tr " " -;
printf '%*s\n' "${c}" '' | tr " " -
return
fi
printf "\\n"
@ -1592,7 +1595,7 @@ checkSelinux() {
;;
esac
else
echo -e " ${INFO} ${COL_GREEN}SELinux not detected${COL_NC}";
echo -e " ${INFO} ${COL_GREEN}SELinux not detected${COL_NC}"
fi
# Exit the installer if any SELinux checks toggled the flag
if [[ "${SELINUX_ENFORCING}" -eq 1 ]] && [[ -z "${PIHOLE_SELINUX}" ]]; then
@ -1601,8 +1604,8 @@ checkSelinux() {
printf " This check can be skipped by setting the environment variable %bPIHOLE_SELINUX%b to %btrue%b\\n" "${COL_LIGHT_RED}" "${COL_NC}" "${COL_LIGHT_RED}" "${COL_NC}"
printf " e.g: export PIHOLE_SELINUX=true\\n"
printf " By setting this variable to true you acknowledge there may be issues with Pi-hole during or after the install\\n"
printf "\\n %bSELinux Enforcing detected, exiting installer%b\\n" "${COL_LIGHT_RED}" "${COL_NC}";
exit 1;
printf "\\n %bSELinux Enforcing detected, exiting installer%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"
exit 1
elif [[ "${SELINUX_ENFORCING}" -eq 1 ]] && [[ -n "${PIHOLE_SELINUX}" ]]; then
printf " %b %bSELinux Enforcing detected%b. PIHOLE_SELINUX env variable set - installer will continue\\n" "${INFO}" "${COL_LIGHT_RED}" "${COL_NC}"
fi
@ -1626,7 +1629,6 @@ displayFinalMessage() {
# Store a message in a variable and display it
additional="View the web interface at http://pi.hole/admin:${WEBPORT} or http://${IPV4_ADDRESS%/*}:${WEBPORT}/admin\\n\\nYour Admin Webpage login password is ${pwstring}"
# Final completion message to user
dialog --no-shadow --keep-tite \
--title "Installation Complete!" \
@ -1687,12 +1689,19 @@ update_dialogs() {
}
check_download_exists() {
# Check if the download exists and we can reach the server
status=$(curl --head --silent "https://ftl.pi-hole.net/${1}" | head -n 1)
if grep -q "404" <<< "$status"; then
return 1
else
# Check the status code
if grep -q "200" <<<"$status"; then
return 0
elif grep -q "404" <<<"$status"; then
return 1
fi
# Other error or no status code at all, e.g., no Internet, server not
# available/reachable, ...
return 2
}
fully_fetch_repo() {
@ -1770,26 +1779,30 @@ clone_or_update_repos() {
if [[ "${reconfigure}" == true ]]; then
printf " %b Performing reconfiguration, skipping download of local repos\\n" "${INFO}"
# Reset the Core repo
resetRepo ${PI_HOLE_LOCAL_REPO} || \
{ printf " %b Unable to reset %s, exiting installer%b\\n" "${COL_LIGHT_RED}" "${PI_HOLE_LOCAL_REPO}" "${COL_NC}"; \
exit 1; \
resetRepo ${PI_HOLE_LOCAL_REPO} ||
{
printf " %b Unable to reset %s, exiting installer%b\\n" "${COL_LIGHT_RED}" "${PI_HOLE_LOCAL_REPO}" "${COL_NC}"
exit 1
}
# Reset the Web repo
resetRepo ${webInterfaceDir} || \
{ printf " %b Unable to reset %s, exiting installer%b\\n" "${COL_LIGHT_RED}" "${webInterfaceDir}" "${COL_NC}"; \
exit 1; \
resetRepo ${webInterfaceDir} ||
{
printf " %b Unable to reset %s, exiting installer%b\\n" "${COL_LIGHT_RED}" "${webInterfaceDir}" "${COL_NC}"
exit 1
}
# Otherwise, a repair is happening
else
# so get git files for Core
getGitFiles ${PI_HOLE_LOCAL_REPO} ${piholeGitUrl} || \
{ printf " %b Unable to clone %s into %s, unable to continue%b\\n" "${COL_LIGHT_RED}" "${piholeGitUrl}" "${PI_HOLE_LOCAL_REPO}" "${COL_NC}"; \
exit 1; \
getGitFiles ${PI_HOLE_LOCAL_REPO} ${piholeGitUrl} ||
{
printf " %b Unable to clone %s into %s, unable to continue%b\\n" "${COL_LIGHT_RED}" "${piholeGitUrl}" "${PI_HOLE_LOCAL_REPO}" "${COL_NC}"
exit 1
}
# get the Web git files
getGitFiles ${webInterfaceDir} ${webInterfaceGitUrl} || \
{ printf " %b Unable to clone %s into ${webInterfaceDir}, exiting installer%b\\n" "${COL_LIGHT_RED}" "${webInterfaceGitUrl}" "${COL_NC}"; \
exit 1; \
getGitFiles ${webInterfaceDir} ${webInterfaceGitUrl} ||
{
printf " %b Unable to clone %s into ${webInterfaceDir}, exiting installer%b\\n" "${COL_LIGHT_RED}" "${webInterfaceGitUrl}" "${COL_NC}"
exit 1
}
fi
}
@ -1803,7 +1816,10 @@ FTLinstall() {
printf " %b %s..." "${INFO}" "${str}"
# Move into the temp ftl directory
pushd "$(mktemp -d)" > /dev/null || { printf "Unable to make temporary directory for FTL binary download\\n"; return 1; }
pushd "$(mktemp -d)" >/dev/null || {
printf "Unable to make temporary directory for FTL binary download\\n"
return 1
}
local tempdir
tempdir="$(pwd)"
local ftlBranch
@ -1843,7 +1859,10 @@ FTLinstall() {
install -T -m 0755 "${binary}" /usr/bin/pihole-FTL
# Move back into the original directory the user was in
popd > /dev/null || { printf "Unable to return to original directory after FTL binary download.\\n"; return 1; }
popd >/dev/null || {
printf "Unable to return to original directory after FTL binary download.\\n"
return 1
}
# Installed the FTL service
printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}"
@ -1854,7 +1873,10 @@ FTLinstall() {
return 0
else
# Otherwise, the hash download failed, so print and exit.
popd > /dev/null || { printf "Unable to return to original directory after FTL binary download.\\n"; return 1; }
popd >/dev/null || {
printf "Unable to return to original directory after FTL binary download.\\n"
return 1
}
printf "%b %b %s\\n" "${OVER}" "${CROSS}" "${str}"
printf " %b Error: Download of %s/%s failed (checksum error)%b\\n" "${COL_LIGHT_RED}" "${url}" "${binary}" "${COL_NC}"
@ -1864,7 +1886,10 @@ FTLinstall() {
fi
else
# Otherwise, the download failed, so print and exit.
popd > /dev/null || { printf "Unable to return to original directory after FTL binary download.\\n"; return 1; }
popd >/dev/null || {
printf "Unable to return to original directory after FTL binary download.\\n"
return 1
}
printf "%b %b %s\\n" "${OVER}" "${CROSS}" "${str}"
# The URL could not be found
printf " %b Error: URL %s/%s not found%b\\n" "${COL_LIGHT_RED}" "${url}" "${binary}" "${COL_NC}"
@ -1877,7 +1902,7 @@ FTLinstall() {
remove_dir() {
# Delete dir
rm -r "${1}" > /dev/null 2>&1 || \
rm -r "${1}" >/dev/null 2>&1 ||
echo -e " ${CROSS} Unable to remove ${1}"
}
@ -1959,8 +1984,6 @@ get_binary_name() {
FTLcheckUpdate() {
# In the next section we check to see if FTL is already installed (in case of pihole -r).
# If the installed version matches the latest version, then check the installed sha1sum of the binary vs the remote sha1sum. If they do not match, then download
printf " %b Checking for existing FTL binary...\\n" "${INFO}"
local ftlLoc
ftlLoc=$(command -v pihole-FTL 2>/dev/null)
@ -1983,10 +2006,20 @@ FTLcheckUpdate() {
local path
path="${ftlBranch}/${binary}"
# shellcheck disable=SC1090
if ! check_download_exists "$path"; then
check_download_exists "$path"
local ret=$?
if [ $ret -ne 0 ]; then
if [[ $ret -eq 1 ]]; then
printf " %b Branch \"%s\" is not available.\\n" "${INFO}" "${ftlBranch}"
printf " %b Use %bpihole checkout ftl [branchname]%b to switch to a valid branch.\\n" "${INFO}" "${COL_LIGHT_GREEN}" "${COL_NC}"
return 2
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}"
return 3
else
printf " %b Unknown error. Please contact Pi-hole Support\\n" "${CROSS}"
return 4
fi
fi
if [[ ${ftlLoc} ]]; then
@ -2011,12 +2044,14 @@ FTLcheckUpdate() {
FTLversion=$(/usr/bin/pihole-FTL tag)
local FTLlatesttag
# Get the latest version from the GitHub API
if ! FTLlatesttag=$(curl -sI https://github.com/pi-hole/FTL/releases/latest | grep --color=never -i Location: | awk -F / '{print $NF}' | tr -d '[:cntrl:]'); then
# There was an issue while retrieving the latest version
printf " %b Failed to retrieve latest FTL release metadata" "${CROSS}"
return 3
fi
# Check if the installed version matches the latest version
if [[ "${FTLversion}" != "${FTLlatesttag}" ]]; then
return 0
else
@ -2222,9 +2257,14 @@ main() {
# Check for and disable systemd-resolved-DNSStubListener before reloading resolved
# DNSStubListener needs to remain in place for installer to download needed files,
# so this change needs to be made after installation is complete,
# but before starting or restarting the ftl service
# but before starting or resttarting the ftl service
disable_resolved_stublistener
# Check if gravity database needs to be upgraded. If so, do it without rebuilding
# gravity altogether. This may be a very long running task needlessly blocking
# the update process.
/opt/pihole/gravity.sh --upgrade
printf " %b Restarting services...\\n" "${INFO}"
# Start services

@ -36,21 +36,16 @@ blacklistFile="${piholeDir}/blacklist.txt"
regexFile="${piholeDir}/regex.list"
adListFile="${piholeDir}/adlists.list"
localList="${piholeDir}/local.list"
VPNList="/etc/openvpn/ipp.txt"
piholeGitDir="/etc/.pihole"
GRAVITYDB=$(getFTLConfigValue files.gravity)
GRAVITY_TMPDIR=$(getFTLConfigValue files.gravity_tmp)
gravityDBschema="${piholeGitDir}/advanced/Templates/gravity.db.sql"
gravityDBcopy="${piholeGitDir}/advanced/Templates/gravity_copy.sql"
domainsExtension="domains"
curl_connect_timeout=10
# Set up tmp dir variable in case it's not configured
: "${GRAVITY_TMPDIR:=/tmp}"
# Check gravity temp directory
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}"
GRAVITY_TMPDIR="/tmp"
@ -66,7 +61,7 @@ gravityOLDfile="${gravityDIR}/gravity_old.db"
# Generate new SQLite3 file from schema template
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}"
return 1
fi
@ -81,7 +76,7 @@ gravity_build_tree() {
echo -ne " ${INFO} ${str}..."
# 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="$?"
if [[ "${status}" -ne 0 ]]; then
@ -120,7 +115,7 @@ gravity_swap_databases() {
# Update timestamp when the gravity table was last updated successfully
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="$?"
if [[ "${status}" -ne 0 ]]; then
@ -165,7 +160,7 @@ database_table_from_file() {
# Get MAX(id) from domainlist when INSERTing into this table
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
rowid=0
fi
@ -174,8 +169,7 @@ database_table_from_file() {
# Loop over all domains in ${src} file
# Read file line by line
grep -v '^ *#' < "${src}" | while IFS= read -r domain
do
grep -v '^ *#' <"${src}" | while IFS= read -r domain; do
# Only add non-empty lines
if [[ -n "${domain}" ]]; then
if [[ "${table}" == "domain_audit" ]]; then
@ -195,7 +189,7 @@ database_table_from_file() {
# Store domains in database table specified by ${table}
# Use printf as .mode and .import need to be on separate lines
# 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="$?"
if [[ "${status}" -ne 0 ]]; then
@ -205,17 +199,17 @@ database_table_from_file() {
# Move source file to backup directory, create directory if not existing
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}"
# Delete tmpFile
rm "${tmpFile}" > /dev/null 2>&1 || \
rm "${tmpFile}" >/dev/null 2>&1 ||
echo -e " ${CROSS} Unable to remove ${tmpFile}"
}
# Check if a column with name ${2} exists in gravity table with name ${1}
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
return 0 # Bash 0 is success
fi
@ -227,10 +221,10 @@ gravity_column_exists() {
database_adlist_number() {
# Only try to set number of domains when this field exists in the gravity database
if ! gravity_column_exists "adlist" "number"; then
return;
return
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="$?"
if [[ "${status}" -ne 0 ]]; then
@ -243,10 +237,10 @@ database_adlist_number() {
database_adlist_status() {
# Only try to set the status when this field exists in the gravity database
if ! gravity_column_exists "adlist" "status"; then
return;
return
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="$?"
if [[ "${status}" -ne 0 ]]; then
@ -300,12 +294,7 @@ migrate_to_database() {
# Determine if DNS resolution is available before proceeding
gravity_CheckDNSResolutionAvailable() {
local lookupDomain="pi.hole"
# Determine if $localList does not exist, and ensure it is not empty
if [[ ! -e "${localList}" ]] || [[ -s "${localList}" ]]; then
lookupDomain="raw.githubusercontent.com"
fi
local lookupDomain="raw.githubusercontent.com"
# Determine if $lookupDomain is resolvable
if timeout 4 getent hosts "${lookupDomain}" &>/dev/null; then
@ -364,9 +353,9 @@ gravity_DownloadBlocklists() {
# Retrieve source URLs from gravity database
# 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 sourceIDs <<< "$(pihole-FTL sqlite3 "${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 sources <<<"$(pihole-FTL sqlite3 -ni "${gravityDBfile}" "SELECT address 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 -ni "${gravityDBfile}" "SELECT type FROM vw_adlist;" 2>/dev/null)"
# Parse source domains from $sources
mapfile -t sourceDomains <<<"$(
@ -395,7 +384,7 @@ gravity_DownloadBlocklists() {
str="Preparing new gravity database"
echo -ne " ${INFO} ${str}..."
rm "${gravityTEMPfile}" >/dev/null 2>&1
output=$( { pihole-FTL sqlite3 "${gravityTEMPfile}" < "${gravityDBschema}"; } 2>&1 )
output=$({ pihole-FTL sqlite3 -ni "${gravityTEMPfile}" <"${gravityDBschema}"; } 2>&1)
status="$?"
if [[ "${status}" -ne 0 ]]; then
@ -415,7 +404,7 @@ gravity_DownloadBlocklists() {
copyGravity="${copyGravity//"${gravityDBfile_default}"/"${gravityDBfile}"}"
fi
output=$( { pihole-FTL sqlite3 "${gravityTEMPfile}" <<< "${copyGravity}"; } 2>&1 )
output=$({ pihole-FTL sqlite3 -ni "${gravityTEMPfile}" <<<"${copyGravity}"; } 2>&1)
status="$?"
if [[ "${status}" -ne 0 ]]; then
@ -517,6 +506,31 @@ gravity_DownloadBlocklistFromUrl() {
str="Status:"
echo -ne " ${INFO} ${str} Pending..."
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
# local file or empty
@ -567,8 +581,9 @@ gravity_DownloadBlocklistFromUrl() {
fi
ip=$(dig "@${ip_addr}" -p "${port}" +short "${domain}" | tail -1)
if [[ $(echo "${url}" | awk -F '://' '{print $1}') = "https" ]]; then
port=443;
else port=80
port=443
else
port=80
fi
echo -e "${OVER} ${CROSS} ${str} ${domain} is blocked by one of your lists. Using DNS server ${upstream} instead";
echo -ne " ${INFO} ${str} Pending..."
@ -583,16 +598,24 @@ gravity_DownloadBlocklistFromUrl() {
# Did we "download" a local file?
"file"*)
if [[ -s "${listCurlBuffer}" ]]; then
echo -e "${OVER} ${TICK} ${str} Retrieval successful"; success=true
echo -e "${OVER} ${TICK} ${str} Retrieval successful"
success=true
else
echo -e "${OVER} ${CROSS} ${str} Not found / empty list"
fi;;
fi
;;
# Did we "download" a remote file?
*)
# Determine "Status:" output based on HTTP response
case "${httpCode}" in
"200") echo -e "${OVER} ${TICK} ${str} Retrieval successful"; success=true;;
"304") echo -e "${OVER} ${TICK} ${str} No changes detected"; success=true;;
"200")
echo -e "${OVER} ${TICK} ${str} Retrieval successful"
success=true
;;
"304")
echo -e "${OVER} ${TICK} ${str} No changes detected"
success=true
;;
"000") echo -e "${OVER} ${CROSS} ${str} Connection Refused" ;;
"403") echo -e "${OVER} ${CROSS} ${str} Forbidden" ;;
"404") echo -e "${OVER} ${CROSS} ${str} Not found" ;;
@ -603,7 +626,8 @@ gravity_DownloadBlocklistFromUrl() {
"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"
@ -684,12 +708,12 @@ gravity_Table_Count() {
local table="${1}"
local str="${2}"
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
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})"
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
echo -e " ${INFO} Number of ${str}: ${num}"
fi
@ -706,18 +730,6 @@ gravity_ShowCount() {
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
gravity_Trap() {
trap '{ echo -e "\\n\\n ${INFO} ${COL_LIGHT_RED}User-abort detected${COL_NC}"; gravity_Cleanup "error"; }' INT
@ -744,7 +756,7 @@ gravity_Cleanup() {
for file in "${piholeDir}"/*."${domainsExtension}"; do
# If list is not in active array, then remove it
if [[ ! "${activeDomains[*]}" == *"${file}"* ]]; then
rm -f "${file}" 2> /dev/null || \
rm -f "${file}" 2>/dev/null ||
echo -e " ${CROSS} Failed to remove ${file##*/}"
fi
done
@ -770,7 +782,7 @@ database_recovery() {
local str="Checking integrity of existing gravity database (this can take a while)"
local option="${1}"
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
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)"
echo -ne " ${INFO} ${str}..."
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
echo -e "${OVER} ${TICK} ${str} - no errors found"
if [[ "${option}" != "force" ]]; then
@ -797,7 +809,7 @@ database_recovery() {
echo -ne " ${INFO} ${str}..."
# We have to remove any possibly existing recovery database or this will fail
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"
mv "${gravityDBfile}" "${gravityDBfile}.old"
mv "${gravityDBfile}.recovered" "${gravityDBfile}"
@ -826,7 +838,8 @@ repairSelector() {
case "$1" in
"recover") recover_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
Available options:
@ -845,7 +858,8 @@ Available options:
and create a new file from scratch. If you still
have the migration backup created when migrating
to Pi-hole v5.0, Pi-hole will import these files."
exit 0;;
exit 0
;;
esac
}
@ -853,6 +867,7 @@ for var in "$@"; do
case "${var}" in
"-f" | "--force" ) forceDelete=true;;
"-r" | "--repair" ) repairSelector "$3";;
"-u" | "--upgrade" ) upgrade_gravityDB "${gravityDBfile}" "${piholeDir}"; exit 0;;
"-h" | "--help" ) helpFunc;;
esac
done
@ -904,9 +919,6 @@ if ! gravity_DownloadBlocklists; then
exit 1
fi
# Create local.list
gravity_generateLocalList
# Update gravity timestamp
update_gravity_timestamp

@ -140,8 +140,7 @@ uninstallFunc() {
}
versionFunc() {
shift
exec "${PI_HOLE_SCRIPT_DIR}"/version.sh "$@"
exec "${PI_HOLE_SCRIPT_DIR}"/version.sh
}
restartDNS() {
@ -508,7 +507,6 @@ Options:
-up, updatePihole Update Pi-hole subsystems
Add '--check-only' to exit script before update is performed.
-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
status Display the running status of Pi-hole subsystems
enable Enable Pi-hole subsystems
@ -531,7 +529,7 @@ fi
need_root=1
case "${1}" in
"-h" | "help" | "--help" ) helpFunc;;
"-v" | "version" ) versionFunc "$@";;
"-v" | "version" ) versionFunc;;
"-c" | "chronometer" ) chronometerFunc "$@";;
"-q" | "query" ) queryFunc "$@";;
"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
ENV GITDIR /etc/.pihole

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

@ -12,6 +12,8 @@ from .conftest import (
run_script,
)
FTL_BRANCH = "development-v6"
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 yum > /dev/null && yum install -y man")
# Workaround to get FTLv6 installed until it reaches master branch
host.run(
"""
echo "development-v6" > /etc/pihole/ftlbranch
"""
)
host.run('echo "' + FTL_BRANCH + '" > /etc/pihole/ftlbranch')
install = host.run(
"""
export TERM=xterm
@ -174,10 +172,6 @@ def test_installPihole_fresh_install_readableFiles(host):
)
actual_rc = host.run(check_man).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_sudo = test_cmd.format("x", "/etc/cron.d/", piholeuser)
actual_rc = host.run(check_sudo).rc
@ -235,78 +229,35 @@ def test_update_package_cache_failure_no_errors(host):
assert "Error: Unable to update package cache." in updateCache.stdout
def test_FTL_detect_aarch64_no_errors(host):
"""
confirms only aarch64 package is downloaded for FTL engine
"""
# mock uname to return aarch64 platform
mock_command("uname", {"-m": ("aarch64", "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 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}"
"""
@pytest.mark.parametrize(
"arch,detected_string,supported",
[
("aarch64", "AArch64 (64 Bit ARM)", True),
("armv6", "ARMv6", True),
("armv7l", "ARMv7 (or newer)", True),
("armv7", "ARMv7 (or newer)", True),
("armv8a", "ARMv7 (or newer)", True),
("x86_64", "x86_64", True),
("riscv64", "riscv64", True),
("mips", "mips", False),
],
)
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):
def test_FTL_detect_no_errors(host, arch, detected_string, supported):
"""
confirms only armv7l package is downloaded for FTL engine
confirms only correct 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 uname to return passed platform
mock_command("uname", {"-m": (arch, "0")}, host)
# mock readelf to respond with passed CPU architecture
mock_command_2(
"readelf",
{
"-A /bin/sh": ("Tag_CPU_arch: armv7l", "0"),
"-A /usr/bin/sh": ("Tag_CPU_arch: armv7l", "0"),
"-A /bin/sh": ("Tag_CPU_arch: " + arch, "0"),
"-A /usr/bin/sh": ("Tag_CPU_arch: " + arch, "0"),
},
host,
)
host.run('echo "' + FTL_BRANCH + '" > /etc/pihole/ftlbranch')
detectPlatform = host.run(
"""
source /opt/pihole/basic-install.sh
@ -317,155 +268,30 @@ def test_FTL_detect_armv7l_no_errors(host):
FTLdetect "${binary}" "${theRest}"
"""
)
if supported:
expected_stdout = info_box + " FTL Checks..."
assert expected_stdout in detectPlatform.stdout
expected_stdout = tick_box + (" Detected ARMv7 (or newer) architecture")
expected_stdout = tick_box + " Detected " + detected_string + " 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}"
"""
else:
expected_stdout = (
"Not able to detect architecture (unknown: " + detected_string + ")"
)
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
"""
# mock uname to return armv8a platform
mock_command("uname", {"-m": ("armv8a", "0")}, host)
# mock readelf to respond with armv8a CPU architecture
mock_command_2(
"readelf",
{
"-A /bin/sh": ("Tag_CPU_arch: armv8a", "0"),
"-A /usr/bin/sh": ("Tag_CPU_arch: armv8a", "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 (armv8a)"
assert expected_stdout in detectPlatform.stdout
expected_stdout = tick_box + " Downloading and Installing FTL"
assert expected_stdout in detectPlatform.stdout
def test_FTL_detect_x86_64_no_errors(host):
"""
confirms only x86_64 package is downloaded for FTL engine
"""
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):
"""
confirms FTL development binary is copied and functional in installed location
"""
host.run('echo "' + FTL_BRANCH + '" > /etc/pihole/ftlbranch')
host.run(
"""
source /opt/pihole/basic-install.sh
create_pihole_user
funcOutput=$(get_binary_name)
echo "development" > /etc/pihole/ftlbranch
binary="pihole-FTL${funcOutput##*pihole-FTL}"
theRest="${funcOutput%pihole-FTL*}"
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]
allowlist_externals = docker
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
Loading…
Cancel
Save