diff --git a/nixos/modules/services/networking/pihole-ftl-setup-script.nix b/nixos/modules/services/networking/pihole-ftl-setup-script.nix index e5d0644c2f82..96c0c224aeb1 100644 --- a/nixos/modules/services/networking/pihole-ftl-setup-script.nix +++ b/nixos/modules/services/networking/pihole-ftl-setup-script.nix @@ -6,7 +6,7 @@ }: let - pihole = pkgs.pihole; + pihole = cfg.piholePackage; makePayload = list: builtins.toJSON { @@ -29,8 +29,8 @@ in ${lib.getExe' pkgs.procps "kill"} -s SIGRTMIN $(systemctl show --property MainPID --value ${config.systemd.services.pihole-ftl.name}) fi - source ${pihole}/usr/share/pihole/advanced/Scripts/api.sh - source ${pihole}/usr/share/pihole/advanced/Scripts/utils.sh + source ${pihole}/share/pihole/advanced/Scripts/api.sh + source ${pihole}/share/pihole/advanced/Scripts/utils.sh any_failed=0 diff --git a/nixos/modules/services/networking/pihole-ftl.nix b/nixos/modules/services/networking/pihole-ftl.nix index f170da405e70..e229d818cb8c 100644 --- a/nixos/modules/services/networking/pihole-ftl.nix +++ b/nixos/modules/services/networking/pihole-ftl.nix @@ -198,6 +198,19 @@ in ''; }; }; + + webserverEnabled = mkOption { + type = types.bool; + default = ( + (hasAttrByPath [ "webserver" "port" ] cfg.settings) + && !builtins.elem cfg.settings.webserver.port [ + "" + null + ] + ); + internal = true; + description = "Whether the webserver is enabled."; + }; }; config = mkIf cfg.enable { @@ -208,15 +221,7 @@ in } { - assertion = - builtins.length cfg.lists == 0 - || ( - (hasAttrByPath [ "webserver" "port" ] cfg.settings) - && !builtins.elem cfg.settings.webserver.port [ - "" - null - ] - ); + assertion = builtins.length cfg.lists == 0 || cfg.webserverEnabled; message = '' The Pi-hole webserver must be enabled for lists set in services.pihole-ftl.lists to be automatically loaded on startup via the web API. services.pihole-ftl.settings.port must be defined, e.g. by enabling services.pihole-web.enable and defining services.pihole-web.port. @@ -348,6 +353,8 @@ in pihole-ftl-setup = { description = "Pi-hole FTL setup"; + enable = builtins.length cfg.lists > 0; + # Wait for network so lists can be downloaded after = [ "network-online.target" ]; requires = [ "network-online.target" ]; diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 398fa4157388..cf347fc92061 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1193,6 +1193,7 @@ in pict-rs = runTest ./pict-rs.nix; pingvin-share = runTest ./pingvin-share.nix; pinnwand = runTest ./pinnwand.nix; + pihole-ftl = import ./pihole-ftl { inherit runTest; }; plantuml-server = runTest ./plantuml-server.nix; plasma6 = runTest ./plasma6.nix; plausible = runTest ./plausible.nix; diff --git a/nixos/tests/pihole-ftl/basic.nix b/nixos/tests/pihole-ftl/basic.nix new file mode 100644 index 000000000000..2d1a867b0938 --- /dev/null +++ b/nixos/tests/pihole-ftl/basic.nix @@ -0,0 +1,47 @@ +# A basic test with no webserver, no API, just checking DNS functionality +{ + lib, + pkgs, + ... +}: + +rec { + name = "pihole-ftl-basic"; + meta.maintainers = with lib.maintainers; [ averyvigolo ]; + + nodes.machine = + { pkgs, lib, ... }: + { + services.pihole-ftl = { + enable = true; + openFirewallDNS = true; + }; + environment.systemPackages = with pkgs; [ dig ]; + }; + + nodes.client = + { pkgs, lib, ... }: + { + environment.systemPackages = with pkgs; [ dig ]; + }; + + testScript = + { nodes, ... }: + '' + machine.wait_for_unit("pihole-ftl.service") + machine.wait_for_open_port(53) + client.wait_for_unit("network.target") + + with subtest("the pi-hole machine resolves properly"): + ret, out = machine.execute("dig @localhost +short pi.hole") + assert ret == 0, "pi.hole should resolve on the local machine" + assert out.rstrip() == "127.0.0.1", "pi.hole should resolve to localhost on the local machine" + + machine_address = "${(builtins.head nodes.machine.networking.interfaces.eth1.ipv4.addresses).address}" + + with subtest("a client machine resolves properly"): + ret, out = client.execute(f"dig @{machine_address} +short pi.hole") + assert ret == 0, "pi.hole should resolve on a client machine" + assert out.rstrip() == machine_address, "pi.hole should resolve to the machine's address" + ''; +} diff --git a/nixos/tests/pihole-ftl/default.nix b/nixos/tests/pihole-ftl/default.nix new file mode 100644 index 000000000000..71c971174dca --- /dev/null +++ b/nixos/tests/pihole-ftl/default.nix @@ -0,0 +1,5 @@ +{ runTest }: + +{ + basic = runTest ./basic.nix; +} diff --git a/pkgs/by-name/pi/pihole-ftl/disable-redirect-root.patch b/pkgs/by-name/pi/pihole-ftl/disable-redirect-root.patch new file mode 100644 index 000000000000..e2e1b96b8bdc --- /dev/null +++ b/pkgs/by-name/pi/pihole-ftl/disable-redirect-root.patch @@ -0,0 +1,45 @@ +From 14311f18ae9427a0baa1e0cb67198580d9d2dc69 Mon Sep 17 00:00:00 2001 +From: averyvigolo +Date: Sun, 24 Aug 2025 18:35:37 +0100 +Subject: [PATCH] Only use redirect_root_handler if webhome is set (fixes + #2518) + +This fixes an infinite redirect on the home page when authentication is enabled. +The redirects are caused by XHR calls to the API, which receive a 401 response, and +the error handlers simply reload the page. + +If webhome is not set, the default request handler should be used to properly +handle authentication. So, conditionally enable redirect_root_handler, if +webhome is not empty or `/`. This fixes the problem, as there's an immediate +redirect to /login, before any XHR calls. + +Remove the initial fix in https://github.com/pi-hole/FTL/pull/2521, as it is no +longer necessary. That fix involved checking in redirect_root_handler, if the +redirect destination is the same as the request URI. + +Signed-off-by: averyvigolo +--- + src/webserver/webserver.c | 15 ++++++++------- + 1 file changed, 8 insertions(+), 7 deletions(-) + +diff --git a/src/webserver/webserver.c b/src/webserver/webserver.c +index e0999d230..c3e32a35c 100644 +--- a/src/webserver/webserver.c ++++ b/src/webserver/webserver.c +@@ -720,7 +714,14 @@ void http_init(void) + // prefix should be stripped away by the reverse proxy + mg_set_request_handler(ctx, "/api", api_handler, NULL); + +- mg_set_request_handler(ctx, "/$", redirect_root_handler, NULL); ++ if(strcmp(prefix_webhome, "/") == 0 || strlen(prefix_webhome) == 0) ++ { ++ log_debug(DEBUG_API, "Not redirecting root since webhome is '%s'", ++ prefix_webhome); ++ } else { ++ // Redirect requests to / to the webhome path. ++ mg_set_request_handler(ctx, "/$", redirect_root_handler, NULL); ++ } + + if(strcmp(config.webserver.paths.webhome.v.s, "/") == 0 && + config.dns.blocking.mode.v.blocking_mode == MODE_IP) + diff --git a/pkgs/by-name/pi/pihole-ftl/package.nix b/pkgs/by-name/pi/pihole-ftl/package.nix index 855e3d01087e..6999dc989e1b 100644 --- a/pkgs/by-name/pi/pihole-ftl/package.nix +++ b/pkgs/by-name/pi/pihole-ftl/package.nix @@ -26,6 +26,11 @@ stdenv.mkDerivation (finalAttrs: { hash = "sha256-d1kpkBKuc30oIT1dRac8gkzh36Yyg80cizNRbyZ4424="; }; + patches = [ + # https://github.com/pi-hole/FTL/pull/2610: Fix authentication redirect when webhome is / + ./disable-redirect-root.patch + ]; + nativeBuildInputs = [ cmake xxd diff --git a/pkgs/by-name/pi/pihole/0001-Remove-sudo.patch b/pkgs/by-name/pi/pihole/0001-Remove-sudo.patch index f037d2953bea..f10afba82490 100644 --- a/pkgs/by-name/pi/pihole/0001-Remove-sudo.patch +++ b/pkgs/by-name/pi/pihole/0001-Remove-sudo.patch @@ -1,7 +1,7 @@ -From a2b3aa45d6e073272608506b1d27e4f43f2b0032 Mon Sep 17 00:00:00 2001 -From: williamvds -Date: Sun, 6 Apr 2025 23:00:41 +0100 -Subject: [PATCH 1/3] Remove sudo +From 833856bd77dc6ea77557ab0659c1459bee393653 Mon Sep 17 00:00:00 2001 +From: averyv +Date: Sat, 6 Sep 2025 17:51:48 +0100 +Subject: [PATCH 1/2] Remove sudo Rely on polkit and sensible permissions --- @@ -9,16 +9,16 @@ Rely on polkit and sensible permissions 1 file changed, 8 deletions(-) diff --git a/pihole b/pihole -index 1d5093c..6afc48a 100755 +index 7b5e3eb..b00617e 100755 --- a/pihole +++ b/pihole @@ -570,14 +570,6 @@ if [[ -z ${USER} ]]; then USER=$(whoami) fi --# Check if the current user is neither root nor pihole and if the command +-# Check if the current user is not root and if the command -# requires root. If so, exit with an error message. --if [[ $EUID -ne 0 && ${USER} != "pihole" && need_root -eq 1 ]];then +-if [[ $EUID -ne 0 && need_root -eq 1 ]];then - echo -e " ${CROSS} The Pi-hole command requires root privileges, try:" - echo -e " ${COL_GREEN}sudo pihole $*${COL_NC}" - exit 1 @@ -28,5 +28,5 @@ index 1d5093c..6afc48a 100755 case "${1}" in "allow" | "allowlist" ) listFunc "$@";; -- -2.48.1 +2.50.1 diff --git a/pkgs/by-name/pi/pihole/0002-Remove-unsupported-commands.patch b/pkgs/by-name/pi/pihole/0002-Remove-unsupported-commands.patch index 541d9b487764..d153afd6a8d8 100644 --- a/pkgs/by-name/pi/pihole/0002-Remove-unsupported-commands.patch +++ b/pkgs/by-name/pi/pihole/0002-Remove-unsupported-commands.patch @@ -1,7 +1,7 @@ -From ab0650484cdd89afb5b60a0a046509ec5ae14375 Mon Sep 17 00:00:00 2001 -From: williamvds -Date: Sun, 6 Apr 2025 23:01:30 +0100 -Subject: [PATCH 2/3] Remove unsupported commands +From d72475d713a8eecd1476bc5bb4acfb0448d5abad Mon Sep 17 00:00:00 2001 +From: averyv +Date: Sat, 6 Sep 2025 18:04:11 +0100 +Subject: [PATCH 2/2] Remove unsupported commands Remove some unsupported maintenance commands, particularly the ones which reinstall, update, and uninstall pihole. This is managed by NixOS, after all. @@ -10,7 +10,7 @@ reinstall, update, and uninstall pihole. This is managed by NixOS, after all. 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pihole b/pihole -index 6afc48a..cce7c97 100755 +index b00617e..a3c2e20 100755 --- a/pihole +++ b/pihole @@ -92,6 +92,7 @@ debugFunc() { @@ -63,5 +63,5 @@ index 6afc48a..cce7c97 100755 helpFunc() { -- -2.48.1 +2.50.1 diff --git a/pkgs/by-name/pi/pihole/0003-Fix-redefinition-of-readonly-variable-utilsfile.patch b/pkgs/by-name/pi/pihole/0003-Fix-redefinition-of-readonly-variable-utilsfile.patch deleted file mode 100644 index 62c67c026ded..000000000000 --- a/pkgs/by-name/pi/pihole/0003-Fix-redefinition-of-readonly-variable-utilsfile.patch +++ /dev/null @@ -1,52 +0,0 @@ -From cca2f6437e3ba09019b8fcb1986b4558d7c6db4e Mon Sep 17 00:00:00 2001 -From: williamvds -Date: Sat, 31 May 2025 13:43:42 +0100 -Subject: [PATCH 3/3] Fix redefinition of readonly variable utilsfile - ---- - advanced/Scripts/api.sh | 2 +- - pihole | 10 +++------- - 2 files changed, 4 insertions(+), 8 deletions(-) - -diff --git a/advanced/Scripts/api.sh b/advanced/Scripts/api.sh -index 613a8d8..8720043 100755 ---- a/advanced/Scripts/api.sh -+++ b/advanced/Scripts/api.sh -@@ -19,7 +19,7 @@ - - TestAPIAvailability() { - -- local chaos_api_list authResponse authStatus authData apiAvailable DNSport -+ local chaos_api_list authResponse authStatus authData apiAvailable DNSport utilsfile - - # as we are running locally, we can get the port value from FTL directly - readonly utilsfile="${PI_HOLE_SCRIPT_DIR}/utils.sh" -diff --git a/pihole b/pihole -index cce7c97..d63d064 100755 ---- a/pihole -+++ b/pihole -@@ -16,18 +16,14 @@ readonly PI_HOLE_SCRIPT_DIR="/opt/pihole" - # error due to modifying a readonly variable. - PI_HOLE_BIN_DIR="/usr/local/bin" - --readonly colfile="${PI_HOLE_SCRIPT_DIR}/COL_TABLE" - # shellcheck source=./advanced/Scripts/COL_TABLE --source "${colfile}" -+source "${PI_HOLE_SCRIPT_DIR}/COL_TABLE" - --readonly utilsfile="${PI_HOLE_SCRIPT_DIR}/utils.sh" - # shellcheck source=./advanced/Scripts/utils.sh --source "${utilsfile}" -+source "${PI_HOLE_SCRIPT_DIR}/utils.sh" - --# Source api functions --readonly apifile="${PI_HOLE_SCRIPT_DIR}/api.sh" - # shellcheck source=./advanced/Scripts/api.sh --source "${apifile}" -+source "${PI_HOLE_SCRIPT_DIR}/api.sh" - - versionsfile="/etc/pihole/versions" - if [ -f "${versionsfile}" ]; then --- -2.48.1 - diff --git a/pkgs/by-name/pi/pihole/package.nix b/pkgs/by-name/pi/pihole/package.nix index 152fc208643b..5a816b3623f9 100644 --- a/pkgs/by-name/pi/pihole/package.nix +++ b/pkgs/by-name/pi/pihole/package.nix @@ -31,13 +31,13 @@ (resholve.mkDerivation rec { pname = "pihole"; - version = "6.1"; + version = "6.1.4"; src = fetchFromGitHub { owner = "pi-hole"; repo = "pi-hole"; tag = "v${version}"; - hash = "sha256-aEnv8Lhb5vf0yDyuriVTaUY1wcdVmTdqoK+KDHvT/Lw="; + hash = "sha256-2B2GUJKt4jHEjQLBx96FRuHpnLCTzE4UPDaeQvnDONc="; }; patches = [ @@ -45,8 +45,6 @@ ./0001-Remove-sudo.patch # Disable unsupported subcommands, particularly those for imperatively installing/upgrading Pi-hole ./0002-Remove-unsupported-commands.patch - # Fix a readonly variable error caused by defining a shadowing local variable - ./0003-Fix-redefinition-of-readonly-variable-utilsfile.patch ]; nativeBuildInputs = [ @@ -57,7 +55,7 @@ installPhase = '' runHook preInstall - readonly scriptsDir=$out/usr/share/pihole + readonly scriptsDir=$out/share/pihole install -Dm 555 -t $out/bin pihole install -Dm 555 -t $scriptsDir/advanced/Scripts gravity.sh @@ -74,12 +72,12 @@ solutions.default = let out = builtins.placeholder "out"; - scriptsDir = "${out}/usr/share/pihole/advanced/Scripts"; + scriptsDir = "${out}/share/pihole/advanced/Scripts"; in { scripts = let - relativeScripts = "usr/share/pihole/advanced/Scripts"; + relativeScripts = "share/pihole/advanced/Scripts"; in [ "bin/pihole" @@ -101,7 +99,7 @@ inputs = [ # TODO: see if these inputs can help resholving "bin" - "usr/share/pihole/advanced/Scripts" + "share/pihole/advanced/Scripts" bash coreutils @@ -150,16 +148,16 @@ }; fix = { "$PI_HOLE_BIN_DIR" = [ "${out}/bin" ]; - "$PI_HOLE_FILES_DIR" = [ "${out}/usr/share/pihole" ]; + "$PI_HOLE_FILES_DIR" = [ "${out}/share/pihole" ]; "$PI_HOLE_INSTALL_DIR" = [ scriptsDir ]; - "$PI_HOLE_LOCAL_REPO" = [ "${out}/usr/share/pihole" ]; + "$PI_HOLE_LOCAL_REPO" = [ "${out}/share/pihole" ]; "$PI_HOLE_SCRIPT_DIR" = [ scriptsDir ]; "$colfile" = [ "${scriptsDir}/COL_TABLE" ]; "$coltable" = [ "${scriptsDir}/COL_TABLE" ]; "$PIHOLE_COLTABLE_FILE" = [ "${scriptsDir}/COL_TABLE" ]; "$utilsfile" = [ "${scriptsDir}/utils.sh" ]; "$apifile" = [ "${scriptsDir}/api.sh" ]; - "$piholeGitDir" = [ "${out}/usr/share/pihole" ]; + "$piholeGitDir" = [ "${out}/share/pihole" ]; "$PIHOLE_COMMAND" = [ "pihole" ]; }; keep = { @@ -168,7 +166,7 @@ "$setupVars" # Global config file "$PIHOLE_SETUP_VARS_FILE" "$versionsfile" # configuration file, doesn't exist on NixOS - "${out}/usr/share/pihole/automated install/basic-install.sh" + "${out}/share/pihole/automated install/basic-install.sh" "${scriptsDir}/COL_TABLE" "${scriptsDir}/database_migration/gravity-db.sh" "${scriptsDir}/gravity.sh" @@ -213,8 +211,8 @@ # both quoted and escaped. Resholve apparently requires matching the # literal path, so we need to provide a version with and without the # backslash. - "'${out}/usr/share/pihole/automated\\ install/basic-install.sh'" = true; - "'${out}/usr/share/pihole/automated install/basic-install.sh'" = true; + "'${out}/share/pihole/automated\\ install/basic-install.sh'" = true; + "'${out}/share/pihole/automated install/basic-install.sh'" = true; "/etc/.pihole" = true; # Patched with an override "/etc/os-release" = true; @@ -232,6 +230,8 @@ meta = { description = "Black hole for Internet advertisements"; + homepage = "https://pi-hole.net"; + changelog = "https://github.com/pi-hole/pi-hole/releases/tag/v${version}"; license = lib.licenses.eupl12; maintainers = with lib.maintainers; [ averyvigolo ]; platforms = lib.platforms.linux; @@ -239,13 +239,13 @@ }; passthru = { - stateDir = stateDir; + inherit stateDir; }; }).overrideAttrs (old: { # Resholve can't fix the hardcoded absolute paths, so substitute them before resholving preFixup = '' - scriptsDir=$out/usr/share/pihole + scriptsDir=$out/share/pihole substituteInPlace $out/bin/pihole $scriptsDir/advanced/Scripts/*.sh \ --replace-quiet /etc/.pihole $scriptsDir \