mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-06-09 19:13:26 +03:00
pihole: init at various (#361571)
Adds pihole-ftl.service and pihole-ftl-log-deleter.service. Authored-By: williamvds <william@williamvds.me>
This commit is contained in:
commit
8922d4f099
17 changed files with 2596 additions and 1 deletions
|
@ -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
|
||||
|
@ -1629,6 +1630,7 @@
|
|||
./services/web-apps/photoprism.nix
|
||||
./services/web-apps/phylactery.nix
|
||||
./services/web-apps/pict-rs.nix
|
||||
./services/web-apps/pihole-web.nix
|
||||
./services/web-apps/pingvin-share.nix
|
||||
./services/web-apps/pixelfed.nix
|
||||
./services/web-apps/plantuml-server.nix
|
||||
|
|
|
@ -115,6 +115,12 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
configFile = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
default = dnsmasqConf;
|
||||
internal = true;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
@ -172,7 +178,7 @@ in
|
|||
serviceConfig = {
|
||||
Type = "dbus";
|
||||
BusName = "uk.org.thekelleys.dnsmasq";
|
||||
ExecStart = "${dnsmasq}/bin/dnsmasq -k --enable-dbus --user=dnsmasq -C ${dnsmasqConf}";
|
||||
ExecStart = "${dnsmasq}/bin/dnsmasq -k --enable-dbus --user=dnsmasq -C ${cfg.configFile}";
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||
PrivateTmp = true;
|
||||
ProtectSystem = true;
|
||||
|
|
|
@ -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
|
||||
''
|
128
nixos/modules/services/networking/pihole-ftl.md
Normal file
128
nixos/modules/services/networking/pihole-ftl.md
Normal file
|
@ -0,0 +1,128 @@
|
|||
# pihole-FTL {#module-services-networking-pihole-ftl}
|
||||
|
||||
*Upstream documentation*: <https://docs.pi-hole.net/ftldns/>
|
||||
|
||||
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*: <https://docs.pi-hole.net/main/pihole-command>
|
||||
|
||||
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)
|
483
nixos/modules/services/networking/pihole-ftl.nix
Normal file
483
nixos/modules/services/networking/pihole-ftl.nix
Normal file
|
@ -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 ];
|
||||
};
|
||||
}
|
19
nixos/modules/services/web-apps/pihole-web.md
Normal file
19
nixos/modules/services/web-apps/pihole-web.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Pi-hole Web Dashboard {#module-services-web-apps-pihole-web}
|
||||
|
||||
The Pi-hole suite provides a web GUI for controlling and monitoring
|
||||
[pihole-FTL](index.html#module-services-networking-pihole-ftl).
|
||||
|
||||
## Configuration {#module-services-web-apps-pihole-web-configuration}
|
||||
|
||||
Example configuration:
|
||||
|
||||
```nix
|
||||
{
|
||||
services.pihole-web = {
|
||||
enable = true;
|
||||
ports = [ 80 ];
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
The dashboard can be configured using [{option}`services.pihole-ftl.settings`](options.html#opt-services.pihole-ftl.settings), in particular the `webserver` subsection.
|
104
nixos/modules/services/web-apps/pihole-web.nix
Normal file
104
nixos/modules/services/web-apps/pihole-web.nix
Normal file
|
@ -0,0 +1,104 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.pihole-web;
|
||||
in
|
||||
{
|
||||
options.services.pihole-web = {
|
||||
enable = lib.mkEnableOption "Pi-hole dashboard";
|
||||
|
||||
package = lib.mkPackageOption pkgs "pihole-web" { };
|
||||
|
||||
hostName = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Domain name for the website.";
|
||||
default = "pi.hole";
|
||||
};
|
||||
|
||||
ports =
|
||||
let
|
||||
portType = lib.types.submodule {
|
||||
options = {
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
description = "Port to bind";
|
||||
};
|
||||
optional = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Skip the port if it cannot be bound";
|
||||
};
|
||||
redirectSSL = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Redirect from this port to the first configured SSL port";
|
||||
};
|
||||
ssl = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Serve SSL on the port";
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
lib.mkOption {
|
||||
type = lib.types.listOf (
|
||||
lib.types.oneOf [
|
||||
lib.types.port
|
||||
lib.types.str
|
||||
portType
|
||||
]
|
||||
);
|
||||
description = ''
|
||||
Port(s) for the webserver to serve on.
|
||||
|
||||
If provided as a string, optionally append suffixes to control behaviour:
|
||||
|
||||
- `o`: to make the port is optional - failure to bind will not be an error.
|
||||
- `s`: for the port to be used for SSL.
|
||||
- `r`: for a non-SSL port to redirect to the first available SSL port.
|
||||
'';
|
||||
example = [
|
||||
"80r"
|
||||
"443s"
|
||||
];
|
||||
apply =
|
||||
values:
|
||||
let
|
||||
convert =
|
||||
value:
|
||||
if (builtins.typeOf) value == "int" then
|
||||
toString value
|
||||
else if builtins.typeOf value == "set" then
|
||||
lib.strings.concatStrings [
|
||||
(toString value.port)
|
||||
(lib.optionalString value.optional "o")
|
||||
(lib.optionalString value.redirectSSL "r")
|
||||
(lib.optionalString value.ssl "s")
|
||||
]
|
||||
else
|
||||
value;
|
||||
in
|
||||
lib.strings.concatStringsSep "," (map convert values);
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
services.pihole-ftl.settings.webserver = {
|
||||
domain = cfg.hostName;
|
||||
port = cfg.ports;
|
||||
paths.webroot = "${cfg.package}/share/";
|
||||
paths.webhome = "/";
|
||||
};
|
||||
};
|
||||
|
||||
meta = {
|
||||
doc = ./pihole-web.md;
|
||||
maintainers = with lib.maintainers; [ williamvds ];
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue