Compare commits

..

1 Commits

Author SHA1 Message Date
Christian König
7a352e61bc Add trufflehog to testworkflow
Some checks are pending
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
Signed-off-by: Christian König <github@yubiuser.dev>
2025-12-07 17:55:39 +01:00
11 changed files with 208 additions and 98 deletions

View File

@@ -25,16 +25,16 @@ jobs:
steps:
-
name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
# Initializes the CodeQL tools for scanning.
-
name: Initialize CodeQL
uses: github/codeql-action/init@9e907b5e64f6b83e7804b09294d44122997950d6 #v4.32.3
uses: github/codeql-action/init@cf1bb45a277cb3c205638b2cd5c984db1c46a412 #v4.31.7
with:
languages: 'python'
-
name: Autobuild
uses: github/codeql-action/autobuild@9e907b5e64f6b83e7804b09294d44122997950d6 #v4.32.3
uses: github/codeql-action/autobuild@cf1bb45a277cb3c205638b2cd5c984db1c46a412 #v4.31.7
-
name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@9e907b5e64f6b83e7804b09294d44122997950d6 #v4.32.3
uses: github/codeql-action/analyze@cf1bb45a277cb3c205638b2cd5c984db1c46a412 #v4.31.7

View File

@@ -40,7 +40,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- name: Remove 'stale' label
run: gh issue edit ${{ github.event.issue.number }} --remove-label ${{ env.stale_label }}
env:

View File

@@ -33,7 +33,7 @@ jobs:
name: Syncing branches
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- 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:

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
with:
fetch-depth: 0 # Differential ShellCheck requires full git history
@@ -31,11 +31,15 @@ jobs:
[[ $FAIL == 1 ]] && exit 1 || echo "Scripts are executable!"
- name: Differential ShellCheck
uses: redhat-plumbers-in-action/differential-shellcheck@d965e66ec0b3b2f821f75c8eff9b12442d9a7d1e #v5.5.6
uses: redhat-plumbers-in-action/differential-shellcheck@0d9e5b29625f871e6a4215380486d6f1a7cb6cdd #v5.5.5
with:
severity: warning
display-engine: sarif-fmt
- name: Secret Scanning with TruffleHog
uses: trufflesecurity/trufflehog@821e8b9e5cdf8dc484dd23e06f78941fcf6b9191 #v3.91.2
with:
extra_args: --results=verified,unknown
- name: Spell-Checking
uses: codespell-project/actions-codespell@8f01853be192eb0f849a5c7d721450e7a467c579 #v2.2
@@ -49,7 +53,7 @@ jobs:
run: editorconfig-checker
- name: Check python code formatting with black
uses: psf/black@6305bf1ae645ab7541be4f5028a86239316178eb #26.1.0
uses: psf/black@05f0a8ce1f71fbb36e1e032d3b518c7b945089a2 #25.11.0
with:
src: "./test"
options: "--check --diff --color"
@@ -83,10 +87,10 @@ jobs:
DISTRO: ${{matrix.distro}}
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 #v6.2.0
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 #v6.1.0
with:
python-version: "3.13"

View File

@@ -1251,6 +1251,10 @@ 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
@@ -1803,12 +1807,6 @@ 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} ||
{

View File

@@ -947,7 +947,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 -g -r recreate\" instead."
echo -e " ${CROSS} Recovery failed. Try \"pihole -r recreate\" instead."
exit 1
fi
echo ""

View File

@@ -51,19 +51,29 @@ 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 <<EOF> {script}\n{content}\nEOF
@@ -84,23 +94,37 @@ 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 <<EOF> {script}\n{content}\nEOF
@@ -117,19 +141,29 @@ 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(r"""
esac""")
mock_script += dedent(
"""
esac"""
)
container.run(
"""
cat <<EOF> {script}\n{content}\nEOF
@@ -146,19 +180,29 @@ 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(r"""
esac""")
mock_script += dedent(
"""
esac"""
)
container.run(
"""
cat <<EOF> {script}\n{content}\nEOF

View File

@@ -1,6 +1,6 @@
pyyaml == 6.0.3
pytest == 9.0.2
pytest == 9.0.1
pytest-xdist == 3.8.0
pytest-testinfra == 10.2.2
tox == 4.35.0
tox == 4.32.0
pytest-clarity == 1.0.1

View File

@@ -6,8 +6,10 @@ from .conftest import (
info_box,
cross_box,
mock_command,
mock_command_run,
mock_command_2,
mock_command_passthrough,
run_script,
)
FTL_BRANCH = "development"
@@ -21,10 +23,12 @@ 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
@@ -34,11 +38,13 @@ 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
@@ -89,7 +95,8 @@ 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
@@ -98,7 +105,8 @@ 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:
@@ -154,6 +162,12 @@ 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
)
@@ -187,11 +201,13 @@ 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()
@@ -202,11 +218,13 @@ 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
@@ -242,14 +260,16 @@ 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
@@ -269,18 +289,22 @@ 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
@@ -295,10 +319,12 @@ 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
@@ -318,10 +344,12 @@ 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
@@ -341,10 +369,12 @@ 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
@@ -365,10 +395,12 @@ 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
@@ -389,10 +421,12 @@ 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
@@ -403,10 +437,14 @@ 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
@@ -441,13 +479,15 @@ 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
@@ -456,17 +496,21 @@ 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

View File

@@ -1,25 +1,31 @@
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
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
@@ -30,7 +36,8 @@ 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)
@@ -38,12 +45,15 @@ 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

View File

@@ -15,10 +15,14 @@ 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):
@@ -26,10 +30,12 @@ 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"
@@ -42,10 +48,12 @@ 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
@@ -56,10 +64,12 @@ 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