106 lines
3.5 KiB
Nix
106 lines
3.5 KiB
Nix
# `services.nxdns`: the packaged unit, run in file mode against a configuration
|
|
# that the pinned binary accepted at build time.
|
|
#
|
|
# The unit is `deploy/systemd/nxdns.service` from the release tarball, installed
|
|
# by the package and picked up through `systemd.packages`. This module only adds
|
|
# a drop-in, so the hardening lives in that one file and a change to it reaches
|
|
# NixOS with the release that ships it.
|
|
{ self }:
|
|
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
let
|
|
cfg = config.services.nxdns;
|
|
|
|
# Nix treats the ZON as opaque data, so without this a configuration the
|
|
# pinned binary rejects evaluates, builds and deploys, and fails only when
|
|
# the service starts. `import` into a throwaway data directory is the offline
|
|
# validator: it shares the parser, the validator and the reconcile engine
|
|
# with `run`, and its delete gate is inert on an empty database. `check` is
|
|
# not usable here: it probes every upstream, and the build sandbox has no
|
|
# network.
|
|
validatedConfig =
|
|
pkgs.runCommand "nxdns-config.zon"
|
|
{
|
|
nativeBuildInputs = [ cfg.package ];
|
|
src = cfg.configFile;
|
|
}
|
|
''
|
|
mkdir -p "$TMPDIR/validate"
|
|
nxdns import --data-dir "$TMPDIR/validate" "$src"
|
|
cp "$src" "$out"
|
|
'';
|
|
in
|
|
{
|
|
options.services.nxdns = {
|
|
enable = lib.mkEnableOption "nxdns, a DNS sinkhole for a household network";
|
|
|
|
package = lib.mkOption {
|
|
type = lib.types.package;
|
|
default = self.packages.${pkgs.stdenv.hostPlatform.system}.default;
|
|
defaultText = lib.literalExpression "nxdns.packages.\${pkgs.stdenv.hostPlatform.system}.default";
|
|
description = "The nxdns release to run. The default is the release of the flake revision this module comes from.";
|
|
};
|
|
|
|
configFile = lib.mkOption {
|
|
type = lib.types.path;
|
|
example = lib.literalExpression "./nxdns-config.zon";
|
|
description = ''
|
|
The nxdns configuration, in ZON. nxdns runs in file mode: every start
|
|
reconciles the database onto this file, and the web interface refuses
|
|
configuration edits. The build fails when `nxdns import` from
|
|
`package` rejects the file.
|
|
'';
|
|
};
|
|
|
|
openFirewall = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = "Open port 53 over UDP and TCP. The web interface port and the DoH and DoT ports stay closed.";
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
environment.etc."nxdns/config.zon".source = validatedConfig;
|
|
|
|
users.users.nxdns = {
|
|
isSystemUser = true;
|
|
group = "nxdns";
|
|
description = "nxdns DNS sinkhole";
|
|
};
|
|
users.groups.nxdns = { };
|
|
|
|
systemd.packages = [ cfg.package ];
|
|
|
|
systemd.services.nxdns = {
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
# The unit file does not change when only the configuration does, so
|
|
# without this a configuration-only switch restarts nothing and the old
|
|
# configuration keeps serving with no error anywhere.
|
|
restartTriggers = [ validatedConfig ];
|
|
|
|
serviceConfig = {
|
|
# The empty entry clears the packaged ExecStart: a drop-in cannot
|
|
# replace it otherwise.
|
|
ExecStart = [
|
|
""
|
|
"${lib.getExe cfg.package} run --config=/etc/nxdns/config.zon"
|
|
];
|
|
# `environment.etc` creates /etc/nxdns and owns what is in it. A
|
|
# ConfigurationDirectory would make systemd adjust the same directory
|
|
# at every start.
|
|
ConfigurationDirectory = "";
|
|
};
|
|
};
|
|
|
|
networking.firewall = lib.mkIf cfg.openFirewall {
|
|
allowedUDPPorts = [ 53 ];
|
|
allowedTCPPorts = [ 53 ];
|
|
};
|
|
};
|
|
}
|