diff --git a/nixos/doc/manual/redirects.json b/nixos/doc/manual/redirects.json
index 44ab6c6b860c..080756744e56 100644
--- a/nixos/doc/manual/redirects.json
+++ b/nixos/doc/manual/redirects.json
@@ -56,6 +56,12 @@
"module-services-opencloud-basic-usage": [
"index.html#module-services-opencloud-basic-usage"
],
+ "module-services-networking-pihole-ftl-configuration-inherit-dnsmasq": [
+ "index.html#module-services-networking-pihole-ftl-configuration-inherit-dnsmasq"
+ ],
+ "module-services-networking-pihole-ftl-configuration-multiple-interfaces": [
+ "index.html#module-services-networking-pihole-ftl-configuration-multiple-interfaces"
+ ],
"module-services-strfry": [
"index.html#module-services-strfry"
],
@@ -1448,6 +1454,15 @@
"module-services-input-methods-kime": [
"index.html#module-services-input-methods-kime"
],
+ "module-services-networking-pihole-ftl": [
+ "index.html#module-services-networking-pihole-ftl"
+ ],
+ "module-services-networking-pihole-ftl-administration": [
+ "index.html#module-services-networking-pihole-ftl-administration"
+ ],
+ "module-services-networking-pihole-ftl-configuration": [
+ "index.html#module-services-networking-pihole-ftl-configuration"
+ ],
"ch-profiles": [
"index.html#ch-profiles"
],
diff --git a/nixos/doc/manual/release-notes/rl-2511.section.md b/nixos/doc/manual/release-notes/rl-2511.section.md
index 07322ea56669..ce146d0cdedf 100644
--- a/nixos/doc/manual/release-notes/rl-2511.section.md
+++ b/nixos/doc/manual/release-notes/rl-2511.section.md
@@ -13,6 +13,8 @@
- [gtklock](https://github.com/jovanlanik/gtklock), a GTK-based lockscreen for Wayland. Available as [programs.gtklock](#opt-programs.gtklock.enable).
- [Chrysalis](https://github.com/keyboardio/Chrysalis), a graphical configurator for Kaleidoscope-powered keyboards. Available as [programs.chrysalis](#opt-programs.chrysalis.enable).
+- [Pi-hole](https://pi-hole.net/), a DNS sinkhole for advertisements based on Dnsmasq. Available as [services.pihole-ftl](#opt-services.pihole-ftl.enable), and [services.pihole-web](#opt-services.pihole-web.enable) for the web GUI and API.
+
- [FileBrowser](https://filebrowser.org/), a web application for managing and sharing files. Available as [services.filebrowser](#opt-services.filebrowser.enable).
- [LACT](https://github.com/ilya-zlobintsev/LACT), a GPU monitoring and configuration tool, can now be enabled through [services.lact.enable](#opt-services.lact.enable).
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 79f5c22f5b98..303781f6645e 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -1268,6 +1268,7 @@
./services/networking/pdnsd.nix
./services/networking/peroxide.nix
./services/networking/picosnitch.nix
+ ./services/networking/pihole-ftl.nix
./services/networking/pixiecore.nix
./services/networking/pleroma.nix
./services/networking/powerdns.nix
diff --git a/nixos/modules/services/networking/pihole-ftl-setup-script.nix b/nixos/modules/services/networking/pihole-ftl-setup-script.nix
new file mode 100644
index 000000000000..50236a9a49e2
--- /dev/null
+++ b/nixos/modules/services/networking/pihole-ftl-setup-script.nix
@@ -0,0 +1,82 @@
+{
+ cfg,
+ config,
+ lib,
+ pkgs,
+}:
+
+let
+ pihole = pkgs.pihole;
+ makePayload =
+ list:
+ builtins.toJSON {
+ inherit (list) type enabled;
+ address = list.url;
+ comment = list.description;
+ };
+ payloads = map makePayload cfg.lists;
+in
+''
+ # Can't use -u (unset) because api.sh uses API_URL before it is set
+ set -eo pipefail
+ pihole="${lib.getExe pihole}"
+ jq="${lib.getExe pkgs.jq}"
+
+ # If the database doesn't exist, it needs to be created with gravity.sh
+ if [ ! -f '${cfg.stateDirectory}'/gravity.db ]; then
+ $pihole -g
+ # Send SIGRTMIN to FTL, which makes it reload the database, opening the newly created one
+ ${pkgs.procps}/bin/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
+
+ any_failed=0
+
+ addList() {
+ local payload="$1"
+
+ echo "Adding list: $payload"
+ local result=$(PostFTLData "lists" "$payload")
+
+ local error="$($jq '.error' <<< "$result")"
+ if [[ "$error" != "null" ]]; then
+ echo "Error: $error"
+ any_failed=1
+ return
+ fi
+
+ id="$($jq '.lists.[].id?' <<< "$result")"
+ if [[ "$id" == "null" ]]; then
+ any_failed=1
+ error="$($jq '.processed.errors.[].error' <<< "$result")"
+ echo "Error: $error"
+ return
+ fi
+
+ echo "Added list ID $id: $result"
+ }
+
+ for i in 1 2 3; do
+ (TestAPIAvailability) && break
+ echo "Retrying API shortly..."
+ ${pkgs.coreutils}/bin/sleep .5s
+ done;
+
+ LoginAPI
+
+ ${builtins.concatStringsSep "\n" (
+ map (
+ payload:
+ lib.pipe payload [
+ lib.strings.escapeShellArg
+ (payload: "addList ${payload}")
+ ]
+ ) payloads
+ )}
+
+ # Run gravity.sh to load any new lists
+ $pihole -g
+ exit $any_failed
+''
diff --git a/nixos/modules/services/networking/pihole-ftl.md b/nixos/modules/services/networking/pihole-ftl.md
new file mode 100644
index 000000000000..4a1b1f986708
--- /dev/null
+++ b/nixos/modules/services/networking/pihole-ftl.md
@@ -0,0 +1,128 @@
+# pihole-FTL {#module-services-networking-pihole-ftl}
+
+*Upstream documentation*:
+
+pihole-FTL is a fork of [Dnsmasq](index.html#module-services-networking-dnsmasq),
+providing some additional features, including an API for analysis and
+statistics.
+
+Note that pihole-FTL and Dnsmasq cannot be enabled at
+the same time.
+
+## Configuration {#module-services-networking-pihole-ftl-configuration}
+
+pihole-FTL can be configured with [{option}`services.pihole-ftl.settings`](options.html#opt-services.pihole-ftl.settings), which controls the content of `pihole.toml`.
+
+The template pihole.toml is provided in `pihole-ftl.passthru.settingsTemplate`,
+which describes all settings.
+
+Example configuration:
+
+```nix
+{
+ services.pihole-ftl = {
+ enable = true;
+ openFirewallDHCP = true;
+ queryLogDeleter.enable = true;
+ lists = [
+ {
+ url = "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts";
+ # Alternatively, use the file from nixpkgs. Note its contents won't be
+ # automatically updated by Pi-hole, as it would with an online URL.
+ # url = "file://${pkgs.stevenblack-blocklist}/hosts";
+ description = "Steven Black's unified adlist";
+ }
+ ];
+ settings = {
+ dns = {
+ domainNeeded = true;
+ expandHosts = true;
+ interface = "br-lan";
+ listeningMode = "BIND";
+ upstreams = [ "127.0.0.1#5053" ];
+ };
+ dhcp = {
+ active = true;
+ router = "192.168.10.1";
+ start = "192.168.10.2";
+ end = "192.168.10.254";
+ leaseTime = "1d";
+ ipv6 = true;
+ multiDNS = true;
+ hosts = [
+ # Static address for the current host
+ "aa:bb:cc:dd:ee:ff,192.168.10.1,${config.networking.hostName},infinite"
+ ];
+ rapidCommit = true;
+ };
+ misc.dnsmasq_lines = [
+ # This DHCP server is the only one on the network
+ "dhcp-authoritative"
+ # Source: https://data.iana.org/root-anchors/root-anchors.xml
+ "trust-anchor=.,38696,8,2,683D2D0ACB8C9B712A1948B27F741219298D0A450D612C483AF444A4C0FB2B16"
+ ];
+ };
+ };
+}
+```
+
+### Inheriting configuration from Dnsmasq {#module-services-networking-pihole-ftl-configuration-inherit-dnsmasq}
+
+If [{option}`services.pihole-ftl.useDnsmasqConfig`](options.html#opt-services.pihole-ftl.useDnsmasqConfig) is enabled, the configuration [options of the Dnsmasq
+module](index.html#module-services-networking-dnsmasq) will be automatically
+used by pihole-FTL. Note that this may cause duplicate option errors
+depending on pihole-FTL settings.
+
+See the [Dnsmasq
+example](index.html#module-services-networking-dnsmasq-configuration-home) for
+an exemplar Dnsmasq configuration. Make sure to set
+[{option}`services.dnsmasq.enable`](options.html#opt-services.dnsmasq.enable) to false and
+[{option}`services.pihole-ftl.enable`](options.html#opt-services.pihole-ftl.enable) to true instead:
+
+```nix
+{
+ services.pihole-ftl = {
+ enable = true;
+ useDnsmasqConfig = true;
+ };
+}
+```
+
+### Serving on multiple interfaces {#module-services-networking-pihole-ftl-configuration-multiple-interfaces}
+
+Pi-hole's configuration only supports specifying a single interface. If you want
+to configure additional interfaces with different configuration, use
+`misc.dnsmasq_lines` to append extra Dnsmasq options.
+
+```nix
+{
+ services.pihole-ftl = {
+ settings.misc.dnsmasq_lines = [
+ # Specify the secondary interface
+ "interface=enp1s0"
+ # A different device is the router on this network, e.g. the one
+ # provided by your ISP
+ "dhcp-option=enp1s0,option:router,192.168.0.1"
+ # Specify the IPv4 ranges to allocate, with a 1-day lease time
+ "dhcp-range=enp1s0,192.168.0.10,192.168.0.253,1d"
+ # Enable IPv6
+ "dhcp-range=::f,::ff,constructor:enp1s0,ra-names,ra-stateless"
+ ];
+ };
+ };
+}
+```
+
+## Administration {#module-services-networking-pihole-ftl-administration}
+
+*pihole command documentation*:
+
+Enabling pihole-FTL provides the `pihole` command, which can be used to control
+the daemon and some configuration.
+
+Note that in NixOS the script has been patched to remove the reinstallation,
+update, and Dnsmasq configuration commands. In NixOS, Pi-hole's configuration is
+immutable and must be done with NixOS options.
+
+For more convenient administration and monitoring, see [Pi-hole
+Dashboard](#module-services-web-apps-pihole-web)
diff --git a/nixos/modules/services/networking/pihole-ftl.nix b/nixos/modules/services/networking/pihole-ftl.nix
new file mode 100644
index 000000000000..45cd2e5f927b
--- /dev/null
+++ b/nixos/modules/services/networking/pihole-ftl.nix
@@ -0,0 +1,483 @@
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}:
+
+with {
+ inherit (lib)
+ elemAt
+ getExe
+ hasAttrByPath
+ mkEnableOption
+ mkIf
+ mkOption
+ strings
+ types
+ ;
+};
+
+let
+ mkDefaults = lib.mapAttrsRecursive (n: v: lib.mkDefault v);
+
+ cfg = config.services.pihole-ftl;
+
+ piholeScript = pkgs.writeScriptBin "pihole" ''
+ sudo=exec
+ if [[ "$USER" != '${cfg.user}' ]]; then
+ sudo='exec /run/wrappers/bin/sudo -u ${cfg.user}'
+ fi
+ $sudo ${getExe cfg.piholePackage} "$@"
+ '';
+
+ settingsFormat = pkgs.formats.toml { };
+ settingsFile = settingsFormat.generate "pihole.toml" cfg.settings;
+in
+{
+ options.services.pihole-ftl = {
+ enable = mkEnableOption "Pi-hole FTL";
+
+ package = lib.mkPackageOption pkgs "pihole-ftl" { };
+ piholePackage = lib.mkPackageOption pkgs "pihole" { };
+
+ privacyLevel = mkOption {
+ type = types.numbers.between 0 3;
+ description = ''
+ Level of detail in generated statistics. 0 enables full statistics, 3
+ shows only anonymous statistics.
+
+ See [the documentation](https://docs.pi-hole.net/ftldns/privacylevels).
+
+ Also see services.dnsmasq.settings.log-queries to completely disable
+ query logging.
+ '';
+ default = 0;
+ example = "3";
+ };
+
+ openFirewallDHCP = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Open ports in the firewall for pihole-FTL's DHCP server.";
+ };
+
+ openFirewallWebserver = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Open ports in the firewall for pihole-FTL's webserver, as configured in `settings.webserver.port`.
+ '';
+ };
+
+ configDirectory = mkOption {
+ type = types.path;
+ default = "/etc/pihole";
+ internal = true;
+ readOnly = true;
+ description = ''
+ Path for pihole configuration.
+ pihole does not currently support any path other than /etc/pihole.
+ '';
+ };
+
+ stateDirectory = mkOption {
+ type = types.path;
+ default = "/var/lib/pihole";
+ description = ''
+ Path for pihole state files.
+ '';
+ };
+
+ logDirectory = mkOption {
+ type = types.path;
+ default = "/var/log/pihole";
+ description = "Path for Pi-hole log files";
+ };
+
+ settings = mkOption {
+ type = settingsFormat.type;
+ description = ''
+ Configuration options for pihole.toml.
+ See the upstream [documentation](https://docs.pi-hole.net/ftldns/configfile).
+ '';
+ };
+
+ useDnsmasqConfig = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Import options defined in [](#opt-services.dnsmasq.settings) via
+ misc.dnsmasq_lines in Pi-hole's config.
+ '';
+ };
+
+ pihole = mkOption {
+ type = types.package;
+ default = piholeScript;
+ internal = true;
+ description = "Pi-hole admin script";
+ };
+
+ lists =
+ let
+ adlistType = types.submodule {
+ options = {
+ url = mkOption {
+ type = types.str;
+ description = "URL of the domain list";
+ };
+ type = mkOption {
+ type = types.enum [
+ "allow"
+ "block"
+ ];
+ default = "block";
+ description = "Whether domains on this list should be explicitly allowed, or blocked";
+ };
+ enabled = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether this list is enabled";
+ };
+ description = mkOption {
+ type = types.str;
+ description = "Description of the list";
+ default = "";
+ };
+ };
+ };
+ in
+ mkOption {
+ type = with types; listOf adlistType;
+ description = "Deny (or allow) domain lists to use";
+ default = [ ];
+ example = [
+ {
+ url = "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts";
+ }
+ ];
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "pihole";
+ description = "User to run the service as.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "pihole";
+ description = "Group to run the service as.";
+ };
+
+ queryLogDeleter = {
+ enable = mkEnableOption ("Pi-hole FTL DNS query log deleter");
+
+ age = mkOption {
+ type = types.int;
+ default = 90;
+ description = ''
+ Delete DNS query logs older than this many days, if
+ [](#opt-services.pihole-ftl.queryLogDeleter.enable) is on.
+ '';
+ };
+
+ interval = mkOption {
+ type = types.str;
+ default = "weekly";
+ description = ''
+ How often the query log deleter is run. See systemd.time(7) for more
+ information about the format.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [
+ {
+ assertion = !config.services.dnsmasq.enable;
+ message = "pihole-ftl conflicts with dnsmasq. Please disable one of them.";
+ }
+
+ {
+ assertion =
+ builtins.length cfg.lists == 0
+ || (
+ (hasAttrByPath [ "webserver" "port" ] cfg.settings)
+ && !builtins.elem cfg.settings.webserver.port [
+ ""
+ null
+ ]
+ );
+ 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.
+ '';
+ }
+
+ {
+ assertion =
+ builtins.length cfg.lists == 0
+ || !(hasAttrByPath [ "webserver" "api" "cli_pw" ] cfg.settings)
+ || cfg.settings.webserver.api.cli_pw == true;
+ message = ''
+ services.pihole-ftl.settings.webserver.api.cli_pw must be true for lists set in services.pihole-ftl.lists to be automatically loaded on startup.
+ This enables an ephemeral password used by the pihole command.
+ '';
+ }
+ ];
+
+ services.pihole-ftl.settings = lib.mkMerge [
+ # Defaults
+ (mkDefaults {
+ misc.readOnly = true; # Prevent config changes via API or CLI by default
+ webserver.port = ""; # Disable the webserver by default
+ misc.privacyLevel = cfg.privacyLevel;
+ })
+
+ # Move state files to cfg.stateDirectory
+ {
+ # TODO: Pi-hole currently hardcodes dhcp-leasefile this in its
+ # generated dnsmasq.conf, and we can't override it
+ misc.dnsmasq_lines = [
+ # "dhcp-leasefile=${cfg.stateDirectory}/dhcp.leases"
+ # "hostsdir=${cfg.stateDirectory}/hosts"
+ ];
+
+ files = {
+ database = "${cfg.stateDirectory}/pihole-FTL.db";
+ gravity = "${cfg.stateDirectory}/gravity.db";
+ macvendor = "${cfg.stateDirectory}/gravity.db";
+ log.ftl = "${cfg.logDirectory}/FTL.log";
+ log.dnsmasq = "${cfg.logDirectory}/pihole.log";
+ log.webserver = "${cfg.logDirectory}/webserver.log";
+ };
+
+ webserver.tls = "${cfg.stateDirectory}/tls.pem";
+ }
+
+ (lib.optionalAttrs cfg.useDnsmasqConfig {
+ misc.dnsmasq_lines = lib.pipe config.services.dnsmasq.configFile [
+ builtins.readFile
+ (lib.strings.splitString "\n")
+ (builtins.filter (s: s != ""))
+ ];
+ })
+ ];
+
+ systemd.tmpfiles.rules = [
+ "d ${cfg.configDirectory} 0700 ${cfg.user} ${cfg.group} - -"
+ "d ${cfg.stateDirectory} 0700 ${cfg.user} ${cfg.group} - -"
+ "d ${cfg.logDirectory} 0700 ${cfg.user} ${cfg.group} - -"
+ ];
+
+ systemd.services = {
+ pihole-ftl =
+ let
+ setupService = config.systemd.services.pihole-ftl-setup.name;
+ in
+ {
+ description = "Pi-hole FTL";
+
+ after = [ "network.target" ];
+ before = [ setupService ];
+
+ wantedBy = [ "multi-user.target" ];
+ wants = [ setupService ];
+
+ environment = {
+ # Currently unused, but allows the service to be reloaded
+ # automatically when the config is changed.
+ PIHOLE_CONFIG = settingsFile;
+
+ # pihole is executed by the /actions/gravity API endpoint
+ PATH = lib.mkForce (
+ lib.makeBinPath [
+ cfg.piholePackage
+ ]
+ );
+ };
+
+ serviceConfig = {
+ Type = "simple";
+ User = cfg.user;
+ Group = cfg.group;
+ AmbientCapabilities = [
+ "CAP_NET_BIND_SERVICE"
+ "CAP_NET_RAW"
+ "CAP_NET_ADMIN"
+ "CAP_SYS_NICE"
+ "CAP_IPC_LOCK"
+ "CAP_CHOWN"
+ "CAP_SYS_TIME"
+ ];
+ ExecStart = "${getExe cfg.package} no-daemon";
+ Restart = "on-failure";
+ RestartSec = 1;
+ # Hardening
+ NoNewPrivileges = true;
+ PrivateTmp = true;
+ PrivateDevices = true;
+ DevicePolicy = "closed";
+ ProtectSystem = "strict";
+ ProtectHome = "read-only";
+ ProtectControlGroups = true;
+ ProtectKernelModules = true;
+ ProtectKernelTunables = true;
+ ReadWritePaths = [
+ cfg.configDirectory
+ cfg.stateDirectory
+ cfg.logDirectory
+ ];
+ RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
+ RestrictNamespaces = true;
+ RestrictRealtime = true;
+ RestrictSUIDSGID = true;
+ MemoryDenyWriteExecute = true;
+ LockPersonality = true;
+ };
+ };
+
+ pihole-ftl-setup = {
+ description = "Pi-hole FTL setup";
+ # Wait for network so lists can be downloaded
+ after = [ "network-online.target" ];
+ requires = [ "network-online.target" ];
+ serviceConfig = {
+ Type = "oneshot";
+ User = cfg.user;
+ Group = cfg.group;
+
+ # Hardening
+ NoNewPrivileges = true;
+ PrivateTmp = true;
+ PrivateDevices = true;
+ DevicePolicy = "closed";
+ ProtectSystem = "strict";
+ ProtectHome = "read-only";
+ ProtectControlGroups = true;
+ ProtectKernelModules = true;
+ ProtectKernelTunables = true;
+ ReadWritePaths = [
+ cfg.configDirectory
+ cfg.stateDirectory
+ cfg.logDirectory
+ ];
+ RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
+ RestrictNamespaces = true;
+ RestrictRealtime = true;
+ RestrictSUIDSGID = true;
+ MemoryDenyWriteExecute = true;
+ LockPersonality = true;
+ };
+ script = import ./pihole-ftl-setup-script.nix {
+ inherit
+ cfg
+ config
+ lib
+ pkgs
+ ;
+ };
+ };
+
+ pihole-ftl-log-deleter = mkIf cfg.queryLogDeleter.enable {
+ description = "Pi-hole FTL DNS query log deleter";
+ serviceConfig = {
+ Type = "oneshot";
+ User = cfg.user;
+ Group = cfg.group;
+ # Hardening
+ NoNewPrivileges = true;
+ PrivateTmp = true;
+ PrivateDevices = true;
+ DevicePolicy = "closed";
+ ProtectSystem = "strict";
+ ProtectHome = "read-only";
+ ProtectControlGroups = true;
+ ProtectKernelModules = true;
+ ProtectKernelTunables = true;
+ ReadWritePaths = [ cfg.stateDirectory ];
+ RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
+ RestrictNamespaces = true;
+ RestrictRealtime = true;
+ RestrictSUIDSGID = true;
+ MemoryDenyWriteExecute = true;
+ LockPersonality = true;
+ };
+ script =
+ let
+ days = toString cfg.queryLogDeleter.age;
+ database = "${cfg.stateDirectory}/pihole-FTL.db";
+ in
+ ''
+ set -euo pipefail
+
+ echo "Deleting query logs older than ${days} days"
+ ${getExe cfg.package} sqlite3 "${database}" "DELETE FROM query_storage WHERE timestamp <= CAST(strftime('%s', date('now', '-${days} day')) AS INT); select changes() from query_storage limit 1"
+ '';
+ };
+ };
+
+ systemd.timers.pihole-ftl-log-deleter = mkIf cfg.queryLogDeleter.enable {
+ description = "Pi-hole FTL DNS query log deleter";
+ before = [
+ config.systemd.services.pihole-ftl.name
+ config.systemd.services.pihole-ftl-setup.name
+ ];
+ wantedBy = [ "timers.target" ];
+ timerConfig = {
+ OnCalendar = cfg.queryLogDeleter.interval;
+ Unit = "pihole-ftl-log-deleter.service";
+ };
+ };
+
+ networking.firewall = lib.mkMerge [
+ (mkIf cfg.openFirewallDHCP {
+ allowedUDPPorts = [ 53 ];
+ allowedTCPPorts = [ 53 ];
+ })
+
+ (mkIf cfg.openFirewallWebserver {
+ allowedTCPPorts = lib.pipe cfg.settings.webserver.port [
+ (lib.splitString ",")
+ (map (
+ port:
+ lib.pipe port [
+ (builtins.split "[[:alpha:]]+")
+ builtins.head
+ lib.toInt
+ ]
+ ))
+ ];
+ })
+ ];
+
+ users.users.${cfg.user} = {
+ group = cfg.group;
+ isSystemUser = true;
+ };
+
+ users.groups.${cfg.group} = { };
+
+ environment.etc."pihole/pihole.toml" = {
+ source = settingsFile;
+ user = cfg.user;
+ group = cfg.group;
+ mode = "400";
+ };
+
+ environment.systemPackages = [ cfg.pihole ];
+
+ services.logrotate.settings.pihole-ftl = {
+ enable = true;
+ files = [ "${cfg.logDirectory}/FTL.log" ];
+ };
+ };
+
+ meta = {
+ doc = ./pihole-ftl.md;
+ maintainers = with lib.maintainers; [ williamvds ];
+ };
+}