diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 728c789e..242a9de5 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -25,16 +25,16 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@cf1bb45a277cb3c205638b2cd5c984db1c46a412 #v4.31.7 + uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 #v4.32.6 with: languages: 'python' - name: Autobuild - uses: github/codeql-action/autobuild@cf1bb45a277cb3c205638b2cd5c984db1c46a412 #v4.31.7 + uses: github/codeql-action/autobuild@0d579ffd059c29b07949a3cce3983f0780820c98 #v4.32.6 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@cf1bb45a277cb3c205638b2cd5c984db1c46a412 #v4.31.7 + uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 #v4.32.6 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index deeaa675..bb646a3e 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -17,7 +17,7 @@ jobs: issues: write steps: - - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d #v10.1.1 + - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f #v10.2.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 30 @@ -40,7 +40,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 - name: Remove 'stale' label run: gh issue edit ${{ github.event.issue.number }} --remove-label ${{ env.stale_label }} env: diff --git a/.github/workflows/stale_pr.yml b/.github/workflows/stale_pr.yml index 6dfcbe99..44605aab 100644 --- a/.github/workflows/stale_pr.yml +++ b/.github/workflows/stale_pr.yml @@ -17,7 +17,7 @@ jobs: pull-requests: write steps: - - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d #v10.1.1 + - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f #v10.2.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} # Do not automatically mark PR/issue as stale diff --git a/.github/workflows/sync-back-to-dev.yml b/.github/workflows/sync-back-to-dev.yml index 4901f359..058bbcab 100644 --- a/.github/workflows/sync-back-to-dev.yml +++ b/.github/workflows/sync-back-to-dev.yml @@ -33,7 +33,7 @@ jobs: name: Syncing branches steps: - name: Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 - name: Opening pull request run: gh pr create -B development -H master --title 'Sync master back into development' --body 'Created by Github action' --label 'internal' env: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 35ecc6ab..8243d6e2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 with: fetch-depth: 0 # Differential ShellCheck requires full git history @@ -31,7 +31,7 @@ jobs: [[ $FAIL == 1 ]] && exit 1 || echo "Scripts are executable!" - name: Differential ShellCheck - uses: redhat-plumbers-in-action/differential-shellcheck@0d9e5b29625f871e6a4215380486d6f1a7cb6cdd #v5.5.5 + uses: redhat-plumbers-in-action/differential-shellcheck@d965e66ec0b3b2f821f75c8eff9b12442d9a7d1e #v5.5.6 with: severity: warning display-engine: sarif-fmt @@ -53,7 +53,7 @@ jobs: run: editorconfig-checker - name: Check python code formatting with black - uses: psf/black@05f0a8ce1f71fbb36e1e032d3b518c7b945089a2 #25.11.0 + uses: psf/black@c6755bb741b6481d6b3d3bb563c83fa060db96c9 #26.3.1 with: src: "./test" options: "--check --diff --color" @@ -87,10 +87,10 @@ jobs: DISTRO: ${{matrix.distro}} steps: - name: Checkout repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 - name: Set up Python - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 #v6.1.0 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 #v6.2.0 with: python-version: "3.13" diff --git a/advanced/Scripts/piholeCheckout.sh b/advanced/Scripts/piholeCheckout.sh index a6df46f2..deb07172 100755 --- a/advanced/Scripts/piholeCheckout.sh +++ b/advanced/Scripts/piholeCheckout.sh @@ -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 @@ -235,7 +251,7 @@ 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_RED} Error: Unable to complete update, please contact support${COL_NC}" diff --git a/advanced/Scripts/piholeDebug.sh b/advanced/Scripts/piholeDebug.sh index 964fff9b..baf75d24 100755 --- a/advanced/Scripts/piholeDebug.sh +++ b/advanced/Scripts/piholeDebug.sh @@ -169,7 +169,7 @@ initialize_debug() { # Display that the debug process is beginning log_write "${COL_PURPLE}*** [ INITIALIZING ]${COL_NC}" # Timestamp the start of the log - log_write "${INFO} $(date "+%Y-%m-%d:%H:%M:%S") debug log has been initialized." + log_write "${INFO} $(date "+%Y-%m-%d %H:%M:%S") debug log has been initialized." # Uptime of the system # credits to https://stackoverflow.com/questions/28353409/bash-format-uptime-to-show-days-hours-minutes system_uptime=$(uptime | awk -F'( |,|:)+' '{if ($7=="min") m=$6; else {if ($7~/^day/){if ($9=="min") {d=$6;m=$8} else {d=$6;h=$8;m=$9}} else {h=$6;m=$7}}} {print d+0,"days,",h+0,"hours,",m+0,"minutes"}') diff --git a/advanced/Scripts/piholeLogFlush.sh b/advanced/Scripts/piholeLogFlush.sh index ac28aed9..10b4f320 100755 --- a/advanced/Scripts/piholeLogFlush.sh +++ b/advanced/Scripts/piholeLogFlush.sh @@ -17,11 +17,6 @@ utilsfile="${PI_HOLE_SCRIPT_DIR}/utils.sh" # shellcheck source="./advanced/Scripts/utils.sh" source "${utilsfile}" -# In case we're running at the same time as a system logrotate, use a -# separate logrotate state file to prevent stepping on each other's -# toes. -STATEFILE="/var/lib/logrotate/pihole" - # Determine database location DBFILE=$(getFTLConfigValue "files.database") if [ -z "$DBFILE" ]; then @@ -42,25 +37,6 @@ if [ -z "$WEBFILE" ]; then WEBFILE="/var/log/pihole/webserver.log" fi -# Helper function to handle log rotation for a single file -rotate_log() { - # This function copies x.log over to x.log.1 - # and then empties x.log - # Note that moving the file is not an option, as - # dnsmasq would happily continue writing into the - # moved file (it will have the same file handler) - local logfile="$1" - if [[ "$*" != *"quiet"* ]]; then - echo -ne " ${INFO} Rotating ${logfile} ..." - fi - cp -p "${logfile}" "${logfile}.1" - echo " " > "${logfile}" - chmod 640 "${logfile}" - if [[ "$*" != *"quiet"* ]]; then - echo -e "${OVER} ${TICK} Rotated ${logfile} ..." - fi -} - # Helper function to handle log flushing for a single file flush_log() { local logfile="$1" @@ -78,41 +54,23 @@ flush_log() { fi } -if [[ "$*" == *"once"* ]]; then - # Nightly logrotation - if command -v /usr/sbin/logrotate >/dev/null; then - # Logrotate once +# Manual flushing +flush_log "${LOGFILE}" +flush_log "${FTLFILE}" +flush_log "${WEBFILE}" - 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 - rotate_log "${LOGFILE}" - rotate_log "${FTLFILE}" - rotate_log "${WEBFILE}" - fi -else - # Manual flushing - flush_log "${LOGFILE}" - flush_log "${FTLFILE}" - flush_log "${WEBFILE}" - - if [[ "$*" != *"quiet"* ]]; then - echo -ne " ${INFO} Flushing database, DNS resolution temporarily unavailable ..." - fi - - # Stop FTL to make sure it doesn't write to the database while we're deleting data - service pihole-FTL stop - - # Delete most recent 24 hours from FTL's database, leave even older data intact (don't wipe out all history) - deleted=$(pihole-FTL sqlite3 -ni "${DBFILE}" "DELETE FROM query_storage WHERE timestamp >= strftime('%s','now')-86400; select changes() from query_storage limit 1") - - # Restart FTL - service pihole-FTL restart - if [[ "$*" != *"quiet"* ]]; then - echo -e "${OVER} ${TICK} Deleted ${deleted} queries from long-term query database" - fi +if [[ "$*" != *"quiet"* ]]; then + echo -ne " ${INFO} Flushing database, DNS resolution temporarily unavailable ..." +fi + +# Stop FTL to make sure it doesn't write to the database while we're deleting data +service pihole-FTL stop + +# Delete most recent 24 hours from FTL's database, leave even older data intact (don't wipe out all history) +deleted=$(pihole-FTL sqlite3 -ni "${DBFILE}" "DELETE FROM query_storage WHERE timestamp >= strftime('%s','now')-86400; select changes() from query_storage limit 1") + +# Restart FTL +service pihole-FTL restart +if [[ "$*" != *"quiet"* ]]; then + echo -e "${OVER} ${TICK} Deleted ${deleted} queries from long-term query database" fi diff --git a/advanced/Scripts/piholeLogRotate.sh b/advanced/Scripts/piholeLogRotate.sh new file mode 100755 index 00000000..b7de90ee --- /dev/null +++ b/advanced/Scripts/piholeLogRotate.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# Pi-hole: A black hole for Internet advertisements +# (c) 2025 Pi-hole, LLC (https://pi-hole.net) +# Network-wide ad blocking via your own hardware. +# +# Rotate Pi-hole's log file +# +# This file is copyright under the latest version of the EUPL. +# Please see LICENSE file for your rights under this license. + +colfile="/opt/pihole/COL_TABLE" +# shellcheck source="./advanced/Scripts/COL_TABLE" +source ${colfile} + +readonly PI_HOLE_SCRIPT_DIR="/opt/pihole" +utilsfile="${PI_HOLE_SCRIPT_DIR}/utils.sh" +# shellcheck source="./advanced/Scripts/utils.sh" +source "${utilsfile}" + +# In case we're running at the same time as a system logrotate, use a +# separate logrotate state file to prevent stepping on each other's +# toes. +STATEFILE="/var/lib/logrotate/pihole" + + +# Determine log file location +LOGFILE=$(getFTLConfigValue "files.log.dnsmasq") +if [ -z "$LOGFILE" ]; then + LOGFILE="/var/log/pihole/pihole.log" +fi +FTLFILE=$(getFTLConfigValue "files.log.ftl") +if [ -z "$FTLFILE" ]; then + FTLFILE="/var/log/pihole/FTL.log" +fi +WEBFILE=$(getFTLConfigValue "files.log.webserver") +if [ -z "$WEBFILE" ]; then + WEBFILE="/var/log/pihole/webserver.log" +fi + +# Helper function to handle log rotation for a single file +rotate_log() { + # This function copies x.log over to x.log.1 + # and then empties x.log + # Note that moving the file is not an option, as + # dnsmasq would happily continue writing into the + # moved file (it will have the same file handler) + local logfile="$1" + if [[ "$*" != *"quiet"* ]]; then + echo -ne " ${INFO} Rotating ${logfile} ..." + fi + cp -p "${logfile}" "${logfile}.1" + echo " " > "${logfile}" + chmod 640 "${logfile}" + if [[ "$*" != *"quiet"* ]]; then + echo -e "${OVER} ${TICK} Rotated ${logfile} ..." + fi +} + +# Nightly logrotation +if command -v /usr/sbin/logrotate >/dev/null; then + # Logrotate once + 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 + rotate_log "${LOGFILE}" + rotate_log "${FTLFILE}" + rotate_log "${WEBFILE}" +fi diff --git a/advanced/Scripts/piholeNetworkFlush.sh b/advanced/Scripts/piholeNetworkFlush.sh index a8721476..a156362d 100755 --- a/advanced/Scripts/piholeNetworkFlush.sh +++ b/advanced/Scripts/piholeNetworkFlush.sh @@ -15,7 +15,7 @@ if [[ -f ${coltable} ]]; then source ${coltable} fi -readonly PI_HOLE_SCRIPT_DIR="/opt/pihole" +PI_HOLE_SCRIPT_DIR="/opt/pihole" utilsfile="${PI_HOLE_SCRIPT_DIR}/utils.sh" # shellcheck source=./advanced/Scripts/utils.sh source "${utilsfile}" diff --git a/advanced/Scripts/update.sh b/advanced/Scripts/update.sh index 4e0d973e..50602d2d 100755 --- a/advanced/Scripts/update.sh +++ b/advanced/Scripts/update.sh @@ -102,6 +102,50 @@ GitCheckUpdateAvail() { fi } +updateWarnDialog() { + # Display the warning dialog + + local core_str web_str ftl_str + + if [[ "${core_update}" == true ]]; then + core_str="Core: \\Zb\\Z1update available\\Zn" + else + core_str="Core: \\Zb\\Z4up to date\\Zn" + fi + if [[ "${web_update}" == true ]]; then + web_str="Web: \\Zb\\Z1update available\\Zn" + else + web_str="Web: \\Zb\\Z4up to date\\Zn" + fi + if [[ "${FTL_update}" == true ]]; then + ftl_str="FTL: \\Zb\\Z1update available\\Zn" + else + ftl_str="FTL: \\Zb\\Z4up to date\\Zn" + fi + # shellcheck disable=SC2154 # Variables "${r}" "${c}" are defined in the main script + dialog --no-shadow --clear --keep-tite \ + --colors \ + --backtitle "Updating Pi-hole" \ + --title "Warning" \ + --no-button "Exit" --yes-button "Continue" \ + --defaultno \ + --yesno "\\nThe following Pi-hole components are going to be updated.\\n\\n\\n\ + $core_str\\n\ + $web_str\\n\ + $ftl_str\\n\\n\\n\ +\\Zb\\Z1IMPORTANT:\\Zn Make a (teleporter) backup of your system!\\n\\n\ +Updates can come with significant changes. Please read the changelog at https://pi-hole.net/blog carefully.\\n\\n\\n\ +Please confirm you want to start the update process." \ + "${r}" "${c}" && result=0 || result="$?" + + case "${result}" in + "${DIALOG_CANCEL}" | "${DIALOG_ESC}") + printf " %b User canceled the update process.\\n" "${INFO}" + exit 1 + ;; + esac +} + main() { local basicError="\\n ${COL_RED}Unable to complete update, please contact Pi-hole Support${COL_NC}" local core_update @@ -149,31 +193,37 @@ main() { 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_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 + echo -e " ${INFO} FTL:\\t\\t${COL_YELLOW}--skipFTL set - update check skipped${COL_NC}" FTL_update=false fi @@ -202,6 +252,11 @@ main() { exit 0 fi + # if there is any update, show the warning dialog and ask for confirmation + if [[ "${core_update}" == true || "${web_update}" == true || "${FTL_update}" == true ]]; then + updateWarnDialog + fi + if [[ "${core_update}" == true ]]; then echo "" echo -e " ${INFO} Pi-hole core files out of date, updating local repo." @@ -222,7 +277,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 @@ -242,8 +304,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 diff --git a/advanced/Scripts/updatecheck.sh b/advanced/Scripts/updatecheck.sh index 44f21419..c2ffd5a6 100755 --- a/advanced/Scripts/updatecheck.sh +++ b/advanced/Scripts/updatecheck.sh @@ -10,32 +10,32 @@ function get_local_branch() { # Return active branch - cd "${1}" 2>/dev/null || return 1 - git rev-parse --abbrev-ref HEAD || return 1 + cd "${1}" 2>/dev/null || { echo "null"; return; } + git rev-parse --abbrev-ref HEAD || echo "null" } function get_local_version() { # Return active version - cd "${1}" 2>/dev/null || return 1 - git describe --tags --always 2>/dev/null || return 1 + cd "${1}" 2>/dev/null || { echo "null"; return; } + git describe --tags --always 2>/dev/null || echo "null" } function get_local_hash() { - cd "${1}" 2>/dev/null || return 1 - git rev-parse --short=8 HEAD || return 1 + cd "${1}" 2>/dev/null || { echo "null"; return; } + git rev-parse --short=8 HEAD || echo "null" } 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 + curl -s "https://api.github.com/repos/pi-hole/${1}/releases/latest" 2>/dev/null | jq --raw-output .tag_name || echo "null" 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 + git ls-remote "https://github.com/pi-hole/${1}" --tags "${2}" | awk '{print substr($0, 1,8);}' || echo "null" } # Source the utils file for addOrEditKeyValPair() @@ -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 diff --git a/advanced/Scripts/utils.sh b/advanced/Scripts/utils.sh index d000a6db..51f5db36 100755 --- a/advanced/Scripts/utils.sh +++ b/advanced/Scripts/utils.sh @@ -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}" diff --git a/advanced/Scripts/version.sh b/advanced/Scripts/version.sh index e932fe63..03c57f4c 100755 --- a/advanced/Scripts/version.sh +++ b/advanced/Scripts/version.sh @@ -13,14 +13,23 @@ cachedVersions="/etc/pihole/versions" if [ -f ${cachedVersions} ]; then # shellcheck source=/dev/null - . "$cachedVersions" + . "${cachedVersions}" else echo "Could not find /etc/pihole/versions. Running update now." pihole updatechecker # shellcheck source=/dev/null - . "$cachedVersions" + . "${cachedVersions}" fi +# Convert "null" or empty values to "N/A" for display +normalize_version() { + if [ -z "${1}" ] || [ "${1}" = "null" ]; then + echo "N/A" + else + echo "${1}" + fi +} + main() { local details details=false @@ -33,21 +42,21 @@ main() { 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 " Version is $(normalize_version "${CORE_VERSION}") (Latest: $(normalize_version "${GITHUB_CORE_VERSION}"))" + echo " Branch is $(normalize_version "${CORE_BRANCH}")" + echo " Hash is $(normalize_version "${CORE_HASH}") (Latest: $(normalize_version "${GITHUB_CORE_HASH}"))" 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 " Version is $(normalize_version "${WEB_VERSION}") (Latest: $(normalize_version "${GITHUB_WEB_VERSION}"))" + echo " Branch is $(normalize_version "${WEB_BRANCH}")" + echo " Hash is $(normalize_version "${WEB_HASH}") (Latest: $(normalize_version "${GITHUB_WEB_HASH}"))" 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})" + echo " Version is $(normalize_version "${FTL_VERSION}") (Latest: $(normalize_version "${GITHUB_FTL_VERSION}"))" + echo " Branch is $(normalize_version "${FTL_BRANCH}")" + echo " Hash is $(normalize_version "${FTL_HASH}") (Latest: $(normalize_version "${GITHUB_FTL_HASH}"))" else - 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})" + echo "Core version is $(normalize_version "${CORE_VERSION}") (Latest: $(normalize_version "${GITHUB_CORE_VERSION}"))" + echo "Web version is $(normalize_version "${WEB_VERSION}") (Latest: $(normalize_version "${GITHUB_WEB_VERSION}"))" + echo "FTL version is $(normalize_version "${FTL_VERSION}") (Latest: $(normalize_version "${GITHUB_FTL_VERSION}"))" fi } diff --git a/advanced/Templates/pihole-FTL.service b/advanced/Templates/pihole-FTL.service index 7c7e9962..9cdad7a0 100644 --- a/advanced/Templates/pihole-FTL.service +++ b/advanced/Templates/pihole-FTL.service @@ -57,9 +57,9 @@ start() { stop() { if is_running; then kill "${FTL_PID}" - # Give FTL 60 seconds to gracefully stop + # Give FTL 120 seconds to gracefully stop i=1 - while [ "${i}" -le 60 ]; do + while [ "${i}" -le 120 ]; do if ! is_running; then break fi diff --git a/advanced/Templates/pihole-FTL.systemd b/advanced/Templates/pihole-FTL.systemd index 29470c5a..5024b9a2 100644 --- a/advanced/Templates/pihole-FTL.systemd +++ b/advanced/Templates/pihole-FTL.systemd @@ -28,7 +28,7 @@ ExecReload=/bin/kill -HUP $MAINPID ExecStopPost=+/opt/pihole/pihole-FTL-poststop.sh # Use graceful shutdown with a reasonable timeout -TimeoutStopSec=60s +TimeoutStopSec=120s # Make /usr, /boot, /etc and possibly some more folders read-only... ProtectSystem=full diff --git a/advanced/Templates/pihole.cron b/advanced/Templates/pihole.cron index c62d31ab..3b71cbff 100644 --- a/advanced/Templates/pihole.cron +++ b/advanced/Templates/pihole.cron @@ -24,7 +24,7 @@ # The flush script will use logrotate if available # parameter "once": logrotate only once (default is twice) # parameter "quiet": don't print messages -00 00 * * * root PATH="$PATH:/usr/sbin:/usr/local/bin/" pihole flush once quiet +00 00 * * * root PATH="$PATH:/usr/sbin:/usr/local/bin/" pihole logrotate quiet @reboot root /usr/sbin/logrotate --state /var/lib/logrotate/pihole /etc/pihole/logrotate diff --git a/automated install/basic-install.sh b/automated install/basic-install.sh index a0b37a9c..b12a2714 100755 --- a/automated install/basic-install.sh +++ b/automated install/basic-install.sh @@ -84,7 +84,7 @@ webInterfaceDir="${webroot}/admin" piholeGitUrl="https://github.com/pi-hole/pi-hole.git" PI_HOLE_LOCAL_REPO="/etc/.pihole" # List of pihole scripts, stored in an array -PI_HOLE_FILES=(list piholeDebug piholeLogFlush setupLCD update version gravity uninstall webpage) +PI_HOLE_FILES=(list piholeDebug piholeLogFlush piholeLogRotate setupLCD update version gravity uninstall webpage) # This directory is where the Pi-hole scripts will be installed PI_HOLE_INSTALL_DIR="/opt/pihole" PI_HOLE_CONFIG_DIR="/etc/pihole" @@ -165,6 +165,8 @@ PIHOLE_META_DEPS_APK=( cronie curl dialog + doas # sudo replacement + doas-sudo-shim git grep iproute2-minimal # piholeARPTable.sh @@ -178,7 +180,6 @@ PIHOLE_META_DEPS_APK=( procps-ng psmisc shadow - sudo tzdata unzip ) @@ -188,14 +189,27 @@ PIHOLE_META_DEPS_APK=( # The runUnattended flag is one example of this repair=false runUnattended=false +skipFTL=false # Check arguments for the undocumented flags for var in "$@"; do case "${var}" in "--repair") repair=true ;; "--unattended") runUnattended=true ;; + "--skipFTL") skipFTL=true ;; esac done +if [[ "${runUnattended}" == true ]]; then + # In order to run an unattended setup, a pre-seeded /etc/pihole/pihole.toml must exist + if [[ ! -f "${PI_HOLE_CONFIG_DIR}/pihole.toml" ]]; then + printf " %b Error: \"%s\" not found. Cannot run unattended setup\\n" "${CROSS}" "${PI_HOLE_CONFIG_DIR}/pihole.toml" + exit 1 + fi + printf " %b Performing unattended setup, no dialogs will be displayed\\n" "${INFO}" + # also disable debconf-apt-progress dialogs + export DEBIAN_FRONTEND="noninteractive" +fi + # If the color table file exists, if [[ -f "${coltable}" ]]; then # source it @@ -1251,10 +1265,6 @@ install_manpage() { # if not present, create man8 directory install -d -m 755 /usr/local/share/man/man8 fi - if [[ ! -d "/usr/local/share/man/man5" ]]; then - # if not present, create man5 directory - install -d -m 755 /usr/local/share/man/man5 - fi # Testing complete, copy the files & update the man db install -D -m 644 -T ${PI_HOLE_LOCAL_REPO}/manpages/pihole.8 /usr/local/share/man/man8/pihole.8 @@ -1807,6 +1817,12 @@ clone_or_reset_repos() { # If the user wants to repair/update, if [[ "${repair}" == true ]]; then printf " %b Resetting local repos\\n" "${INFO}" + + # import getFTLConfigValue from utils.sh + source "/opt/pihole/utils.sh" + # Use the configured Web repo location on repair/update + webInterfaceDir=$(getFTLConfigValue "webserver.paths.webroot")$(getFTLConfigValue "webserver.paths.webhome") + # Reset the Core repo resetRepo ${PI_HOLE_LOCAL_REPO} || { @@ -1948,9 +1964,18 @@ get_binary_name() { # If the machine is aarch64 (armv8) if [[ "${machine}" == "aarch64" ]]; then - # If AArch64 is found (e.g., BCM2711 in Raspberry Pi 4) - printf "%b %b Detected AArch64 (64 Bit ARM) architecture\\n" "${OVER}" "${TICK}" - l_binary="pihole-FTL-arm64" + if [[ "$(getconf LONG_BIT)" == "64" ]]; then + # If the OS is 64 bit, we use the arm64 binary + printf "%b %b Detected AArch64 (64 Bit ARM) architecture\\n" "${OVER}" "${TICK}" + l_binary="pihole-FTL-arm64" + else + # If the OS is 32 bit, we use the armv7 binary (aarch64 is actually armv8) + # Even though the machine is 64 bit capable, this makes debugging + # very hard as 32bit tools like gdb, etc. cannot analyze the 64 bit + # binary. See FTL issue #2494 for such an example. + printf "%b %b Detected AArch64 (64 Bit ARM) architecture with 32 bit OS\\n" "${OVER}" "${TICK}" + l_binary="pihole-FTL-armv7" + fi elif [[ "${machine}" == "arm"* ]]; then # ARM 32 bit # Get supported processor from other binaries installed on the system @@ -2320,21 +2345,18 @@ main() { # Check if there is a usable FTL binary available on this architecture - do # this early on as FTL is a hard dependency for Pi-hole - local funcOutput - funcOutput=$(get_binary_name) #Store output of get_binary_name here - # Abort early if this processor is not supported (get_binary_name returns empty string) - if [[ "${funcOutput}" == "" ]]; then - printf " %b Upgrade/install aborted\\n" "${CROSS}" "${DISTRO_NAME}" - exit 1 - fi - - if [[ "${fresh_install}" == false ]]; then - # if it's running unattended, - if [[ "${runUnattended}" == true ]]; then - printf " %b Performing unattended setup, no dialogs will be displayed\\n" "${INFO}" - # also disable debconf-apt-progress dialogs - export DEBIAN_FRONTEND="noninteractive" + # Allow the user to skip this check if they are using a self-compiled FTL binary from an unsupported architecture + if [ "${skipFTL}" != true ]; then + # Get the binary name for the current architecture + local funcOutput + funcOutput=$(get_binary_name) #Store output of get_binary_name here + # Abort early if this processor is not supported (get_binary_name returns empty string) + if [[ "${funcOutput}" == "" ]]; then + printf " %b Upgrade/install aborted\\n" "${CROSS}" "${DISTRO_NAME}" + exit 1 fi + else + printf " %b %b--skipFTL set - skipping architecture check%b\\n" "${INFO}" "${COL_YELLOW}" "${COL_NC}" fi if [[ "${fresh_install}" == true ]]; then @@ -2367,13 +2389,18 @@ main() { create_pihole_user # Download and install FTL - 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) - local theRest - theRest="${funcOutput%pihole-FTL*}" # Print the rest of get_binary_name's output to display (cut out from first instance of "pihole-FTL") - if ! FTLdetect "${binary}" "${theRest}"; then - printf " %b FTL Engine not installed\\n" "${CROSS}" - exit 1 + # 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 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) + local theRest + theRest="${funcOutput%pihole-FTL*}" # Print the rest of get_binary_name's output to display (cut out from first instance of "pihole-FTL") + if ! FTLdetect "${binary}" "${theRest}"; then + printf " %b FTL Engine not installed\\n" "${CROSS}" + exit 1 + fi + else + printf " %b %b--skipFTL set - skipping FTL binary installation%b\\n" "${INFO}" "${COL_YELLOW}" "${COL_NC}" fi # Install and log everything to a file diff --git a/gravity.sh b/gravity.sh index 5720ca41..b772dc61 100755 --- a/gravity.sh +++ b/gravity.sh @@ -612,7 +612,7 @@ compareLists() { gravity_DownloadBlocklistFromUrl() { local url="${1}" adlistID="${2}" saveLocation="${3}" compression="${4}" gravity_type="${5}" domain="${6}" local listCurlBuffer str httpCode success="" ip customUpstreamResolver="" - local file_path permissions ip_addr port blocked=false download=true + local file_path ip_addr port blocked=false download=true # modifiedOptions is an array to store all the options used to check if the adlist has been changed upstream local modifiedOptions=() @@ -721,29 +721,40 @@ gravity_DownloadBlocklistFromUrl() { fi fi - # If we are going to "download" a local file, we first check if the target - # file has a+r permission. We explicitly check for all+read because we want - # to make sure that the file is readable by everyone and not just the user - # running the script. - if [[ $url == "file://"* ]]; then + # If we "download" a local file (file://), verify read access before using it. + # When running as root (e.g., via pihole -g), check that the 'pihole' user can read the file + # to match the effective runtime user of FTL; otherwise, check the current user's read access + # (e.g., in Docker or when invoked by a non-root user). The target must + # resolve to a regular file and be readable by the evaluated user. + if [[ "${url}" == "file:/"* ]]; then # Get the file path - file_path=$(echo "$url" | cut -d'/' -f3-) + file_path=$(echo "${url}" | cut -d'/' -f3-) # Check if the file exists and is a regular file (i.e. not a socket, fifo, tty, block). Might still be a symlink. - if [[ ! -f $file_path ]]; then - # Output that the file does not exist - echo -e "${OVER} ${CROSS} ${file_path} does not exist" - download=false - else - # Check if the file or a file referenced by the symlink has a+r permissions - permissions=$(stat -L -c "%a" "$file_path") - if [[ $permissions == *4 || $permissions == *5 || $permissions == *6 || $permissions == *7 ]]; then - # Output that we are using the local file - echo -e "${OVER} ${INFO} Using local file ${file_path}" - else - # Output that the file does not have the correct permissions - echo -e "${OVER} ${CROSS} Cannot read file (file needs to have a+r permission)" + if [[ ! -f ${file_path} ]]; then + # Output that the file does not exist + echo -e "${OVER} ${CROSS} ${file_path} does not exist" download=false - fi + else + if [ "$(id -un)" == "root" ]; then + # If we are root, we need to check if the pihole user has read permission + # otherwise, we might read files that the pihole user should not be able to read + if sudo -u pihole test -r "${file_path}"; then + echo -e "${OVER} ${INFO} Using local file ${file_path}" + else + echo -e "${OVER} ${CROSS} Cannot read file (user 'pihole' lacks read permission)" + download=false + fi + else + # If we are not root, we just check if the current user has read permission + if [[ -r "${file_path}" ]]; then + # Output that we are using the local file + echo -e "${OVER} ${INFO} Using local file ${file_path}" + else + # Output that the file is not readable by the current user + echo -e "${OVER} ${CROSS} Cannot read file (current user '$(id -un)' lacks read permission)" + download=false + fi + fi fi fi @@ -811,6 +822,10 @@ gravity_DownloadBlocklistFromUrl() { fix_owner_permissions "${saveLocation}" # Compare lists if they are identical compareLists "${adlistID}" "${saveLocation}" + # Set permissions for the *.etag file + if [[ -f "${saveLocation}.etag" ]]; then + fix_owner_permissions "${saveLocation}.etag" + fi # Add domains to database table file pihole-FTL "${gravity_type}" parseList "${saveLocation}" "${gravityTEMPfile}" "${adlistID}" done="true" @@ -947,7 +962,7 @@ database_recovery() { else echo -e "${OVER} ${CROSS} ${str} - the following errors happened:" while IFS= read -r line; do echo " - $line"; done <<<"$result" - echo -e " ${CROSS} Recovery failed. Try \"pihole -r recreate\" instead." + echo -e " ${CROSS} Recovery failed. Try \"pihole -g -r recreate\" instead." exit 1 fi echo "" diff --git a/manpages/pihole.8 b/manpages/pihole.8 index ac3146ba..191691ee 100644 --- a/manpages/pihole.8 +++ b/manpages/pihole.8 @@ -100,9 +100,12 @@ Available commands and options: -c Include a Pi-hole database integrity check .br -\fB-f, flush\fR +\fB-f, flush\fR [quite] .br - Flush the Pi-hole log + Flush the Pi-hole log and last 24h from the query database +.br + + quite Suppress output .br \fB-r, repair\fR @@ -242,6 +245,14 @@ Available commands and options: verbose Show authentication and status messages .br +\fBlogrotate\fR [quite] +.br + Rotate Pi-hole's log files +.br + + quite Suppress output +.br + .SH "EXAMPLE" Some usage examples diff --git a/pihole b/pihole index 5af46fa6..edebf48c 100755 --- a/pihole +++ b/pihole @@ -92,8 +92,13 @@ debugFunc() { } flushFunc() { - "${PI_HOLE_SCRIPT_DIR}"/piholeLogFlush.sh "$@" - exit 0 + # unsupported in docker because it requires restarting FTL + if [ -n "${DOCKER_VERSION}" ]; then + unsupportedFunc + else + "${PI_HOLE_SCRIPT_DIR}"/piholeLogFlush.sh "$@" + exit 0 + fi } # Deprecated function, should be removed in the future @@ -105,6 +110,11 @@ arpFunc() { exit 0 } +logrotateFunc() { + "${PI_HOLE_SCRIPT_DIR}"/piholeLogRotate.sh "$@" + exit 0 +} + networkFlush() { shift "${PI_HOLE_SCRIPT_DIR}"/piholeNetworkFlush.sh "$@" @@ -125,7 +135,22 @@ repairPiholeFunc() { if [ -n "${DOCKER_VERSION}" ]; then unsupportedFunc else - /etc/.pihole/automated\ install/basic-install.sh --repair + 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 + + /etc/.pihole/automated\ install/basic-install.sh --repair ${additionalFlag} exit 0; fi } @@ -502,7 +527,8 @@ Debugging Options: -d, debug Start a debugging session Add '-c' or '--check-database' to include a Pi-hole database integrity check Add '-a' to automatically upload the log to tricorder.pi-hole.net - -f, flush Flush the Pi-hole log + -f, flush Flush the Pi-hole logs and last 24h from the query database + Add 'quiet' to suppress output messages -r, repair Repair Pi-hole subsystems -t, tail [arg] View the live output of the Pi-hole log. Add an optional argument to filter the log @@ -535,7 +561,9 @@ Options: checkout Switch Pi-hole subsystems to a different GitHub branch Add '-h' for more info on checkout usage networkflush Flush information stored in Pi-hole's network tables - Add '--arp' to additionally flush the ARP table "; + Add '--arp' to additionally flush the ARP table + logrotate Rotate Pi-hole's log files + Add 'quiet' to suppress output messages"; exit 0 } @@ -578,6 +606,7 @@ case "${1}" in "arpflush" ) need_root=true;; # Deprecated, use networkflush instead "networkflush" ) need_root=true;; "-t" | "tail" ) need_root=true;; + "logrotate" ) need_root=true;; * ) helpFunc;; esac @@ -601,7 +630,7 @@ case "${1}" in "-d" | "debug" ) debugFunc "$@";; "-f" | "flush" ) flushFunc "$@";; "-up" | "updatePihole" ) updatePiholeFunc "$@";; - "-r" | "repair" ) repairPiholeFunc;; + "-r" | "repair" ) repairPiholeFunc "$@";; "-g" | "updateGravity" ) updateGravityFunc "$@";; "-l" | "logging" ) piholeLogging "$@";; "uninstall" ) uninstallFunc;; @@ -613,5 +642,6 @@ case "${1}" in "arpflush" ) arpFunc "$@";; # Deprecated, use networkflush instead "networkflush" ) networkFlush "$@";; "-t" | "tail" ) tailFunc "$2";; + "logrotate" ) logrotateFunc "$@";; * ) helpFunc;; esac diff --git a/test/conftest.py b/test/conftest.py index dcf49790..d4c763e7 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -51,29 +51,19 @@ def mock_command(script, args, container): in unit tests """ full_script_path = "/usr/local/bin/{}".format(script) - mock_script = dedent( - r"""\ + mock_script = dedent(r"""\ #!/bin/bash -e echo "\$0 \$@" >> /var/log/{script} - case "\$1" in""".format( - script=script - ) - ) + case "\$1" in""".format(script=script)) for k, v in args.items(): - case = dedent( - """ + case = dedent(""" {arg}) echo {res} exit {retcode} - ;;""".format( - arg=k, res=v[0], retcode=v[1] - ) - ) + ;;""".format(arg=k, res=v[0], retcode=v[1])) mock_script += case - mock_script += dedent( - """ - esac""" - ) + mock_script += dedent(""" + esac""") container.run( """ cat < {script}\n{content}\nEOF @@ -94,37 +84,23 @@ def mock_command_passthrough(script, args, container): """ orig_script_path = container.check_output("command -v {}".format(script)) full_script_path = "/usr/local/bin/{}".format(script) - mock_script = dedent( - r"""\ + mock_script = dedent(r"""\ #!/bin/bash -e echo "\$0 \$@" >> /var/log/{script} - case "\$1" in""".format( - script=script - ) - ) + case "\$1" in""".format(script=script)) for k, v in args.items(): - case = dedent( - """ + case = dedent(""" {arg}) echo {res} exit {retcode} - ;;""".format( - arg=k, res=v[0], retcode=v[1] - ) - ) + ;;""".format(arg=k, res=v[0], retcode=v[1])) mock_script += case - mock_script += dedent( - r""" + mock_script += dedent(r""" *) {orig_script_path} "\$@" - ;;""".format( - orig_script_path=orig_script_path - ) - ) - mock_script += dedent( - """ - esac""" - ) + ;;""".format(orig_script_path=orig_script_path)) + mock_script += dedent(""" + esac""") container.run( """ cat < {script}\n{content}\nEOF @@ -141,29 +117,19 @@ def mock_command_run(script, args, container): in unit tests """ full_script_path = "/usr/local/bin/{}".format(script) - mock_script = dedent( - r"""\ + mock_script = dedent(r"""\ #!/bin/bash -e echo "\$0 \$@" >> /var/log/{script} - case "\$1 \$2" in""".format( - script=script - ) - ) + case "\$1 \$2" in""".format(script=script)) for k, v in args.items(): - case = dedent( - """ + case = dedent(""" \"{arg}\") echo {res} exit {retcode} - ;;""".format( - arg=k, res=v[0], retcode=v[1] - ) - ) + ;;""".format(arg=k, res=v[0], retcode=v[1])) mock_script += case - mock_script += dedent( - """ - esac""" - ) + mock_script += dedent(r""" + esac""") container.run( """ cat < {script}\n{content}\nEOF @@ -180,29 +146,19 @@ def mock_command_2(script, args, container): in unit tests """ full_script_path = "/usr/local/bin/{}".format(script) - mock_script = dedent( - r"""\ + mock_script = dedent(r"""\ #!/bin/bash -e echo "\$0 \$@" >> /var/log/{script} - case "\$1 \$2" in""".format( - script=script - ) - ) + case "\$1 \$2" in""".format(script=script)) for k, v in args.items(): - case = dedent( - """ + case = dedent(""" \"{arg}\") echo \"{res}\" exit {retcode} - ;;""".format( - arg=k, res=v[0], retcode=v[1] - ) - ) + ;;""".format(arg=k, res=v[0], retcode=v[1])) mock_script += case - mock_script += dedent( - """ - esac""" - ) + mock_script += dedent(r""" + esac""") container.run( """ cat < {script}\n{content}\nEOF diff --git a/test/requirements.txt b/test/requirements.txt index d00f8fbe..c8feda47 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -1,6 +1,6 @@ pyyaml == 6.0.3 -pytest == 9.0.1 +pytest == 9.0.2 pytest-xdist == 3.8.0 pytest-testinfra == 10.2.2 -tox == 4.32.0 +tox == 4.49.1 pytest-clarity == 1.0.1 diff --git a/test/test_any_automated_install.py b/test/test_any_automated_install.py index 0a561b36..aa48fd32 100644 --- a/test/test_any_automated_install.py +++ b/test/test_any_automated_install.py @@ -6,10 +6,8 @@ from .conftest import ( info_box, cross_box, mock_command, - mock_command_run, mock_command_2, mock_command_passthrough, - run_script, ) FTL_BRANCH = "development" @@ -23,12 +21,10 @@ def test_supported_package_manager(host): host.run("rm -rf /usr/bin/apt-get") host.run("rm -rf /usr/bin/rpm") host.run("rm -rf /sbin/apk") - package_manager_detect = host.run( - """ + package_manager_detect = host.run(""" source /opt/pihole/basic-install.sh package_manager_detect - """ - ) + """) expected_stdout = cross_box + " No supported package manager found" assert expected_stdout in package_manager_detect.stdout # assert package_manager_detect.rc == 1 @@ -38,13 +34,11 @@ def test_selinux_not_detected(host): """ confirms installer continues when SELinux configuration file does not exist """ - check_selinux = host.run( - """ + check_selinux = host.run(""" rm -f /etc/selinux/config source /opt/pihole/basic-install.sh checkSelinux - """ - ) + """) expected_stdout = info_box + " SELinux not detected" assert expected_stdout in check_selinux.stdout assert check_selinux.rc == 0 @@ -95,8 +89,7 @@ def test_installPihole_fresh_install_readableFiles(host): host.run("command -v apk > /dev/null && apk add mandoc man-pages") # Workaround to get FTLv6 installed until it reaches master branch host.run('echo "' + FTL_BRANCH + '" > /etc/pihole/ftlbranch') - install = host.run( - """ + install = host.run(""" export TERM=xterm export DEBIAN_FRONTEND=noninteractive umask 0027 @@ -105,8 +98,7 @@ def test_installPihole_fresh_install_readableFiles(host): runUnattended=true main /opt/pihole/pihole-FTL-prestart.sh - """ - ) + """) assert 0 == install.rc maninstalled = True if (info_box + " man not installed") in install.stdout: @@ -162,12 +154,6 @@ def test_installPihole_fresh_install_readableFiles(host): check_man = test_cmd.format("r", "/usr/local/share/man/man8", piholeuser) actual_rc = host.run(check_man).rc assert exit_status_success == actual_rc - check_man = test_cmd.format("x", "/usr/local/share/man/man5", piholeuser) - actual_rc = host.run(check_man).rc - assert exit_status_success == actual_rc - check_man = test_cmd.format("r", "/usr/local/share/man/man5", piholeuser) - actual_rc = host.run(check_man).rc - assert exit_status_success == actual_rc check_man = test_cmd.format( "r", "/usr/local/share/man/man8/pihole.8", piholeuser ) @@ -201,13 +187,11 @@ def test_update_package_cache_success_no_errors(host): """ confirms package cache was updated without any errors """ - updateCache = host.run( - """ + updateCache = host.run(""" source /opt/pihole/basic-install.sh package_manager_detect update_package_cache - """ - ) + """) expected_stdout = tick_box + " Update local cache of available packages" assert expected_stdout in updateCache.stdout assert "error" not in updateCache.stdout.lower() @@ -218,13 +202,11 @@ def test_update_package_cache_failure_no_errors(host): confirms package cache was not updated """ mock_command("apt-get", {"update": ("", "1")}, host) - updateCache = host.run( - """ + updateCache = host.run(""" source /opt/pihole/basic-install.sh package_manager_detect update_package_cache - """ - ) + """) expected_stdout = cross_box + " Update local cache of available packages" assert expected_stdout in updateCache.stdout assert "Error: Unable to update package cache." in updateCache.stdout @@ -260,16 +242,14 @@ def test_FTL_detect_no_errors(host, arch, detected_string, supported): host, ) host.run('echo "' + FTL_BRANCH + '" > /etc/pihole/ftlbranch') - detectPlatform = host.run( - """ + 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}" - """ - ) + """) if supported: expected_stdout = info_box + " FTL Checks..." assert expected_stdout in detectPlatform.stdout @@ -289,22 +269,18 @@ 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( - """ + 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}" - """ - ) - version_check = host.run( - """ + """) + version_check = host.run(""" VERSION=$(pihole-FTL version) echo ${VERSION:0:1} - """ - ) + """) expected_stdout = "v" assert expected_stdout in version_check.stdout @@ -319,12 +295,10 @@ def test_IPv6_only_link_local(host): {"-6 address": ("inet6 fe80::d210:52fa:fe00:7ad7/64 scope link", "0")}, host, ) - detectPlatform = host.run( - """ + detectPlatform = host.run(""" source /opt/pihole/basic-install.sh find_IPv6_information - """ - ) + """) expected_stdout = "Unable to find IPv6 ULA/GUA address" assert expected_stdout in detectPlatform.stdout @@ -344,12 +318,10 @@ def test_IPv6_only_ULA(host): }, host, ) - detectPlatform = host.run( - """ + detectPlatform = host.run(""" source /opt/pihole/basic-install.sh find_IPv6_information - """ - ) + """) expected_stdout = "Found IPv6 ULA address" assert expected_stdout in detectPlatform.stdout @@ -369,12 +341,10 @@ def test_IPv6_only_GUA(host): }, host, ) - detectPlatform = host.run( - """ + detectPlatform = host.run(""" source /opt/pihole/basic-install.sh find_IPv6_information - """ - ) + """) expected_stdout = "Found IPv6 GUA address" assert expected_stdout in detectPlatform.stdout @@ -395,12 +365,10 @@ def test_IPv6_GUA_ULA_test(host): }, host, ) - detectPlatform = host.run( - """ + detectPlatform = host.run(""" source /opt/pihole/basic-install.sh find_IPv6_information - """ - ) + """) expected_stdout = "Found IPv6 ULA address" assert expected_stdout in detectPlatform.stdout @@ -421,12 +389,10 @@ def test_IPv6_ULA_GUA_test(host): }, host, ) - detectPlatform = host.run( - """ + detectPlatform = host.run(""" source /opt/pihole/basic-install.sh find_IPv6_information - """ - ) + """) expected_stdout = "Found IPv6 ULA address" assert expected_stdout in detectPlatform.stdout @@ -437,14 +403,10 @@ def test_validate_ip(host): """ def test_address(addr, success=True): - output = host.run( - """ + output = host.run(""" source /opt/pihole/basic-install.sh valid_ip "{addr}" - """.format( - addr=addr - ) - ) + """.format(addr=addr)) assert output.rc == 0 if success else 1 @@ -479,15 +441,13 @@ def test_validate_ip(host): def test_package_manager_has_pihole_deps(host): """Confirms OS is able to install the required packages for Pi-hole""" mock_command("dialog", {"*": ("", "0")}, host) - output = host.run( - """ + output = host.run(""" source /opt/pihole/basic-install.sh package_manager_detect update_package_cache build_dependency_package install_dependent_packages - """ - ) + """) assert "No package" not in output.stdout assert output.rc == 0 @@ -496,21 +456,17 @@ def test_package_manager_has_pihole_deps(host): def test_meta_package_uninstall(host): """Confirms OS is able to install and uninstall the Pi-hole meta package""" mock_command("dialog", {"*": ("", "0")}, host) - install = host.run( - """ + install = host.run(""" source /opt/pihole/basic-install.sh package_manager_detect update_package_cache build_dependency_package install_dependent_packages - """ - ) + """) assert install.rc == 0 - uninstall = host.run( - """ + uninstall = host.run(""" source /opt/pihole/uninstall.sh removeMetaPackage - """ - ) + """) assert uninstall.rc == 0 diff --git a/test/test_any_utils.py b/test/test_any_utils.py index 0f9ae6d2..e4646572 100644 --- a/test/test_any_utils.py +++ b/test/test_any_utils.py @@ -1,31 +1,26 @@ def test_key_val_replacement_works(host): """Confirms addOrEditKeyValPair either adds or replaces a key value pair in a given file""" - host.run( - """ + host.run(""" source /opt/pihole/utils.sh + touch ./testoutput addOrEditKeyValPair "./testoutput" "KEY_ONE" "value1" addOrEditKeyValPair "./testoutput" "KEY_TWO" "value2" addOrEditKeyValPair "./testoutput" "KEY_ONE" "value3" addOrEditKeyValPair "./testoutput" "KEY_FOUR" "value4" - """ - ) - output = host.run( - """ + """) + output = host.run(""" cat ./testoutput - """ - ) + """) expected_stdout = "KEY_ONE=value3\nKEY_TWO=value2\nKEY_FOUR=value4\n" assert expected_stdout == output.stdout def test_getFTLPID_default(host): """Confirms getFTLPID returns the default value if FTL is not running""" - output = host.run( - """ + output = host.run(""" source /opt/pihole/utils.sh getFTLPID - """ - ) + """) expected_stdout = "-1\n" assert expected_stdout == output.stdout @@ -36,8 +31,7 @@ def test_setFTLConfigValue_getFTLConfigValue(host): Requires FTL to be installed, so we do that first (taken from test_FTL_development_binary_installed_and_responsive_no_errors) """ - host.run( - """ + host.run(""" source /opt/pihole/basic-install.sh create_pihole_user funcOutput=$(get_binary_name) @@ -45,15 +39,12 @@ def test_setFTLConfigValue_getFTLConfigValue(host): binary="pihole-FTL${funcOutput##*pihole-FTL}" theRest="${funcOutput%pihole-FTL*}" FTLdetect "${binary}" "${theRest}" - """ - ) + """) - output = host.run( - """ + output = host.run(""" source /opt/pihole/utils.sh setFTLConfigValue "dns.upstreams" '["9.9.9.9"]' > /dev/null getFTLConfigValue "dns.upstreams" - """ - ) + """) assert "[ 9.9.9.9 ]" in output.stdout diff --git a/test/test_centos_fedora_common_support.py b/test/test_centos_fedora_common_support.py index 7e0bae4e..a892db87 100644 --- a/test/test_centos_fedora_common_support.py +++ b/test/test_centos_fedora_common_support.py @@ -15,14 +15,10 @@ def mock_selinux_config(state, host): # getenforce returns the running state of SELinux mock_command("getenforce", {"*": (state.capitalize(), "0")}, host) # create mock configuration with desired content - host.run( - """ + host.run(""" mkdir /etc/selinux echo "SELINUX={state}" > /etc/selinux/config - """.format( - state=state.lower() - ) - ) + """.format(state=state.lower())) def test_selinux_enforcing_exit(host): @@ -30,12 +26,10 @@ def test_selinux_enforcing_exit(host): confirms installer prompts to exit when SELinux is Enforcing by default """ mock_selinux_config("enforcing", host) - check_selinux = host.run( - """ + check_selinux = host.run(""" source /opt/pihole/basic-install.sh checkSelinux - """ - ) + """) expected_stdout = cross_box + " Current SELinux: enforcing" assert expected_stdout in check_selinux.stdout expected_stdout = "SELinux Enforcing detected, exiting installer" @@ -48,12 +42,10 @@ def test_selinux_permissive(host): confirms installer continues when SELinux is Permissive """ mock_selinux_config("permissive", host) - check_selinux = host.run( - """ + check_selinux = host.run(""" source /opt/pihole/basic-install.sh checkSelinux - """ - ) + """) expected_stdout = tick_box + " Current SELinux: permissive" assert expected_stdout in check_selinux.stdout assert check_selinux.rc == 0 @@ -64,12 +56,10 @@ def test_selinux_disabled(host): confirms installer continues when SELinux is Disabled """ mock_selinux_config("disabled", host) - check_selinux = host.run( - """ + check_selinux = host.run(""" source /opt/pihole/basic-install.sh checkSelinux - """ - ) + """) expected_stdout = tick_box + " Current SELinux: disabled" assert expected_stdout in check_selinux.stdout assert check_selinux.rc == 0