{ config, lib, pkgs, ... }: with lib; let cfg = config.services.unbound; stateDir = "/var/lib/unbound"; access = concatMapStringsSep "\n " (x: "access-control: ${x} allow") cfg.allowedAccess; interfaces = concatMapStringsSep "\n " (x: "interface: ${x}") cfg.interfaces; isLocalAddress = x: substring 0 3 x == "::1" || substring 0 9 x == "127.0.0.1"; forward = optionalString (any isLocalAddress cfg.forwardAddresses) '' do-not-query-localhost: no '' + optionalString (cfg.forwardAddresses != []) '' forward-zone: name: . '' + concatMapStringsSep "\n" (x: " forward-addr: ${x}") cfg.forwardAddresses; rootTrustAnchorFile = "${stateDir}/root.key"; trustAnchor = optionalString cfg.enableRootTrustAnchor "auto-trust-anchor-file: ${rootTrustAnchorFile}"; confFile = pkgs.writeText "unbound.conf" '' server: directory: "${stateDir}" username: unbound chroot: "${stateDir}" pidfile: "" ${interfaces} ${access} ${trustAnchor} ${cfg.extraConfig} ${forward} ''; in { ###### interface options = { services.unbound = { enable = mkEnableOption "Unbound domain name server"; package = mkOption { type = types.package; default = pkgs.unbound; defaultText = "pkgs.unbound"; description = "The unbound package to use"; }; allowedAccess = mkOption { default = [ "127.0.0.0/24" ]; type = types.listOf types.str; description = "What networks are allowed to use unbound as a resolver."; }; interfaces = mkOption { default = [ "127.0.0.1" ] ++ optional config.networking.enableIPv6 "::1"; type = types.listOf types.str; description = "What addresses the server should listen on."; }; forwardAddresses = mkOption { default = [ ]; type = types.listOf types.str; description = "What servers to forward queries to."; }; enableRootTrustAnchor = mkOption { default = true; type = types.bool; description = "Use and update root trust anchor for DNSSEC validation."; }; extraConfig = mkOption { default = ""; type = types.lines; description = '' Extra unbound config. See unbound.conf8 . ''; }; }; }; ###### implementation config = mkIf cfg.enable { environment.systemPackages = [ cfg.package ]; users.users.unbound = { description = "unbound daemon user"; isSystemUser = true; }; networking.resolvconf.useLocalResolver = mkDefault true; systemd.services.unbound = { description = "Unbound recursive Domain Name Server"; after = [ "network.target" ]; before = [ "nss-lookup.target" ]; wants = [ "nss-lookup.target" ]; wantedBy = [ "multi-user.target" ]; preStart = '' mkdir -m 0755 -p ${stateDir}/dev/ cp ${confFile} ${stateDir}/unbound.conf ${optionalString cfg.enableRootTrustAnchor '' ${cfg.package}/bin/unbound-anchor -a ${rootTrustAnchorFile} || echo "Root anchor updated!" chown unbound ${stateDir} ${rootTrustAnchorFile} ''} touch ${stateDir}/dev/random ${pkgs.utillinux}/bin/mount --bind -n /dev/urandom ${stateDir}/dev/random ''; serviceConfig = { ExecStart = "${cfg.package}/bin/unbound -d -c ${stateDir}/unbound.conf"; ExecStopPost="${pkgs.utillinux}/bin/umount ${stateDir}/dev/random"; ProtectSystem = true; ProtectHome = true; PrivateDevices = true; Restart = "always"; RestartSec = "5s"; }; }; # If networkmanager is enabled, ask it to interface with unbound. networking.networkmanager.dns = "unbound"; }; }