Merge branch 'master' into more_time
Some checks are pending
Check for merge conflicts / main (push) Waiting to run
CodeQL / Analyze (pull_request) Waiting to run
Test Supported Distributions / smoke-tests (pull_request) Waiting to run
Test Supported Distributions / distro-test (alpine_3_21) (pull_request) Blocked by required conditions
Test Supported Distributions / distro-test (alpine_3_22) (pull_request) Blocked by required conditions
Test Supported Distributions / distro-test (alpine_3_23) (pull_request) Blocked by required conditions
Test Supported Distributions / distro-test (centos_10) (pull_request) Blocked by required conditions
Test Supported Distributions / distro-test (centos_9) (pull_request) Blocked by required conditions
Test Supported Distributions / distro-test (debian_11) (pull_request) Blocked by required conditions
Test Supported Distributions / distro-test (debian_12) (pull_request) Blocked by required conditions
Test Supported Distributions / distro-test (debian_13) (pull_request) Blocked by required conditions
Test Supported Distributions / distro-test (fedora_40) (pull_request) Blocked by required conditions
Test Supported Distributions / distro-test (fedora_41) (pull_request) Blocked by required conditions
Test Supported Distributions / distro-test (fedora_42) (pull_request) Blocked by required conditions
Test Supported Distributions / distro-test (fedora_43) (pull_request) Blocked by required conditions
Test Supported Distributions / distro-test (ubuntu_20) (pull_request) Blocked by required conditions
Test Supported Distributions / distro-test (ubuntu_22) (pull_request) Blocked by required conditions
Test Supported Distributions / distro-test (ubuntu_24) (pull_request) Blocked by required conditions
Check for merge conflicts / main (pull_request_target) Waiting to run

This commit is contained in:
2026-03-18 14:08:30 +00:00
50 changed files with 1304 additions and 848 deletions

View File

@@ -1,11 +1,12 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034 # Disable warning about unused variables
# Determine if terminal is capable of showing colors
if [ -t 1 ] && [ "$(tput colors)" -ge 8 ]; then
# When COL_TABLE is sourced via gravity invoked by FTL, FORCE_COLOR is set to true
if { [ -t 1 ] && [ "$(tput colors)" -ge 8 ]; } || [ "${FORCE_COLOR}" ]; then
# Bold and underline may not show up on all clients
# If something MUST be emphasized, use both
COL_BOLD=''
COL_ULINE=''
COL_NC=''
COL_GRAY=''
COL_RED=''
@@ -17,8 +18,6 @@ if [ -t 1 ] && [ "$(tput colors)" -ge 8 ]; then
else
# Provide empty variables for `set -u`
COL_BOLD=""
COL_ULINE=""
COL_NC=""
COL_GRAY=""
COL_RED=""
@@ -29,22 +28,8 @@ else
COL_CYAN=""
fi
# Deprecated variables
COL_WHITE="${COL_BOLD}"
COL_BLACK="${COL_NC}"
COL_LIGHT_BLUE="${COL_BLUE}"
COL_LIGHT_GREEN="${COL_GREEN}"
COL_LIGHT_CYAN="${COL_CYAN}"
COL_LIGHT_RED="${COL_RED}"
COL_URG_RED="${COL_RED}${COL_BOLD}${COL_ULINE}"
COL_LIGHT_PURPLE="${COL_PURPLE}"
COL_BROWN="${COL_YELLOW}"
COL_LIGHT_GRAY="${COL_GRAY}"
COL_DARK_GRAY="${COL_GRAY}"
TICK="[${COL_GREEN}✓${COL_NC}]"
CROSS="[${COL_RED}✗${COL_NC}]"
INFO="[i]"
QST="[?]"
DONE="${COL_GREEN} done!${COL_NC}"
OVER="\\r"

View File

@@ -19,13 +19,20 @@
TestAPIAvailability() {
local chaos_api_list authResponse authStatus authData apiAvailable DNSport
# as we are running locally, we can get the port value from FTL directly
local chaos_api_list authResponse authStatus authData apiAvailable
PI_HOLE_SCRIPT_DIR="/opt/pihole"
utilsfile="${PI_HOLE_SCRIPT_DIR}/utils.sh"
# shellcheck source=./advanced/Scripts/utils.sh
. "${utilsfile}"
DNSport=$(getFTLConfigValue dns.port)
# 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)"
chaos_api_list="$(dig +short -p "${DNSport}" chaos txt local.api.ftl @127.0.0.1)"
# If the query was not successful, the variable is empty
if [ -z "${chaos_api_list}" ]; then
@@ -143,7 +150,6 @@ LoginAPI() {
# Try to login again until the session is valid
while [ ! "${validSession}" = true ] ; do
echo "Authentication failed. Please enter your Pi-hole password"
# Print the error message if there is one
if [ ! "${sessionError}" = "null" ] && [ "${1}" = "verbose" ]; then
@@ -154,6 +160,14 @@ LoginAPI() {
echo "Error: ${sessionMessage}"
fi
if [ "${1}" = "verbose" ]; then
# If we are not in verbose mode, no need to print the error message again
echo "Please enter your Pi-hole password"
else
echo "Authentication failed. Please enter your Pi-hole password"
fi
# secretly read the password
secretRead; printf '\n'
@@ -176,13 +190,20 @@ Authentication() {
echo "No response from FTL server. Please check connectivity"
exit 1
fi
# obtain validity, session ID and sessionMessage from session response
validSession=$(echo "${sessionResponse}"| jq .session.valid 2>/dev/null)
SID=$(echo "${sessionResponse}"| jq --raw-output .session.sid 2>/dev/null)
sessionMessage=$(echo "${sessionResponse}"| jq --raw-output .session.message 2>/dev/null)
# obtain the error message from the session response
sessionError=$(echo "${sessionResponse}"| jq --raw-output .error.message 2>/dev/null)
# obtain validity, session ID, sessionMessage and error message from
# session response, apply default values if none returned
result=$(echo "${sessionResponse}" | jq -r '
(.session.valid // false),
(.session.sid // null),
(.session.message // null),
(.error.message // null)
' 2>/dev/null)
validSession=$(echo "${result}" | sed -n '1p')
SID=$(echo "${result}" | sed -n '2p')
sessionMessage=$(echo "${result}" | sed -n '3p')
sessionError=$(echo "${result}" | sed -n '4p')
if [ "${1}" = "verbose" ]; then
if [ "${validSession}" = true ]; then
@@ -346,12 +367,9 @@ apiFunc() {
if [ "${verbosity}" = "verbose" ]; then
echo "Data:"
fi
if command -v jq >/dev/null && echo "${data}" | jq . >/dev/null 2>&1; then
echo "${data}" | jq .
else
echo "${data}"
fi
# Attempt to print the data with jq, if it is not valid JSON, or not installed
# then print the plain text.
echo "${data}" | jq . 2>/dev/null || echo "${data}"
# Delete the session
LogoutAPI "${verbosity}"

View File

@@ -150,4 +150,10 @@ upgrade_gravityDB(){
pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/18_to_19.sql"
version=19
fi
if [[ "$version" == "19" ]]; then
# Update views to use new allowlist/denylist names
echo -e " ${INFO} Upgrading gravity database from version 19 to 20"
pihole-FTL sqlite3 -ni "${database}" < "${scriptPath}/19_to_20.sql"
version=20
fi
}

View File

@@ -0,0 +1,43 @@
.timeout 30000
BEGIN TRANSACTION;
DROP VIEW vw_whitelist;
CREATE VIEW vw_allowlist AS SELECT domain, domainlist.id AS id, domainlist_by_group.group_id AS group_id
FROM domainlist
LEFT JOIN domainlist_by_group ON domainlist_by_group.domainlist_id = domainlist.id
LEFT JOIN "group" ON "group".id = domainlist_by_group.group_id
WHERE domainlist.enabled = 1 AND (domainlist_by_group.group_id IS NULL OR "group".enabled = 1)
AND domainlist.type = 0
ORDER BY domainlist.id;
DROP VIEW vw_blacklist;
CREATE VIEW vw_denylist AS SELECT domain, domainlist.id AS id, domainlist_by_group.group_id AS group_id
FROM domainlist
LEFT JOIN domainlist_by_group ON domainlist_by_group.domainlist_id = domainlist.id
LEFT JOIN "group" ON "group".id = domainlist_by_group.group_id
WHERE domainlist.enabled = 1 AND (domainlist_by_group.group_id IS NULL OR "group".enabled = 1)
AND domainlist.type = 1
ORDER BY domainlist.id;
DROP VIEW vw_regex_whitelist;
CREATE VIEW vw_regex_allowlist AS SELECT domain, domainlist.id AS id, domainlist_by_group.group_id AS group_id
FROM domainlist
LEFT JOIN domainlist_by_group ON domainlist_by_group.domainlist_id = domainlist.id
LEFT JOIN "group" ON "group".id = domainlist_by_group.group_id
WHERE domainlist.enabled = 1 AND (domainlist_by_group.group_id IS NULL OR "group".enabled = 1)
AND domainlist.type = 2
ORDER BY domainlist.id;
DROP VIEW vw_regex_blacklist;
CREATE VIEW vw_regex_denylist AS SELECT domain, domainlist.id AS id, domainlist_by_group.group_id AS group_id
FROM domainlist
LEFT JOIN domainlist_by_group ON domainlist_by_group.domainlist_id = domainlist.id
LEFT JOIN "group" ON "group".id = domainlist_by_group.group_id
WHERE domainlist.enabled = 1 AND (domainlist_by_group.group_id IS NULL OR "group".enabled = 1)
AND domainlist.type = 3
ORDER BY domainlist.id;
UPDATE info SET value = 20 WHERE property = 'version';
COMMIT;

View File

@@ -9,12 +9,12 @@
# This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license.
readonly PI_HOLE_SCRIPT_DIR="/opt/pihole"
readonly utilsfile="${PI_HOLE_SCRIPT_DIR}/utils.sh"
PI_HOLE_SCRIPT_DIR="/opt/pihole"
utilsfile="${PI_HOLE_SCRIPT_DIR}/utils.sh"
# shellcheck source="./advanced/Scripts/utils.sh"
source "${utilsfile}"
readonly apifile="${PI_HOLE_SCRIPT_DIR}/api.sh"
apifile="${PI_HOLE_SCRIPT_DIR}/api.sh"
# shellcheck source="./advanced/Scripts/api.sh"
source "${apifile}"

View File

@@ -1,83 +0,0 @@
#!/usr/bin/env bash
# Pi-hole: A black hole for Internet advertisements
# (c) 2019 Pi-hole, LLC (https://pi-hole.net)
# Network-wide ad blocking via your own hardware.
#
# ARP table interaction
#
# This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license.
coltable="/opt/pihole/COL_TABLE"
if [[ -f ${coltable} ]]; then
# shellcheck source="./advanced/Scripts/COL_TABLE"
source ${coltable}
fi
readonly PI_HOLE_SCRIPT_DIR="/opt/pihole"
utilsfile="${PI_HOLE_SCRIPT_DIR}/utils.sh"
# shellcheck source=./advanced/Scripts/utils.sh
source "${utilsfile}"
# Determine database location
DBFILE=$(getFTLConfigValue "files.database")
if [ -z "$DBFILE" ]; then
DBFILE="/etc/pihole/pihole-FTL.db"
fi
flushARP(){
local output
if [[ "${args[1]}" != "quiet" ]]; then
echo -ne " ${INFO} Flushing network table ..."
fi
# Stop FTL to prevent database access
if ! output=$(service pihole-FTL stop 2>&1); then
echo -e "${OVER} ${CROSS} Failed to stop FTL"
echo " Output: ${output}"
return 1
fi
# Truncate network_addresses table in pihole-FTL.db
# This needs to be done before we can truncate the network table due to
# foreign key constraints
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}"
return 1
fi
# Truncate network table in pihole-FTL.db
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}"
return 1
fi
# Flush ARP cache of the host
if ! output=$(ip -s -s neigh flush all 2>&1); then
echo -e "${OVER} ${CROSS} Failed to flush ARP cache"
echo " Output: ${output}"
return 1
fi
# Start FTL again
if ! output=$(service pihole-FTL restart 2>&1); then
echo -e "${OVER} ${CROSS} Failed to restart FTL"
echo " Output: ${output}"
return 1
fi
if [[ "${args[1]}" != "quiet" ]]; then
echo -e "${OVER} ${TICK} Flushed network table"
fi
}
args=("$@")
case "${args[0]}" in
"arpflush" ) flushARP;;
esac

View File

@@ -26,7 +26,7 @@ source "${PI_HOLE_FILES_DIR}/automated install/basic-install.sh"
warning1() {
echo " Please note that changing branches severely alters your Pi-hole subsystems"
echo " Features that work on the master branch, may not on a development branch"
echo -e " ${COL_LIGHT_RED}This feature is NOT supported unless a Pi-hole developer explicitly asks!${COL_NC}"
echo -e " ${COL_RED}This feature is NOT supported unless a Pi-hole developer explicitly asks!${COL_NC}"
read -r -p " Have you read and understood this? [y/N] " response
case "${response}" in
[yY][eE][sS]|[yY])
@@ -41,6 +41,22 @@ warning1() {
}
checkout() {
local skipFTL additionalFlag
skipFTL=false
# Check arguments
for var in "$@"; do
case "$var" in
"--skipFTL") skipFTL=true ;;
esac
done
if [ "${skipFTL}" == true ]; then
additionalFlag="--skipFTL"
else
additionalFlag=""
fi
local corebranches
local webbranches
@@ -55,19 +71,19 @@ checkout() {
# This is unlikely
if ! is_repo "${PI_HOLE_FILES_DIR}" ; then
echo -e " ${COL_LIGHT_RED}Error: Core Pi-hole repo is missing from system!"
echo -e " ${COL_RED}Error: Core Pi-hole repo is missing from system!"
echo -e " Please re-run install script from https://github.com/pi-hole/pi-hole${COL_NC}"
exit 1;
fi
if ! is_repo "${webInterfaceDir}" ; then
echo -e " ${COL_LIGHT_RED}Error: Web Admin repo is missing from system!"
echo -e " ${COL_RED}Error: Web Admin repo is missing from system!"
echo -e " Please re-run install script from https://github.com/pi-hole/pi-hole${COL_NC}"
exit 1;
fi
if [[ -z "${1}" ]]; then
echo -e " ${COL_LIGHT_RED}Invalid option${COL_NC}"
echo -e " ${COL_RED}Invalid option${COL_NC}"
echo -e " Try 'pihole checkout --help' for more information."
exit 1
fi
@@ -235,10 +251,10 @@ checkout() {
# Force updating everything
if [[ ! "${1}" == "web" && ! "${1}" == "ftl" ]]; then
echo -e " ${INFO} Running installer to upgrade your installation"
if "${PI_HOLE_FILES_DIR}/automated install/basic-install.sh" --unattended; then
if "${PI_HOLE_FILES_DIR}/automated install/basic-install.sh" --unattended ${additionalFlag}; then
exit 0
else
echo -e " ${COL_LIGHT_RED} Error: Unable to complete update, please contact support${COL_NC}"
echo -e " ${COL_RED} Error: Unable to complete update, please contact support${COL_NC}"
exit 1
fi
fi

View File

@@ -367,7 +367,7 @@ check_firewalld() {
# test common required service ports
local firewalld_enabled_services
firewalld_enabled_services=$(firewall-cmd --list-services)
local firewalld_expected_services=("http" "dns" "dhcp" "dhcpv6")
local firewalld_expected_services=("http" "https" "dns" "dhcp" "dhcpv6" "ntp")
for i in "${firewalld_expected_services[@]}"; do
if [[ "${firewalld_enabled_services}" =~ ${i} ]]; then
log_write "${TICK} ${COL_GREEN} Allow Service: ${i}${COL_NC}";
@@ -375,30 +375,6 @@ check_firewalld() {
log_write "${CROSS} ${COL_RED} Allow Service: ${i}${COL_NC} (${FAQ_HARDWARE_REQUIREMENTS_FIREWALLD})"
fi
done
# check for custom FTL FirewallD zone
local firewalld_zones
firewalld_zones=$(firewall-cmd --get-zones)
if [[ "${firewalld_zones}" =~ "ftl" ]]; then
log_write "${TICK} ${COL_GREEN}FTL Custom Zone Detected${COL_NC}";
# check FTL custom zone interface: lo
local firewalld_ftl_zone_interfaces
firewalld_ftl_zone_interfaces=$(firewall-cmd --zone=ftl --list-interfaces)
if [[ "${firewalld_ftl_zone_interfaces}" =~ "lo" ]]; then
log_write "${TICK} ${COL_GREEN} Local Interface Detected${COL_NC}";
else
log_write "${CROSS} ${COL_RED} Local Interface Not Detected${COL_NC} (${FAQ_HARDWARE_REQUIREMENTS_FIREWALLD})"
fi
# check FTL custom zone port: 4711
local firewalld_ftl_zone_ports
firewalld_ftl_zone_ports=$(firewall-cmd --zone=ftl --list-ports)
if [[ "${firewalld_ftl_zone_ports}" =~ "4711/tcp" ]]; then
log_write "${TICK} ${COL_GREEN} FTL Port 4711/tcp Detected${COL_NC}";
else
log_write "${CROSS} ${COL_RED} FTL Port 4711/tcp Not Detected${COL_NC} (${FAQ_HARDWARE_REQUIREMENTS_FIREWALLD})"
fi
else
log_write "${CROSS} ${COL_RED}FTL Custom Zone Not Detected${COL_NC} (${FAQ_HARDWARE_REQUIREMENTS_FIREWALLD})"
fi
fi
else
log_write "${TICK} ${COL_GREEN}Firewalld service not detected${COL_NC}";
@@ -497,16 +473,25 @@ 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 gateway_addr gateway_iface
local gateway gateway_addr gateway_iface default_route
log_write "${INFO} Default IPv${protocol} gateway(s):"
while IFS= read -r gateway; do
log_write " $(cut -d ' ' -f 3 <<< "${gateway}")%$(cut -d ' ' -f 5 <<< "${gateway}")"
done < <(ip -"${protocol}" route | grep default)
while IFS= read -r default_route; do
gateway_addr=$(jq -r '.gateway' <<< "${default_route}")
gateway_iface=$(jq -r '.dev' <<< "${default_route}")
log_write " ${gateway_addr}%${gateway_iface}"
done < <(ip -j -"${protocol}" route | jq -c '.[] | select(.dst == "default")')
# Find the first default route
default_route=$(ip -j -"${protocol}" route show default)
if echo "$default_route" | grep 'gateway' | grep -q 'dev'; then
gateway_addr=$(echo "$default_route" | jq -r -c '.[0].gateway')
gateway_iface=$(echo "$default_route" | jq -r -c '.[0].dev')
else
log_write " Unable to determine gateway address for IPv${protocol}"
fi
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_addr}" ]; then
# Append the interface to the gateway address if it is a link-local address
@@ -592,18 +577,21 @@ check_required_ports() {
# Add port 53
ports_configured+=("53")
local protocol_type port_number service_name
# Now that we have the values stored,
for i in "${!ports_in_use[@]}"; do
# loop through them and assign some local variables
local service_name
service_name=$(echo "${ports_in_use[$i]}" | awk '{gsub(/users:\(\("/,"",$7);gsub(/".*/,"",$7);print $7}')
local protocol_type
protocol_type=$(echo "${ports_in_use[$i]}" | awk '{print $1}')
local port_number
port_number="$(echo "${ports_in_use[$i]}" | awk '{print $5}')" # | awk '{gsub(/^.*:/,"",$5);print $5}')
read -r protocol_type port_number service_name <<< "$(
awk '{
p=$1; n=$5; s=$7
gsub(/users:\(\("/,"",s)
gsub(/".*/,"",s)
print p, n, s
}' <<< "${ports_in_use[$i]}"
)"
# Check if the right services are using the right ports
if [[ ${ports_configured[*]} =~ $(echo "${port_number}" | rev | cut -d: -f1 | rev) ]]; then
if [[ ${ports_configured[*]} =~ ${port_number##*:} ]]; then
compare_port_to_service_assigned "${ftl}" "${service_name}" "${protocol_type}:${port_number}"
else
# If it's not a default port that Pi-hole needs, just print it out for the user to see
@@ -671,7 +659,7 @@ dig_at() {
local record_type="A"
fi
# Find a random blocked url that has not been whitelisted and is not ABP style.
# Find a random blocked url that has not been allowlisted and is not ABP style.
# This helps emulate queries to different domains that a user might query
# It will also give extra assurance that Pi-hole is correctly resolving and blocking domains
local random_url
@@ -721,7 +709,7 @@ dig_at() {
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 local_dig="$(dig +tries=1 +time=2 -"${protocol}" "${random_url}" @"${local_address}" "${record_type}" -p "$(get_ftl_conf_value "dns.port")")"; then
# If it can, show success
if [[ "${local_dig}" == *"status: NOERROR"* ]]; then
local_dig="NOERROR"
@@ -777,7 +765,7 @@ process_status(){
:
else
# non-Docker system
if service "${i}" status | grep -E 'is\srunning' &> /dev/null; then
if service "${i}" status | grep -q -E 'is\srunning|started'; then
status_of_process="active"
else
status_of_process="inactive"
@@ -815,42 +803,27 @@ ftl_full_status(){
make_array_from_file() {
local filename="${1}"
# If the file is a directory do nothing since it cannot be parsed
[[ -d "${filename}" ]] && return
# The second argument can put a limit on how many line should be read from the file
# Since some of the files are so large, this is helpful to limit the output
local limit=${2}
# A local iterator for testing if we are at the limit above
local i=0
# If the file is a directory
if [[ -d "${filename}" ]]; then
# do nothing since it cannot be parsed
:
else
# Otherwise, read the file line by line
while IFS= read -r line;do
# Otherwise, strip out comments and blank lines
new_line=$(echo "${line}" | sed -e 's/^\s*#.*$//' -e '/^$/d')
# If the line still has content (a non-zero value)
if [[ -n "${new_line}" ]]; then
# If the string contains "### CHANGED", highlight this part in red
if [[ "${new_line}" == *"### CHANGED"* ]]; then
new_line="${new_line//### CHANGED/${COL_RED}### CHANGED${COL_NC}}"
fi
# Process the file, strip out comments and blank lines
local processed
processed=$(sed -e 's/^\s*#.*$//' -e '/^$/d' "${filename}")
# Finally, write this line to the log
log_write " ${new_line}"
fi
# Increment the iterator +1
i=$((i+1))
# but if the limit of lines we want to see is exceeded
if [[ -z ${limit} ]]; then
# do nothing
:
elif [[ $i -eq ${limit} ]]; then
break
fi
done < "${filename}"
fi
while IFS= read -r line; do
# If the string contains "### CHANGED", highlight this part in red
log_write " ${line//### CHANGED/${COL_RED}### CHANGED${COL_NC}}"
((i++))
# if the limit of lines we want to see is exceeded do nothing
[[ -n ${limit} && $i -eq ${limit} ]] && break
done <<< "$processed"
}
parse_file() {
@@ -923,38 +896,38 @@ list_files_in_dir() {
fi
# Store the files found in an array
mapfile -t files_found < <(ls "${dir_to_parse}")
local files_found=("${dir_to_parse}"/*)
# For each file in the array,
for each_file in "${files_found[@]}"; do
if [[ -d "${dir_to_parse}/${each_file}" ]]; then
if [[ -d "${each_file}" ]]; then
# If it's a directory, do nothing
:
elif [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_DEBUG_LOG}" ]] || \
[[ "${dir_to_parse}/${each_file}" == "${PIHOLE_RAW_BLOCKLIST_FILES}" ]] || \
[[ "${dir_to_parse}/${each_file}" == "${PIHOLE_INSTALL_LOG_FILE}" ]] || \
[[ "${dir_to_parse}/${each_file}" == "${PIHOLE_LOG}" ]] || \
[[ "${dir_to_parse}/${each_file}" == "${PIHOLE_LOG_GZIPS}" ]]; then
elif [[ "${each_file}" == "${PIHOLE_DEBUG_LOG}" ]] || \
[[ "${each_file}" == "${PIHOLE_RAW_BLOCKLIST_FILES}" ]] || \
[[ "${each_file}" == "${PIHOLE_INSTALL_LOG_FILE}" ]] || \
[[ "${each_file}" == "${PIHOLE_LOG}" ]] || \
[[ "${each_file}" == "${PIHOLE_LOG_GZIPS}" ]]; then
:
elif [[ "${dir_to_parse}" == "${DNSMASQ_D_DIRECTORY}" ]]; then
# in case of the dnsmasq directory include all files in the debug output
log_write "\\n${COL_GREEN}$(ls -lhd "${dir_to_parse}"/"${each_file}")${COL_NC}"
make_array_from_file "${dir_to_parse}/${each_file}"
log_write "\\n${COL_GREEN}$(ls -lhd "${each_file}")${COL_NC}"
make_array_from_file "${each_file}"
else
# Then, parse the file's content into an array so each line can be analyzed if need be
for i in "${!REQUIRED_FILES[@]}"; do
if [[ "${dir_to_parse}/${each_file}" == "${REQUIRED_FILES[$i]}" ]]; then
if [[ "${each_file}" == "${REQUIRED_FILES[$i]}" ]]; then
# display the filename
log_write "\\n${COL_GREEN}$(ls -lhd "${dir_to_parse}"/"${each_file}")${COL_NC}"
log_write "\\n${COL_GREEN}$(ls -lhd "${each_file}")${COL_NC}"
# Check if the file we want to view has a limit (because sometimes we just need a little bit of info from the file, not the entire thing)
case "${dir_to_parse}/${each_file}" in
case "${each_file}" in
# If it's Web server log, give the first and last 25 lines
"${PIHOLE_WEBSERVER_LOG}") head_tail_log "${dir_to_parse}/${each_file}" 25
"${PIHOLE_WEBSERVER_LOG}") head_tail_log "${each_file}" 25
;;
# Same for the FTL log
"${PIHOLE_FTL_LOG}") head_tail_log "${dir_to_parse}/${each_file}" 35
"${PIHOLE_FTL_LOG}") head_tail_log "${each_file}" 35
;;
# parse the file into an array in case we ever need to analyze it line-by-line
*) make_array_from_file "${dir_to_parse}/${each_file}";
*) make_array_from_file "${each_file}";
esac
else
# Otherwise, do nothing since it's not a file needed for Pi-hole so we don't care about it
@@ -990,6 +963,7 @@ head_tail_log() {
local filename="${1}"
# The number of lines to use for head and tail
local qty="${2}"
local filebasename="${filename##*/}"
local head_line
local tail_line
# Put the current Internal Field Separator into another variable so it can be restored later
@@ -998,14 +972,14 @@ head_tail_log() {
IFS=$'\r\n'
local log_head=()
mapfile -t log_head < <(head -n "${qty}" "${filename}")
log_write " ${COL_CYAN}-----head of $(basename "${filename}")------${COL_NC}"
log_write " ${COL_CYAN}-----head of ${filebasename}------${COL_NC}"
for head_line in "${log_head[@]}"; do
log_write " ${head_line}"
done
log_write ""
local log_tail=()
mapfile -t log_tail < <(tail -n "${qty}" "${filename}")
log_write " ${COL_CYAN}-----tail of $(basename "${filename}")------${COL_NC}"
log_write " ${COL_CYAN}-----tail of ${filebasename}------${COL_NC}"
for tail_line in "${log_tail[@]}"; do
log_write " ${tail_line}"
done
@@ -1032,6 +1006,24 @@ show_db_entries() {
)
for line in "${entries[@]}"; do
# Use gray color for "no". Normal color for "yes"
line=${line//--no---/${COL_GRAY} no ${COL_NC}}
line=${line//--yes--/ yes }
# Use red for "deny" and green for "allow"
if [ "$title" = "Domainlist" ]; then
line=${line//regex-deny/${COL_RED}regex-deny${COL_NC}}
line=${line//regex-allow/${COL_GREEN}regex-allow${COL_NC}}
line=${line//exact-deny/${COL_RED}exact-deny${COL_NC}}
line=${line//exact-allow/${COL_GREEN}exact-allow${COL_NC}}
fi
# Use red for "block" and green for "allow"
if [ "$title" = "Adlists" ]; then
line=${line//-BLOCK-/${COL_RED} Block ${COL_NC}}
line=${line//-ALLOW-/${COL_GREEN} Allow ${COL_NC}}
fi
log_write " ${line}"
done
@@ -1079,15 +1071,15 @@ check_dhcp_servers() {
}
show_groups() {
show_db_entries "Groups" "SELECT id,CASE enabled WHEN '0' THEN ' 0' WHEN '1' THEN ' 1' ELSE enabled END enabled,name,datetime(date_added,'unixepoch','localtime') date_added,datetime(date_modified,'unixepoch','localtime') date_modified,description FROM \"group\"" "4 7 50 19 19 50"
show_db_entries "Groups" "SELECT id,CASE enabled WHEN '0' THEN '--no---' WHEN '1' THEN '--yes--' ELSE enabled END enabled,name,datetime(date_added,'unixepoch','localtime') date_added,datetime(date_modified,'unixepoch','localtime') date_modified,description FROM \"group\"" "4 7 50 19 19 50"
}
show_adlists() {
show_db_entries "Adlists" "SELECT id,CASE enabled WHEN '0' THEN ' 0' WHEN '1' THEN ' 1' ELSE enabled END enabled,GROUP_CONCAT(adlist_by_group.group_id) group_ids,address,datetime(date_added,'unixepoch','localtime') date_added,datetime(date_modified,'unixepoch','localtime') date_modified,comment FROM adlist LEFT JOIN adlist_by_group ON adlist.id = adlist_by_group.adlist_id GROUP BY id;" "5 7 12 100 19 19 50"
show_db_entries "Adlists" "SELECT id,CASE enabled WHEN '0' THEN '--no---' WHEN '1' THEN '--yes--' ELSE enabled END enabled,GROUP_CONCAT(adlist_by_group.group_id) group_ids, CASE type WHEN '0' THEN '-BLOCK-' WHEN '1' THEN '-ALLOW-' ELSE type END type, address,datetime(date_added,'unixepoch','localtime') date_added,datetime(date_modified,'unixepoch','localtime') date_modified,comment FROM adlist LEFT JOIN adlist_by_group ON adlist.id = adlist_by_group.adlist_id GROUP BY id;" "5 7 12 7 100 19 19 50"
}
show_domainlist() {
show_db_entries "Domainlist (0/1 = exact white-/blacklist, 2/3 = regex white-/blacklist)" "SELECT id,CASE type WHEN '0' THEN '0 ' WHEN '1' THEN ' 1 ' WHEN '2' THEN ' 2 ' WHEN '3' THEN ' 3' ELSE type END type,CASE enabled WHEN '0' THEN ' 0' WHEN '1' THEN ' 1' ELSE enabled END enabled,GROUP_CONCAT(domainlist_by_group.group_id) group_ids,domain,datetime(date_added,'unixepoch','localtime') date_added,datetime(date_modified,'unixepoch','localtime') date_modified,comment FROM domainlist LEFT JOIN domainlist_by_group ON domainlist.id = domainlist_by_group.domainlist_id GROUP BY id;" "5 4 7 12 100 19 19 50"
show_db_entries "Domainlist" "SELECT id,CASE type WHEN '0' THEN 'exact-allow' WHEN '1' THEN 'exact-deny' WHEN '2' THEN 'regex-allow' WHEN '3' THEN 'regex-deny' ELSE type END type,CASE enabled WHEN '0' THEN '--no---' WHEN '1' THEN '--yes--' ELSE enabled END enabled,GROUP_CONCAT(domainlist_by_group.group_id) group_ids,domain,datetime(date_added,'unixepoch','localtime') date_added,datetime(date_modified,'unixepoch','localtime') date_modified,comment FROM domainlist LEFT JOIN domainlist_by_group ON domainlist.id = domainlist_by_group.domainlist_id GROUP BY id;" "5 11 7 12 90 19 19 50"
}
show_clients() {

View File

@@ -86,6 +86,7 @@ if [[ "$*" == *"once"* ]]; then
if [[ "$*" != *"quiet"* ]]; then
echo -ne " ${INFO} Running logrotate ..."
fi
mkdir -p "${STATEFILE%/*}"
/usr/sbin/logrotate --force --state "${STATEFILE}" /etc/pihole/logrotate
else
# Handle rotation for each log file
@@ -115,4 +116,3 @@ else
echo -e "${OVER} ${TICK} Deleted ${deleted} queries from long-term query database"
fi
fi

View File

@@ -0,0 +1,84 @@
#!/usr/bin/env bash
# Pi-hole: A black hole for Internet advertisements
# (c) 2019 Pi-hole, LLC (https://pi-hole.net)
# Network-wide ad blocking via your own hardware.
#
# Network table flush
#
# This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license.
coltable="/opt/pihole/COL_TABLE"
if [[ -f ${coltable} ]]; then
# shellcheck source="./advanced/Scripts/COL_TABLE"
source ${coltable}
fi
readonly PI_HOLE_SCRIPT_DIR="/opt/pihole"
utilsfile="${PI_HOLE_SCRIPT_DIR}/utils.sh"
# shellcheck source=./advanced/Scripts/utils.sh
source "${utilsfile}"
# Source api functions
# shellcheck source="./advanced/Scripts/api.sh"
. "${PI_HOLE_SCRIPT_DIR}/api.sh"
flushNetwork(){
local output
echo -ne " ${INFO} Flushing network table ..."
local data status error
# Authenticate with FTL
LoginAPI
# send query again
data=$(PostFTLData "action/flush/network" "" "status")
# Separate the status from the data
status=$(printf %s "${data#"${data%???}"}")
data=$(printf %s "${data%???}")
# If there is an .error object in the returned data, display it
local error
error=$(jq --compact-output <<< "${data}" '.error')
if [[ $error != "null" && $error != "" ]]; then
echo -e "${OVER} ${CROSS} Failed to flush the network table:"
echo -e " $(jq <<< "${data}" '.error')"
LogoutAPI
exit 1
elif [[ "${status}" == "200" ]]; then
echo -e "${OVER} ${TICK} Flushed network table"
fi
# Delete session
LogoutAPI
}
flushArp(){
# Flush ARP cache of the host
if ! output=$(ip -s -s neigh flush all 2>&1); then
echo -e "${OVER} ${CROSS} Failed to flush ARP cache"
echo " Output: ${output}"
return 1
fi
}
# Process all options (if present)
while [ "$#" -gt 0 ]; do
case "$1" in
"--arp" ) doARP=true ;;
esac
shift
done
flushNetwork
if [[ "${doARP}" == true ]]; then
echo -ne " ${INFO} Flushing ARP cache"
if flushArp; then
echo -e "${OVER} ${TICK} Flushed ARP cache"
fi
fi

View File

@@ -37,19 +37,16 @@ Options:
}
GenerateOutput() {
local data gravity_data lists_data num_gravity num_lists search_type_str
local gravity_data_csv lists_data_csv line current_domain url type color
local counts data num_gravity num_lists search_type_str
local gravity_data_csv lists_data_csv line 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=$(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=$(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=$(printf %s "${gravity_data}" | jq length)
num_lists=$(printf %s "${lists_data}" | jq length)
# Get count of list and gravity matches
# Use JQ to count number of entries in lists and gravity
# (output is number of list matches then number of gravity matches)
counts=$(printf %s "${data}" | jq --raw-output '(.search.domains | length), (.search.gravity | group_by(.address,.type) | length)')
num_lists=$(echo "$counts" | sed -n '1p')
num_gravity=$(echo "$counts" | sed -n '2p')
if [ "${partial}" = true ]; then
search_type_str="partially"
@@ -62,7 +59,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=$(printf %s "${lists_data}" | jq --raw-output '.[] | [.domain, .type] | join(",")')
lists_data_csv=$(printf %s "${data}" | jq --raw-output '.search.domains | map([.domain, .type] | join(",")) | join("\n")')
# 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
@@ -71,11 +68,11 @@ GenerateOutput() {
fi
# Results from gravity
printf "%s\n\n" "Found ${num_gravity} adlists ${search_type_str} matching '${COL_BLUE}${domain}${COL_NC}'."
printf "%s\n\n" "Found ${num_gravity} lists ${search_type_str} matching '${COL_BLUE}${domain}${COL_NC}'."
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,type,domain,domain,...." string
# not using jq's @csv here as it quotes each value individually
gravity_data_csv=$(printf %s "${gravity_data}" | jq --raw-output '.[] | [.address, .type, .domains[]] | join(",")')
gravity_data_csv=$(printf %s "${data}" | jq --raw-output '.search.gravity | group_by(.address,.type) | map([.[0].address, .[0].type, (.[] | .domain)] | join(",")) | join("\n")')
# Generate line-by-line output for each csv line
echo "${gravity_data_csv}" | while read -r line; do
@@ -97,15 +94,8 @@ GenerateOutput() {
# 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
current_domain=${line%%,*}
printf ' - %s\n' "${COL_GREEN}${current_domain}${COL_NC}"
# we need to remove the current_domain and the comma in two steps because
# the last domain won't have a trailing comma and the while loop wouldn't exit
line=${line#"${current_domain}"}
line=${line#,}
done
# Replace commas with newlines and format output
echo "${line}" | sed 's/,/\n/g' | sed "s/^/ - ${COL_GREEN}/" | sed "s/$/${COL_NC}/"
printf "\n\n"
done
fi

View File

@@ -47,7 +47,7 @@ GitCheckUpdateAvail() {
# Fetch latest changes in this repo
if ! git fetch --quiet origin ; then
echo -e "\\n ${COL_LIGHT_RED}Error: Unable to update local repository. Contact Pi-hole Support.${COL_NC}"
echo -e "\\n ${COL_RED}Error: Unable to update local repository. Contact Pi-hole Support.${COL_NC}"
exit 1
fi
@@ -76,13 +76,13 @@ GitCheckUpdateAvail() {
if [[ "${#LOCAL}" == 0 ]]; then
echo -e "\\n ${COL_LIGHT_RED}Error: Local revision could not be obtained, please contact Pi-hole Support"
echo -e "\\n ${COL_RED}Error: Local revision could not be obtained, please contact Pi-hole Support"
echo -e " Additional debugging output:${COL_NC}"
git status
exit 1
fi
if [[ "${#REMOTE}" == 0 ]]; then
echo -e "\\n ${COL_LIGHT_RED}Error: Remote revision could not be obtained, please contact Pi-hole Support"
echo -e "\\n ${COL_RED}Error: Remote revision could not be obtained, please contact Pi-hole Support"
echo -e " Additional debugging output:${COL_NC}"
git status
exit 1
@@ -103,7 +103,7 @@ GitCheckUpdateAvail() {
}
main() {
local basicError="\\n ${COL_LIGHT_RED}Unable to complete update, please contact Pi-hole Support${COL_NC}"
local basicError="\\n ${COL_RED}Unable to complete update, please contact Pi-hole Support${COL_NC}"
local core_update
local web_update
local FTL_update
@@ -120,7 +120,7 @@ main() {
# This is unlikely
if ! is_repo "${PI_HOLE_FILES_DIR}" ; then
echo -e "\\n ${COL_LIGHT_RED}Error: Core Pi-hole repo is missing from system!"
echo -e "\\n ${COL_RED}Error: Core Pi-hole repo is missing from system!"
echo -e " Please re-run install script from https://pi-hole.net${COL_NC}"
exit 1;
fi
@@ -132,11 +132,11 @@ main() {
echo -e " ${INFO} Pi-hole Core:\\t${COL_YELLOW}update available${COL_NC}"
else
core_update=false
echo -e " ${INFO} Pi-hole Core:\\t${COL_LIGHT_GREEN}up to date${COL_NC}"
echo -e " ${INFO} Pi-hole Core:\\t${COL_GREEN}up to date${COL_NC}"
fi
if ! is_repo "${ADMIN_INTERFACE_DIR}" ; then
echo -e "\\n ${COL_LIGHT_RED}Error: Web Admin repo is missing from system!"
echo -e "\\n ${COL_RED}Error: Web Admin repo is missing from system!"
echo -e " Please re-run install script from https://pi-hole.net${COL_NC}"
exit 1;
fi
@@ -146,33 +146,40 @@ main() {
echo -e " ${INFO} Web Interface:\\t${COL_YELLOW}update available${COL_NC}"
else
web_update=false
echo -e " ${INFO} Web Interface:\\t${COL_LIGHT_GREEN}up to date${COL_NC}"
echo -e " ${INFO} Web Interface:\\t${COL_GREEN}up to date${COL_NC}"
fi
local funcOutput
funcOutput=$(get_binary_name) #Store output of get_binary_name here
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)
# Allow the user to skip this check if they are using a self-compiled FTL binary from an unsupported architecture
if [ "${skipFTL}" != true ]; then
local funcOutput
funcOutput=$(get_binary_name) #Store output of get_binary_name here
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
FTL_update=true
echo -e " ${INFO} FTL:\\t\\t${COL_YELLOW}update available${COL_NC}"
if FTLcheckUpdate "${binary}" &>/dev/null; then
FTL_update=true
echo -e " ${INFO} FTL:\\t\\t${COL_YELLOW}update available${COL_NC}"
else
case $? in
1)
echo -e " ${INFO} FTL:\\t\\t${COL_GREEN}up to date${COL_NC}"
;;
2)
echo -e " ${INFO} FTL:\\t\\t${COL_RED}Branch is not available.${COL_NC}\\n\\t\\t\\tUse ${COL_GREEN}pihole checkout ftl [branchname]${COL_NC} to switch to a valid branch."
exit 1
;;
3)
echo -e " ${INFO} FTL:\\t\\t${COL_RED}Something has gone wrong, cannot reach download server${COL_NC}"
exit 1
;;
*)
echo -e " ${INFO} FTL:\\t\\t${COL_RED}Something has gone wrong, contact support${COL_NC}"
exit 1
esac
FTL_update=false
fi
else
case $? in
1)
echo -e " ${INFO} FTL:\\t\\t${COL_LIGHT_GREEN}up to date${COL_NC}"
;;
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
echo -e " ${INFO} FTL:\\t\\t${COL_YELLOW}--skipFTL set - update check skipped${COL_NC}"
FTL_update=false
fi
@@ -187,7 +194,7 @@ main() {
if [[ ! "${ftlBranch}" == "master" && ! "${ftlBranch}" == "development" ]]; then
# Notify user that they are on a custom branch which might mean they they are lost
# behind if a branch was merged to development and got abandoned
printf " %b %bWarning:%b You are using FTL from a custom branch (%s) and might be missing future releases.\\n" "${INFO}" "${COL_LIGHT_RED}" "${COL_NC}" "${ftlBranch}"
printf " %b %bWarning:%b You are using FTL from a custom branch (%s) and might be missing future releases.\\n" "${INFO}" "${COL_RED}" "${COL_NC}" "${ftlBranch}"
fi
if [[ "${core_update}" == false && "${web_update}" == false && "${FTL_update}" == false ]]; then
@@ -221,7 +228,14 @@ main() {
fi
if [[ "${FTL_update}" == true || "${core_update}" == true ]]; then
${PI_HOLE_FILES_DIR}/automated\ install/basic-install.sh --repair --unattended || \
local addionalFlag
if [[ ${skipFTL} == true ]]; then
addionalFlag="--skipFTL"
else
addionalFlag=""
fi
${PI_HOLE_FILES_DIR}/automated\ install/basic-install.sh --repair --unattended ${addionalFlag} || \
echo -e "${basicError}" && exit 1
fi
@@ -241,8 +255,15 @@ main() {
exit 0
}
if [[ "$1" == "--check-only" ]]; then
CHECK_ONLY=true
fi
CHECK_ONLY=false
skipFTL=false
# Check arguments
for var in "$@"; do
case "$var" in
"--check-only") CHECK_ONLY=true ;;
"--skipFTL") skipFTL=true ;;
esac
done
main

View File

@@ -50,9 +50,10 @@ rm -f "/etc/pihole/GitHubVersions"
rm -f "/etc/pihole/localbranches"
rm -f "/etc/pihole/localversions"
# Create new versions file if it does not exist
VERSION_FILE="/etc/pihole/versions"
touch "${VERSION_FILE}"
# Truncates the file to zero length if it exists to clear it up, otherwise creates an empty file.
truncate -s 0 "${VERSION_FILE}"
chmod 644 "${VERSION_FILE}"
# if /pihole.docker.tag file exists, we will use it's value later in this script

View File

@@ -30,9 +30,6 @@ addOrEditKeyValPair() {
local key="${2}"
local value="${3}"
# touch file to prevent grep error if file does not exist yet
touch "${file}"
if grep -q "^${key}=" "${file}"; then
# Key already exists in file, modify the value
sed -i "/^${key}=/c\\${key}=${value}" "${file}"
@@ -73,7 +70,9 @@ getFTLPID() {
# Example getFTLConfigValue dns.piholePTR
#######################
getFTLConfigValue(){
pihole-FTL --config -q "${1}"
# Pipe to cat to avoid pihole-FTL assuming this is an interactive command
# returning colored output.
pihole-FTL --config -q "${1}" | cat
}
#######################
@@ -86,9 +85,17 @@ getFTLConfigValue(){
# setFTLConfigValue dns.upstreams '[ "8.8.8.8" , "8.8.4.4" ]'
#######################
setFTLConfigValue(){
pihole-FTL --config "${1}" "${2}" >/dev/null
if [ $? -eq 5 ]; then
printf " %s %s set by environment variable. Please unset it to use this function\n" "${CROSS}" "${1}"
exit 5
fi
local err
{ pihole-FTL --config "${1}" "${2}" >/dev/null; err="$?"; } || true
case $err in
0) ;;
5)
# FTL returns 5 if the value was set by an environment variable and is therefore read-only
printf " %s %s set by environment variable. Please unset it to use this function\n" "${CROSS}" "${1}";
exit 5;;
*)
printf " %s Failed to set %s. Try with sudo power\n" "${CROSS}" "${1}"
exit 1
esac
}

View File

@@ -66,7 +66,7 @@ CREATE TABLE info
value TEXT NOT NULL
);
INSERT INTO "info" VALUES('version','19');
INSERT INTO "info" VALUES('version','20');
/* This is a flag to indicate if gravity was restored from a backup
false = not restored,
failed = restoration failed due to no backup
@@ -111,7 +111,7 @@ CREATE TRIGGER tr_domainlist_update AFTER UPDATE ON domainlist
UPDATE domainlist SET date_modified = (cast(strftime('%s', 'now') as int)) WHERE domain = NEW.domain;
END;
CREATE VIEW vw_whitelist AS SELECT domain, domainlist.id AS id, domainlist_by_group.group_id AS group_id
CREATE VIEW vw_allowlist AS SELECT domain, domainlist.id AS id, domainlist_by_group.group_id AS group_id
FROM domainlist
LEFT JOIN domainlist_by_group ON domainlist_by_group.domainlist_id = domainlist.id
LEFT JOIN "group" ON "group".id = domainlist_by_group.group_id
@@ -119,7 +119,7 @@ CREATE VIEW vw_whitelist AS SELECT domain, domainlist.id AS id, domainlist_by_gr
AND domainlist.type = 0
ORDER BY domainlist.id;
CREATE VIEW vw_blacklist AS SELECT domain, domainlist.id AS id, domainlist_by_group.group_id AS group_id
CREATE VIEW vw_denylist AS SELECT domain, domainlist.id AS id, domainlist_by_group.group_id AS group_id
FROM domainlist
LEFT JOIN domainlist_by_group ON domainlist_by_group.domainlist_id = domainlist.id
LEFT JOIN "group" ON "group".id = domainlist_by_group.group_id
@@ -127,7 +127,7 @@ CREATE VIEW vw_blacklist AS SELECT domain, domainlist.id AS id, domainlist_by_gr
AND domainlist.type = 1
ORDER BY domainlist.id;
CREATE VIEW vw_regex_whitelist AS SELECT domain, domainlist.id AS id, domainlist_by_group.group_id AS group_id
CREATE VIEW vw_regex_allowlist AS SELECT domain, domainlist.id AS id, domainlist_by_group.group_id AS group_id
FROM domainlist
LEFT JOIN domainlist_by_group ON domainlist_by_group.domainlist_id = domainlist.id
LEFT JOIN "group" ON "group".id = domainlist_by_group.group_id
@@ -135,7 +135,7 @@ CREATE VIEW vw_regex_whitelist AS SELECT domain, domainlist.id AS id, domainlist
AND domainlist.type = 2
ORDER BY domainlist.id;
CREATE VIEW vw_regex_blacklist AS SELECT domain, domainlist.id AS id, domainlist_by_group.group_id AS group_id
CREATE VIEW vw_regex_denylist AS SELECT domain, domainlist.id AS id, domainlist_by_group.group_id AS group_id
FROM domainlist
LEFT JOIN domainlist_by_group ON domainlist_by_group.domainlist_id = domainlist.id
LEFT JOIN "group" ON "group".id = domainlist_by_group.group_id

View File

@@ -8,12 +8,20 @@ utilsfile="${PI_HOLE_SCRIPT_DIR}/utils.sh"
# Get file paths
FTL_PID_FILE="$(getFTLConfigValue files.pid)"
FTL_LOG_FILE="$(getFTLConfigValue files.log.ftl)"
PIHOLE_LOG_FILE="$(getFTLConfigValue files.log.dnsmasq)"
WEBSERVER_LOG_FILE="$(getFTLConfigValue files.log.webserver)"
FTL_PID_FILE="${FTL_PID_FILE:-/run/pihole-FTL.pid}"
FTL_LOG_FILE="${FTL_LOG_FILE:-/var/log/pihole/FTL.log}"
PIHOLE_LOG_FILE="${PIHOLE_LOG_FILE:-/var/log/pihole/pihole.log}"
WEBSERVER_LOG_FILE="${WEBSERVER_LOG_FILE:-/var/log/pihole/webserver.log}"
# Ensure that permissions are set so that pihole-FTL can edit all necessary files
mkdir -p /var/log/pihole
chown -R pihole:pihole /etc/pihole/ /var/log/pihole/
# allow all users read version file (and use pihole -v)
touch /etc/pihole/versions
chmod 0644 /etc/pihole/versions
# allow pihole to access subdirs in /etc/pihole (sets execution bit on dirs)
@@ -28,7 +36,7 @@ chown root:root /etc/pihole/logrotate
# Touch files to ensure they exist (create if non-existing, preserve if existing)
[ -f "${FTL_PID_FILE}" ] || install -D -m 644 -o pihole -g pihole /dev/null "${FTL_PID_FILE}"
[ -f /var/log/pihole/FTL.log ] || install -m 640 -o pihole -g pihole /dev/null /var/log/pihole/FTL.log
[ -f /var/log/pihole/pihole.log ] || install -m 640 -o pihole -g pihole /dev/null /var/log/pihole/pihole.log
[ -f /var/log/pihole/webserver.log ] || install -m 640 -o pihole -g pihole /dev/null /var/log/pihole/webserver.log
[ -f "${FTL_LOG_FILE}" ] || install -m 640 -o pihole -g pihole /dev/null "${FTL_LOG_FILE}"
[ -f "${PIHOLE_LOG_FILE}" ] || install -m 640 -o pihole -g pihole /dev/null "${PIHOLE_LOG_FILE}"
[ -f "${WEBSERVER_LOG_FILE}" ] || install -m 640 -o pihole -g pihole /dev/null "${WEBSERVER_LOG_FILE}"
[ -f /etc/pihole/dhcp.leases ] || install -m 644 -o pihole -g pihole /dev/null /etc/pihole/dhcp.leases

View File

@@ -0,0 +1,40 @@
#!/sbin/openrc-run
# shellcheck shell=sh disable=SC2034
: "${PI_HOLE_SCRIPT_DIR:=/opt/pihole}"
command="/usr/bin/pihole-FTL"
command_user="pihole:pihole"
supervisor=supervise-daemon
command_args_foreground="-f"
command_background=true
pidfile="/run/${RC_SVCNAME}_openrc.pid"
extra_started_commands="reload"
respawn_max=5
respawn_period=60
capabilities="^CAP_NET_BIND_SERVICE,^CAP_NET_RAW,^CAP_NET_ADMIN,^CAP_SYS_NICE,^CAP_IPC_LOCK,^CAP_CHOWN,^CAP_SYS_TIME"
depend() {
want net
provide dns
}
checkconfig() {
$command -f test
}
start_pre() {
sh "${PI_HOLE_SCRIPT_DIR}/pihole-FTL-prestart.sh"
}
stop_post() {
sh "${PI_HOLE_SCRIPT_DIR}/pihole-FTL-poststop.sh"
}
reload() {
checkconfig || return $?
ebegin "Reloading ${RC_SVCNAME}"
start-stop-daemon --signal HUP --pidfile "${pidfile}"
eend $?
}

View File

@@ -17,15 +17,15 @@ StartLimitIntervalSec=60s
[Service]
User=pihole
PermissionsStartOnly=true
AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_NET_ADMIN CAP_SYS_NICE CAP_IPC_LOCK CAP_CHOWN CAP_SYS_TIME
ExecStartPre=/opt/pihole/pihole-FTL-prestart.sh
# Run prestart with elevated permissions
ExecStartPre=+/opt/pihole/pihole-FTL-prestart.sh
ExecStart=/usr/bin/pihole-FTL -f
Restart=on-failure
RestartSec=5s
ExecReload=/bin/kill -HUP $MAINPID
ExecStopPost=/opt/pihole/pihole-FTL-poststop.sh
ExecStopPost=+/opt/pihole/pihole-FTL-poststop.sh
# Use graceful shutdown with a reasonable timeout
TimeoutStopSec=120s

View File

@@ -1,51 +0,0 @@
_pihole() {
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]}"
prev2="${COMP_WORDS[COMP_CWORD-2]}"
case "${prev}" in
"pihole")
opts="allow allow-regex allow-wild deny checkout debug disable enable flush help logging query repair regex reloaddns reloadlists status tail uninstall updateGravity updatePihole version wildcard arpflush api"
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
;;
"allow"|"deny"|"wildcard"|"regex"|"allow-regex"|"allow-wild")
opts_lists="\not \--delmode \--quiet \--list \--help"
COMPREPLY=( $(compgen -W "${opts_lists}" -- ${cur}) )
;;
"checkout")
opts_checkout="core ftl web master dev"
COMPREPLY=( $(compgen -W "${opts_checkout}" -- ${cur}) )
;;
"debug")
opts_debug="-a"
COMPREPLY=( $(compgen -W "${opts_debug}" -- ${cur}) )
;;
"logging")
opts_logging="on off 'off noflush'"
COMPREPLY=( $(compgen -W "${opts_logging}" -- ${cur}) )
;;
"query")
opts_query="--partial --all"
COMPREPLY=( $(compgen -W "${opts_query}" -- ${cur}) )
;;
"updatePihole"|"-up")
opts_update="--check-only"
COMPREPLY=( $(compgen -W "${opts_update}" -- ${cur}) )
;;
"core"|"admin"|"ftl")
if [[ "$prev2" == "checkout" ]]; then
opts_checkout="master dev"
COMPREPLY=( $(compgen -W "${opts_checkout}" -- ${cur}) )
else
return 1
fi
;;
*)
return 1
;;
esac
return 0
}
complete -F _pihole pihole

View File

@@ -0,0 +1,9 @@
#!/bin/bash
#
# Bash completion script for pihole-FTL
#
# This completion script provides tab completion for pihole-FTL CLI flags and commands.
# It uses the `pihole-FTL --complete` command to generate the completion options.
_complete_FTL() { mapfile -t COMPREPLY < <(pihole-FTL --complete "${COMP_WORDS[@]}"); }
complete -F _complete_FTL pihole-FTL

View File

@@ -0,0 +1,59 @@
#!/bin/bash
#
# Bash completion script for pihole
#
_pihole() {
local cur prev prev2 opts opts_lists opts_checkout opts_debug opts_logging opts_query opts_update opts_networkflush
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
prev2="${COMP_WORDS[COMP_CWORD-2]}"
case "${prev}" in
"pihole")
opts="allow allow-regex allow-wild deny checkout debug disable enable flush help logging query repair regex reloaddns reloadlists setpassword status tail uninstall updateGravity updatePihole version wildcard networkflush api"
mapfile -t COMPREPLY < <(compgen -W "${opts}" -- "${cur}")
;;
"allow"|"deny"|"wildcard"|"regex"|"allow-regex"|"allow-wild")
opts_lists="\not \--delmode \--quiet \--list \--help"
mapfile -t COMPREPLY < <(compgen -W "${opts_lists}" -- "${cur}")
;;
"checkout")
opts_checkout="core ftl web master dev"
mapfile -t COMPREPLY < <(compgen -W "${opts_checkout}" -- "${cur}")
;;
"debug")
opts_debug="-a"
mapfile -t COMPREPLY < <(compgen -W "${opts_debug}" -- "${cur}")
;;
"logging")
opts_logging="on off 'off noflush'"
mapfile -t COMPREPLY < <(compgen -W "${opts_logging}" -- "${cur}")
;;
"query")
opts_query="--partial --all"
mapfile -t COMPREPLY < <(compgen -W "${opts_query}" -- "${cur}")
;;
"updatePihole"|"-up")
opts_update="--check-only"
mapfile -t COMPREPLY < <(compgen -W "${opts_update}" -- "${cur}")
;;
"networkflush")
opts_networkflush="--arp"
mapfile -t COMPREPLY < <(compgen -W "${opts_networkflush}" -- "${cur}")
;;
"core"|"web"|"ftl")
if [[ "$prev2" == "checkout" ]]; then
opts_checkout="master development"
mapfile -t COMPREPLY < <(compgen -W "${opts_checkout}" -- "${cur}")
else
return 1
fi
;;
*)
return 1
;;
esac
return 0
}
complete -F _pihole pihole