From 0db48383ae9a5c8ffb0da44a79ec86d16bb36d18 Mon Sep 17 00:00:00 2001 From: "Michael Ziminsky (Z)" Date: Mon, 2 Jun 2025 18:23:15 -0700 Subject: [PATCH 1/6] Add support for alpine Signed-off-by: Michael Ziminsky (Z) --- advanced/Templates/pihole-FTL.openrc | 40 +++++++++++++++++ automated install/basic-install.sh | 66 +++++++++++++++++++++++++--- gravity.sh | 7 ++- 3 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 advanced/Templates/pihole-FTL.openrc diff --git a/advanced/Templates/pihole-FTL.openrc b/advanced/Templates/pihole-FTL.openrc new file mode 100644 index 00000000..34a30a0b --- /dev/null +++ b/advanced/Templates/pihole-FTL.openrc @@ -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 $? +} diff --git a/automated install/basic-install.sh b/automated install/basic-install.sh index 9d3bd2b3..d5720df6 100755 --- a/automated install/basic-install.sh +++ b/automated install/basic-install.sh @@ -154,6 +154,34 @@ Pi-hole dependency meta package EOM ) +# List of required packages on APK based systems +PIHOLE_META_VERSION_APK=0.1 +PIHOLE_META_DEPS_APK=( + bash + bash-completion + bind-tools + binutils + coreutils + cronie + curl + dialog + git + grep + iproute2-ss + jq + libcap + logrotate + ncurses + nmap-ncat + procps-ng + psmisc + shadow + sudo + tzdata + unzip + wget +) + ######## Undocumented Flags. Shhh ######## # These are undocumented flags; some of which we can use when repairing an installation # The runUnattended flag is one example of this @@ -271,7 +299,15 @@ package_manager_detect() { PKG_COUNT="${PKG_MANAGER} check-update | grep -E '(.i686|.x86|.noarch|.arm|.src|.riscv64)' | wc -l || true" # The command we will use to remove packages (used in the uninstaller) PKG_REMOVE="${PKG_MANAGER} remove -y" - # If neither apt-get or yum/dnf package managers were found + + # If neither apt-get or yum/dnf package managers were found, check for apk. + elif is_command apk; then + PKG_MANAGER="apk" + UPDATE_PKG_CACHE="${PKG_MANAGER} update" + PKG_INSTALL="${PKG_MANAGER} add" + PKG_COUNT="${PKG_MANAGER} list --upgradable -q | wc -l" + PKG_REMOVE="${PKG_MANAGER} del" + else # we cannot install required packages printf " %b No supported package manager found\\n" "${CROSS}" @@ -282,13 +318,20 @@ package_manager_detect() { build_dependency_package(){ # This function will build a package that contains all the dependencies needed for Pi-hole + if is_command apk ; then + local str="APK based system detected. Dependencies will be installed using a virtual package named pihole-meta" + printf " %b %s...\\n" "${INFO}" "${str}" + return 0 + fi # remove any leftover build directory that may exist rm -rf /tmp/pihole-meta_* # Create a fresh build directory with random name + # Busybox Compat: `mktemp` long flags unsupported + # -d flag is short form of --directory local tempdir - tempdir="$(mktemp --directory /tmp/pihole-meta_XXXXX)" + tempdir="$(mktemp -d /tmp/pihole-meta_XXXXX)" chmod 0755 "${tempdir}" if is_command apt-get; then @@ -1177,7 +1220,12 @@ installConfigs() { # Load final service systemctl daemon-reload else - install -T -m 0755 "${PI_HOLE_LOCAL_REPO}/advanced/Templates/pihole-FTL.service" '/etc/init.d/pihole-FTL' + local INIT="service" + if is_command openrc; then + INIT="openrc" + fi + + install -T -m 0755 "${PI_HOLE_LOCAL_REPO}/advanced/Templates/pihole-FTL.${INIT}" '/etc/init.d/pihole-FTL' fi install -T -m 0755 "${PI_HOLE_LOCAL_REPO}/advanced/Templates/pihole-FTL-prestart.sh" "${PI_HOLE_INSTALL_DIR}/pihole-FTL-prestart.sh" install -T -m 0755 "${PI_HOLE_LOCAL_REPO}/advanced/Templates/pihole-FTL-poststop.sh" "${PI_HOLE_INSTALL_DIR}/pihole-FTL-poststop.sh" @@ -1266,6 +1314,8 @@ enable_service() { if is_command systemctl; then # use that to enable the service systemctl -q enable "${1}" + elif is_command openrc; then + rc-update add "${1}" "${2:-default}" &> /dev/null else # Otherwise, use update-rc.d to accomplish this update-rc.d "${1}" defaults >/dev/null @@ -1282,6 +1332,8 @@ disable_service() { if is_command systemctl; then # use that to disable the service systemctl -q disable "${1}" + elif is_command openrc; then + rc-update del "${1}" "${2:-default}" &> /dev/null else # Otherwise, use update-rc.d to accomplish this update-rc.d "${1}" disable >/dev/null @@ -1294,6 +1346,8 @@ check_service_active() { if is_command systemctl; then # use that to check the status of the service systemctl -q is-enabled "${1}" 2>/dev/null + elif is_command openrc; then + rc-status default boot | grep -q "${1}" else # Otherwise, fall back to service command service "${1}" status &>/dev/null @@ -1395,8 +1449,10 @@ install_dependent_packages() { printf " %b Error: Unable to find Pi-hole dependency package.\\n" "${COL_RED}" return 1 fi - - # If neither apt-get or yum/dnf package managers were found + # Install Alpine packages + elif is_command apk; then + ${PKG_INSTALL} -q -t "pihole-meta=${PIHOLE_META_VERSION_APK}" "${PIHOLE_META_DEPS_APK[@]}" &> /dev/null && + printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}" else # we cannot install the dependency package printf " %b No supported package manager found\\n" "${CROSS}" diff --git a/gravity.sh b/gravity.sh index fd5d45de..a3b44279 100755 --- a/gravity.sh +++ b/gravity.sh @@ -118,9 +118,12 @@ gravity_swap_databases() { # Swap databases and remove or conditionally rename old database # Number of available blocks on disk - availableBlocks=$(stat -f --format "%a" "${gravityDIR}") + # Busybox Compat: `stat` long flags unsupported + # -f flag is short form of --file-system. + # -c flag is short form of --format. + availableBlocks=$(stat -f -c "%a" "${gravityDIR}") # Number of blocks, used by gravity.db - gravityBlocks=$(stat --format "%b" "${gravityDBfile}") + gravityBlocks=$(stat -c "%b" "${gravityDBfile}") # Only keep the old database if available disk space is at least twice the size of the existing gravity.db. # Better be safe than sorry... oldAvail=false From d75dae788d04679161d99cbe52ca6327ecb52469 Mon Sep 17 00:00:00 2001 From: Rob Gill Date: Tue, 15 Apr 2025 16:18:46 +1000 Subject: [PATCH 2/6] Alpine Linux tests Add Dockerfile and corresponding tox file to add alpine linux 3.21 & 3.22 to the test suite Add apk as another package manager in the automated install tests Use short switches for su command as busybox's su does not accept long switches here Add Alpine test to github workflow Signed-off-by: Michael Ziminsky (Z) --- .github/workflows/test.yml | 2 ++ test/_alpine_3_21.Dockerfile | 18 ++++++++++++++++++ test/_alpine_3_22.Dockerfile | 18 ++++++++++++++++++ test/test_any_automated_install.py | 14 +++++++++++++- test/tox.alpine_3_21.ini | 10 ++++++++++ test/tox.alpine_3_22.ini | 10 ++++++++++ 6 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 test/_alpine_3_21.Dockerfile create mode 100644 test/_alpine_3_22.Dockerfile create mode 100644 test/tox.alpine_3_21.ini create mode 100644 test/tox.alpine_3_22.ini diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ee61c30a..492e131b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -73,6 +73,8 @@ jobs: fedora_40, fedora_41, fedora_42, + alpine_3_21, + alpine_3_22, ] env: DISTRO: ${{matrix.distro}} diff --git a/test/_alpine_3_21.Dockerfile b/test/_alpine_3_21.Dockerfile new file mode 100644 index 00000000..d7b88f20 --- /dev/null +++ b/test/_alpine_3_21.Dockerfile @@ -0,0 +1,18 @@ +FROM alpine:3.21 + +ENV GITDIR=/etc/.pihole +ENV SCRIPTDIR=/opt/pihole +RUN sed -i 's/#\(.*\/community\)/\1/' /etc/apk/repositories +RUN apk --no-cache add bash coreutils curl git jq openrc shadow + +RUN mkdir -p $GITDIR $SCRIPTDIR /etc/pihole +ADD . $GITDIR +RUN cp $GITDIR/advanced/Scripts/*.sh $GITDIR/gravity.sh $GITDIR/pihole $GITDIR/automated\ install/*.sh $GITDIR/advanced/Scripts/COL_TABLE $SCRIPTDIR/ +ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$SCRIPTDIR + +RUN true && \ + chmod +x $SCRIPTDIR/* + +ENV SKIP_INSTALL=true + +#sed '/# Start the installer/Q' /opt/pihole/basic-install.sh > /opt/pihole/stub_basic-install.sh && \ diff --git a/test/_alpine_3_22.Dockerfile b/test/_alpine_3_22.Dockerfile new file mode 100644 index 00000000..25beb4e0 --- /dev/null +++ b/test/_alpine_3_22.Dockerfile @@ -0,0 +1,18 @@ +FROM alpine:3.22 + +ENV GITDIR=/etc/.pihole +ENV SCRIPTDIR=/opt/pihole +RUN sed -i 's/#\(.*\/community\)/\1/' /etc/apk/repositories +RUN apk --no-cache add bash coreutils curl git jq openrc shadow + +RUN mkdir -p $GITDIR $SCRIPTDIR /etc/pihole +ADD . $GITDIR +RUN cp $GITDIR/advanced/Scripts/*.sh $GITDIR/gravity.sh $GITDIR/pihole $GITDIR/automated\ install/*.sh $GITDIR/advanced/Scripts/COL_TABLE $SCRIPTDIR/ +ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$SCRIPTDIR + +RUN true && \ + chmod +x $SCRIPTDIR/* + +ENV SKIP_INSTALL=true + +#sed '/# Start the installer/Q' /opt/pihole/basic-install.sh > /opt/pihole/stub_basic-install.sh && \ diff --git a/test/test_any_automated_install.py b/test/test_any_automated_install.py index cf4b454d..0a561b36 100644 --- a/test/test_any_automated_install.py +++ b/test/test_any_automated_install.py @@ -22,6 +22,7 @@ def test_supported_package_manager(host): # break supported package managers 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( """ source /opt/pihole/basic-install.sh @@ -77,10 +78,21 @@ def test_installPihole_fresh_install_readableFiles(host): }, host, ) + mock_command_2( + "rc-service", + { + "rc-service pihole-FTL enable": ("", "0"), + "rc-service pihole-FTL restart": ("", "0"), + "rc-service pihole-FTL start": ("", "0"), + "*": ('echo "rc-service call with $@"', "0"), + }, + host, + ) # try to install man host.run("command -v apt-get > /dev/null && apt-get install -qq man") host.run("command -v dnf > /dev/null && dnf install -y man") host.run("command -v yum > /dev/null && yum install -y man") + host.run("command -v 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( @@ -103,7 +115,7 @@ def test_installPihole_fresh_install_readableFiles(host): maninstalled = False piholeuser = "pihole" exit_status_success = 0 - test_cmd = 'su --shell /bin/bash --command "test -{0} {1}" -p {2}' + test_cmd = 'su -s /bin/bash -c "test -{0} {1}" -p {2}' # check files in /etc/pihole for read, write and execute permission check_etc = test_cmd.format("r", "/etc/pihole", piholeuser) actual_rc = host.run(check_etc).rc diff --git a/test/tox.alpine_3_21.ini b/test/tox.alpine_3_21.ini new file mode 100644 index 00000000..b0465f6c --- /dev/null +++ b/test/tox.alpine_3_21.ini @@ -0,0 +1,10 @@ +[tox] +envlist = py3 + +[testenv:py3] +allowlist_externals = docker +deps = -rrequirements.txt +setenv = + COLUMNS=120 +commands = docker buildx build --load --progress plain -f _alpine_3_21.Dockerfile -t pytest_pihole:test_container ../ + pytest {posargs:-vv -n auto} ./test_any_automated_install.py ./test_any_utils.py diff --git a/test/tox.alpine_3_22.ini b/test/tox.alpine_3_22.ini new file mode 100644 index 00000000..38f66c4f --- /dev/null +++ b/test/tox.alpine_3_22.ini @@ -0,0 +1,10 @@ +[tox] +envlist = py3 + +[testenv:py3] +allowlist_externals = docker +deps = -rrequirements.txt +setenv = + COLUMNS=120 +commands = docker buildx build --load --progress plain -f _alpine_3_22.Dockerfile -t pytest_pihole:test_container ../ + pytest {posargs:-vv -n auto} ./test_any_automated_install.py ./test_any_utils.py From 4bf67a3c7923baf112a030a8ae1097ef86c117c5 Mon Sep 17 00:00:00 2001 From: "Michael Ziminsky (Z)" Date: Thu, 26 Jun 2025 22:53:55 -0700 Subject: [PATCH 3/6] Alpine: Add some additional dependencies and minor script fixes Signed-off-by: Michael Ziminsky (Z) --- advanced/Scripts/piholeDebug.sh | 2 +- advanced/Scripts/piholeLogFlush.sh | 2 +- automated install/basic-install.sh | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/advanced/Scripts/piholeDebug.sh b/advanced/Scripts/piholeDebug.sh index 2b903e50..a478b845 100755 --- a/advanced/Scripts/piholeDebug.sh +++ b/advanced/Scripts/piholeDebug.sh @@ -778,7 +778,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" diff --git a/advanced/Scripts/piholeLogFlush.sh b/advanced/Scripts/piholeLogFlush.sh index ca70f31b..ac28aed9 100755 --- a/advanced/Scripts/piholeLogFlush.sh +++ b/advanced/Scripts/piholeLogFlush.sh @@ -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 - diff --git a/automated install/basic-install.sh b/automated install/basic-install.sh index d5720df6..601183e7 100755 --- a/automated install/basic-install.sh +++ b/automated install/basic-install.sh @@ -167,12 +167,14 @@ PIHOLE_META_DEPS_APK=( dialog git grep - iproute2-ss + iproute2-minimal # piholeARPTable.sh + iproute2-ss # piholeDebug.sh jq libcap logrotate + lscpu # piholeDebug.sh + lshw # piholeDebug.sh ncurses - nmap-ncat procps-ng psmisc shadow From 44f95a4f576f40f21a80d91534af1239caec4e8a Mon Sep 17 00:00:00 2001 From: "Michael Ziminsky (Z)" Date: Wed, 9 Jul 2025 15:28:51 -0700 Subject: [PATCH 4/6] Alpine: Ensure community repo is enabled and handle dependency install errors Signed-off-by: Michael Ziminsky (Z) --- automated install/basic-install.sh | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/automated install/basic-install.sh b/automated install/basic-install.sh index 601183e7..aaf10cd7 100755 --- a/automated install/basic-install.sh +++ b/automated install/basic-install.sh @@ -1453,8 +1453,25 @@ install_dependent_packages() { fi # Install Alpine packages elif is_command apk; then - ${PKG_INSTALL} -q -t "pihole-meta=${PIHOLE_META_VERSION_APK}" "${PIHOLE_META_DEPS_APK[@]}" &> /dev/null && + local repo_str="Ensuring alpine 'community' repo is enabled." + printf "%b %b %s" "${OVER}" "${INFO}" "${repo_str}" + + local pattern='^\s*#(.*/community/?)\s*$' + sed -Ei "s:${pattern}:\1:" /etc/apk/repositories + if grep -Eq "${pattern}" /etc/apk/repositories; then + # Repo still commented out = Failure + printf "%b %b %s\\n" "${OVER}" "${CROSS}" "${repo_str}" + else + printf "%b %b %s\\n" "${OVER}" "${TICK}" "${repo_str}" + fi + printf " %b %s..." "${INFO}" "${str}" + if { ${PKG_INSTALL} -q -t "pihole-meta=${PIHOLE_META_VERSION_APK}" "${PIHOLE_META_DEPS_APK[@]}" &>/dev/null; }; then printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}" + else + printf "%b %b %s\\n" "${OVER}" "${CROSS}" "${str}" + printf " %b Error: Unable to install Pi-hole dependency package.\\n" "${COL_RED}" + return 1 + fi else # we cannot install the dependency package printf " %b No supported package manager found\\n" "${CROSS}" From 3908be911c30ceb8d728f332d92009ce2535b84d Mon Sep 17 00:00:00 2001 From: "Michael Ziminsky (Z)" Date: Mon, 14 Jul 2025 09:29:51 -0700 Subject: [PATCH 5/6] Alpine: Switch cron service from busybox to cronie during install Signed-off-by: Michael Ziminsky (Z) --- automated install/basic-install.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/automated install/basic-install.sh b/automated install/basic-install.sh index aaf10cd7..5daf2622 100755 --- a/automated install/basic-install.sh +++ b/automated install/basic-install.sh @@ -1496,6 +1496,15 @@ installCron() { # Randomize update checker time sed -i "s/59 17/$((1 + RANDOM % 58)) $((12 + RANDOM % 8))/" /etc/cron.d/pihole printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}" + + # Switch off of busybox cron on alpine + if is_command openrc; then + printf " %b Switching from busybox crond to cronie...\\n" "${INFO}" + stop_service crond + disable_service crond + enable_service cronie + restart_service cronie + fi } # Gravity is a very important script as it aggregates all of the domains into a single HOSTS formatted list, From f50a4c1c89fabbd87cf5922e830188e5f166d20c Mon Sep 17 00:00:00 2001 From: "Michael Ziminsky (Z)" Date: Wed, 30 Jul 2025 12:32:39 -0700 Subject: [PATCH 6/6] Don't use hard-coded path for pihole user `nologin` shell Signed-off-by: Michael Ziminsky (Z) --- automated install/basic-install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/automated install/basic-install.sh b/automated install/basic-install.sh index 5daf2622..d2871ec8 100755 --- a/automated install/basic-install.sh +++ b/automated install/basic-install.sh @@ -1554,7 +1554,7 @@ create_pihole_user() { # then create and add her to the pihole group local str="Creating user 'pihole'" printf "%b %b %s..." "${OVER}" "${INFO}" "${str}" - if useradd -r --no-user-group -g pihole -s /usr/sbin/nologin pihole; then + if useradd -r --no-user-group -g pihole -s "$(command -v nologin)" pihole; then printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}" else printf "%b %b %s\\n" "${OVER}" "${CROSS}" "${str}" @@ -1569,7 +1569,7 @@ create_pihole_user() { # create and add pihole user to the pihole group local str="Creating user 'pihole'" printf "%b %b %s..." "${OVER}" "${INFO}" "${str}" - if useradd -r --no-user-group -g pihole -s /usr/sbin/nologin pihole; then + if useradd -r --no-user-group -g pihole -s "$(command -v nologin)" pihole; then printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}" else printf "%b %b %s\\n" "${OVER}" "${CROSS}" "${str}"