{ config, lib, utils, ... }: let inherit (utils.systemdUtils.lib) settingsToSections; inherit (utils.systemdUtils.unitOptions) unitOption; inherit (lib) concatStringsSep elem isList literalExpression mapAttrs' mapAttrsToList mkIf mkMerge mkOption mkOrder mkRenamedOptionModule mkRemovedOptionModule nameValuePair optionalAttrs types ; cfg = config.services.resolved; dnsmasqResolve = config.services.dnsmasq.enable && config.services.dnsmasq.resolveLocalQueries; transformSettings = settings: lib.mapAttrs ( key: value: # concat lists for options that should result in space-separated values if elem key [ "DNS" "Domains" "FallbackDNS" ] && isList value then concatStringsSep " " value else value ) (lib.filterAttrs (key: value: value != null) settings); resolvedConf = settingsToSections { Resolve = transformSettings cfg.settings.Resolve; }; in { imports = [ (mkRenamedOptionModule [ "services" "resolved" "fallbackDns" ] [ "services" "resolved" "settings" "Resolve" "FallbackDNS" ] ) (mkRenamedOptionModule [ "services" "resolved" "domains" ] [ "services" "resolved" "settings" "Resolve" "Domains" ] ) (mkRenamedOptionModule [ "services" "resolved" "llmnr" ] [ "services" "resolved" "settings" "Resolve" "LLMNR" ] ) (mkRenamedOptionModule [ "services" "resolved" "dnssec" ] [ "services" "resolved" "settings" "Resolve" "DNSSEC" ] ) (mkRenamedOptionModule [ "services" "resolved" "dnsovertls" ] [ "services" "resolved" "settings" "Resolve" "DNSOverTLS" ] ) (mkRemovedOptionModule [ "services" "resolved" "extraConfig" ] "Use services.resolved.settings instead") ]; options = { services.resolved = { enable = lib.mkEnableOption "the Systemd DNS resolver daemon (systemd-resolved)"; settings.Resolve = mkOption { description = '' Settings option for systemd-resolved. See {manpage}`resolved.conf(5)` for all available options. ''; default = { }; type = types.submodule { freeformType = types.attrsOf unitOption; options = { DNS = mkOption { type = unitOption; default = config.networking.nameservers; defaultText = literalExpression "config.networking.nameservers"; description = '' List of IP addresses to query as recursive DNS resolvers. ''; }; DNSOverTLS = mkOption { type = unitOption; default = false; description = '' Whether to use TLS encryption for DNS queries. Requires nameservers that support DNS-over-TLS. ''; }; DNSSEC = mkOption { type = unitOption; default = false; description = '' Whether to validate DNSSEC for DNS lookups. ''; }; Domains = mkOption { type = unitOption; default = config.networking.search; defaultText = literalExpression "config.networking.search"; example = [ "scope.example.com" "example.com" ]; description = '' List of search domains used to complete unqualified name lookups. ''; }; }; }; }; dnsDelegates = mkOption { description = '' dns-delegate files to be created. See {manpage}`systemd.dns-delegate(5)` for more info. ''; default = { }; type = types.attrsOf ( types.submodule { options.Delegate = mkOption { description = '' Settings option for systemd dns-delegate files. See {manpage}`systemd.dns-delegate(5)` for all available options. ''; type = types.submodule { freeformType = types.attrsOf unitOption; }; }; } ); }; }; boot.initrd.services.resolved.enable = mkOption { default = config.boot.initrd.systemd.network.enable; defaultText = "config.boot.initrd.systemd.network.enable"; description = '' Whether to enable resolved for stage 1 networking. Uses the toplevel 'services.resolved' options for 'resolved.conf' ''; }; }; config = mkMerge [ (mkIf cfg.enable { assertions = [ { assertion = !config.networking.useHostResolvConf; message = "Using host resolv.conf is not supported with systemd-resolved"; } ]; users.users.systemd-resolve.group = "systemd-resolve"; # add resolve to nss hosts database if enabled and nscd enabled # system.nssModules is configured in nixos/modules/system/boot/systemd.nix # added with order 501 to allow modules to go before with mkBefore system.nssDatabases.hosts = (mkOrder 501 [ "resolve [!UNAVAIL=return]" ]); systemd.additionalUpstreamSystemUnits = [ "systemd-resolved.service" "systemd-resolved-monitor.socket" "systemd-resolved-varlink.socket" ]; systemd.services.systemd-resolved = { wantedBy = [ "sysinit.target" ]; aliases = [ "dbus-org.freedesktop.resolve1.service" ]; reloadTriggers = [ config.environment.etc."systemd/resolved.conf".source ] ++ mapAttrsToList ( name: _: config.environment.etc."systemd/dns-delegate.d/${name}.dns-delegate".source ) cfg.dnsDelegates; stopIfChanged = false; }; environment.etc = { "systemd/resolved.conf".text = resolvedConf; # symlink the dynamic stub resolver of resolv.conf as recommended by upstream: # https://www.freedesktop.org/software/systemd/man/systemd-resolved.html#/etc/resolv.conf "resolv.conf".source = "/run/systemd/resolve/stub-resolv.conf"; } // optionalAttrs dnsmasqResolve { "dnsmasq-resolv.conf".source = "/run/systemd/resolve/resolv.conf"; } // mapAttrs' ( name: value: nameValuePair "systemd/dns-delegate.d/${name}.dns-delegate" { text = settingsToSections (transformSettings value); } ) cfg.dnsDelegates; # If networkmanager is enabled, ask it to interface with resolved. networking.networkmanager.dns = "systemd-resolved"; # Since we explicitly provide a resolv.conf, disable resolvconf networking.resolvconf.enable = false; # ... but we still set the package for correct compatibility. networking.resolvconf.package = config.systemd.package; nix.firewall.extraNftablesRules = [ "ip daddr { 127.0.0.53, 127.0.0.54 } udp dport 53 accept comment \"systemd-resolved listening IPs\"" ]; }) (mkIf config.boot.initrd.services.resolved.enable { assertions = [ { assertion = config.boot.initrd.systemd.enable; message = "'boot.initrd.services.resolved.enable' can only be enabled with systemd stage 1."; } ]; boot.initrd.systemd = { contents = { "/etc/systemd/resolved.conf".text = resolvedConf; }; tmpfiles.settings.systemd-resolved-stub."/etc/resolv.conf".L.argument = "/run/systemd/resolve/stub-resolv.conf"; additionalUpstreamUnits = [ "systemd-resolved.service" "systemd-resolved-monitor.socket" "systemd-resolved-varlink.socket" ]; users.systemd-resolve = { }; groups.systemd-resolve = { }; storePaths = [ "${config.boot.initrd.systemd.package}/lib/systemd/systemd-resolved" ]; services.systemd-resolved = { wantedBy = [ "sysinit.target" ]; aliases = [ "dbus-org.freedesktop.resolve1.service" ]; }; }; }) ]; }