Introduction

This is the documentation for NixOS-DNS which was built to decouple modules even more from NixOS hosts and is slowly escalating into anything nix*-dns. Please open a issue if you find something is missing.

Here you will find:

  • automagically generated docs for the modules and utility functions (Work In Progress)
  • general usage instructions
  • a contributing/development guide
  • debugging help

Usage

For all the modules options take a look here. There is a quite elaborate example here, you can also use it as a template (nix flake init -t github:Janik-Haag/nixos-dns).

classical nix

Please note that the following code uses colmena to retrieve the DNS information from the hosts configurations, and npins for version pinning. You can of course use nixos-dns without colmena, you'll just have to figure out how to reference nixosConfigurations.

# default.nix
let
  pins = import ./npins;
in
{ pkgs ? import pins.nixpkgs { }
, colmena ? pins.colmena
, nixos-dns ? import pins.nixos-dns
}:
let
  generate = nixos-dns.utils.generate pkgs;
in {
  dns = generate.octodnsConfig {
    config = {
      providers = {
        hetzner = {
          class = "octodns_hetzner.HetznerProvider";
          token = "env/HETZNER_DNS_API";
        };
      };
    };
    zones = {
      "example.com." = nixos-dns.utils.octodns.generateZoneAttrs [ "hetzner" ];
    };
    dnsConfig = {
      nixosConfigurations = (import "${colmena}/src/nix/hive/eval.nix" {
        rawHive = (import ./hive.nix { inherit pkgs nixos-dns; });
      }).nodes;
      extraConfig = import ./dns.nix;
    };
  };
}
# hive.nix
{ pkgs, nixos-dns }: {
  meta = {
    nixpkgs = pkgs;
  };
  defaults = { pkgs, lib, config, ... }: {
    imports = [
      ((import nixos-dns).nixosModules.default)
    ];
  };
  exampleHost = {
    deployment.targetHost = "host.example.com";
    deployment.tags = [ "infra" ];
    imports = [
      ./hosts/exampleHost.nix
    ];
  };
}

Now you just need to run nix-build -A dns to realize the zonefiles and octodns config.

flakes

{
  # You of course have to add the `nixos-dns` input like:
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
    nixos-dns.url = "github:Janik-Haag/nixos-dns";
    nixos-dns.inputs.nixpkgs.follows = "nixpkgs";
  };

  outputs = inputs @ {
    self,
    nixpkgs,
    nixos-dns
  }: let
    # You probably know this but flake outputs are architecture dependent,
    # so we use this little helper function. Many people use `github:numtide/flake-utils` for that.
    forAllSystems = nixpkgs.lib.genAttrs [ "x86_64-linux" "aarch64-linux" ];
  in {
    # Your NixOS configurations
    nixosConfigurations = {
      exampleHost = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [
          # Note that we are importing the nixos-dns module here
          nixos-dns.nixosModules.dns
          ./hosts/exampleHost.nix
        ];
      };
    };

    # We are adding this as package since the zoneFile output is architecture dependent
    packages = forAllSystems (system:
      let
        # `nixos-dns.utils.generate` is a function taking in pkgs as argument
        # and returns a set of functions that are architecture dependent like writing zoneFiles
        generate = nixos-dns.utils.generate nixpkgs.legacyPackages.${system};
      in
        # the attrset `generate.zoneFiles` get passed here is the default interface for dnsConfigs with nixos-dns
        # and anything from the debugging functions, `generate.octodnsConfig` and zoneFiles uses it.
        zoneFiles = generate.zoneFiles {
          inherit (self) nixosConfigurations;
          extraConfig = import ./dns.nix;
        };
      }
    )
  };
}

NixOS Module

NixOS-DNS was built to decouple modules even more from their host. To achieve this we have the concept of baseDomains and subDomains.

In a nixos hosts configuration you would do something like:

  networking.hostName = "example-host";
  networking.domains = {
    enable = true;
    baseDomains = {
      "example.com" = {
        a.data = "203.0.113.42";
        aaaa.data = "2001:db8:1c1b:c00e::1";
      };
    };
  };

This enables the networking.domains module. And registers the baseDomain example.com with the a and aaaa records set. You might notice the .data behind any record, this is because you might want to set the ttl different based on record type. As you can see above the .ttl isn't specifically added to every record, this is because there is networking.domains.defaultTTL So every record has two fields ttl and data, the data type differs based on the record, for more info please refer to the module docs.

And inside of a module you would do something like:

  networking.domains.subDomains."grafana.example.com" = { };

So this would produce this set:

{
  "example.com" = {
    "grafana.example.com" = {
      a = {
        ttl = 86400;
        data = "203.0.113.42";
      };
      aaaa = {
        ttl = 86400;
        data = "2001:db8:1c1b:c00e::1";
      };
    };
  };
}

note

baseDomains and their records don't end up in zone files, octodns configs, or any other output for that matter So in the example above for "example.com" to end up in a zone file you would have to add:

  networking.domains.subDomains."example.com" = { };

to the hosts configuration.

Nix supports ${} operations inside of attrsets, so you can get creative and do stuff like:

  networking.domains.subDomains."${networking.hostname}.example.com" = { };
  networking.domains.subDomains."*.${networking.hostname}.example.com" = { };
  networking.domains.subDomains."${networking.hostname}.prometheus.example.com" = { };

NixOS-DNS does a bunch of magic to automatically map subDomains to their closest baseDomain and throws an error if there is no matching baseDomain. So if we have:

  networking.domains.baseDomains = {
    "example.net" = {};
    "example.com" = {};
    "domain.example.com" = {};
    "subdomain.example.com" = {};
  };

and:

  networking.domains.baseDomains = {
    "example.com" = {};
    "mydomain.example.com" = {};
    "cats.subdomain.example.com" = {};
  };

We would get:

subDomainsmatches
"example.com""example.com"
"mydomain.example.com""example.com"
"cats.subdomain.example.com""subdomain.example.com"

And example.net just wouldn't get matched, but that's fine since it is a baseDomain, if it were a subDomain it would cause an error.

extraConfig

You probably want to add some more information, to do so you can use the extraConfiguration key in the dnsConfig. Please take a look at the example for usage information.

All the hosts in nixosConfigurations and extraConfig get merged and nothing gets overwritten. So if you define multiple domains with the same records all the record data gets merged.

octodns

NixOS-DNS has native octodns support. To use it add a package like the zoneFiles one above just for octodns using generate.octodnsConfig which expects a attrSet with a dnsConfig, a config and a zones key. This would look like:

...
octodns = generate.octodnsConfig {
  # this is the same attr we pass to zoneFiles
  dnsConfig = {
    inherit (self) nixosConfigurations;
    extraConfig = import ./dns.nix;
  };
  # the octodns config key
  config = {
    providers = {
      # adding a provider to push to
      powerdns = {
        class = "octodns_powerdns.PowerDnsProvider";
        host = "ns.dns.invalid";
        # reads the env var from your shell
        api_key = "env/POWERDNS_API_KEY";
      };
    };
  };
  zones = {
    # `nixos-dns.utils.octodns.generateZoneAttrs` is a helper function
    # generating the correct values for usage with NixOS-DNS
    # this is the only place in nixos-dns having trailing dots
    # this was left in so we have a one to one map of the octodns values
    "example.com." = nixos-dns.utils.octodns.generateZoneAttrs [ "powerdns" ];
    "example.org." = nixos-dns.utils.octodns.generateZoneAttrs [ "powerdns" ];
    "missing.invalid." = nixos-dns.utils.octodns.generateZoneAttrs [ "powerdns" ];
  };
};
...

With the example above we at least need the octodns bind and powerdns provider. The powerdns provider is needed because it's used in the example. NixOS-DNS uses the bind provider internally, since it can reads zone-files, so we also need that. We do this because it is a lot less maintenance and less likely to have bugs instead of also maintaining a nixos-dns to octodns internal yaml builder.

You can run the example below to check if your config works, just make sure to do a nix build /your/nixos/config#octodns before and replace example.com. (note the trailing dot) with your domain.

nix-shell -p 'octodns.withProviders (ps: [ octodns-providers.bind octodns-providers.powerdns ])' --run "POWERDNS_API_KEY="" octodns-dump --config-file=./result --output-dir=/tmp/octodns_dump example.com. config"
cat /tmp/octodns_dump/example.com.yaml

Please refer to the octodns documentation for more information, the values should map one to one to nixos-dns.

Updating

We want to strongly encourage you to take a look at the CHANGELOG.md before updating. Other then that the updates should be as straight forward as any other NixOS updates.

Modules

There is a module for NixOS and a module for extraConfig. They both take the same record values and have the same assertions. Please take a look at the coming pages for a list of available options. I currently have trouble rendering the record fields if their type is a sub module, if you are interested in any like caa or mx, you'll sadly have to take a look at the source for now.

extraConfig

Takes in the extraConfig module.

Type: submodule

Default: { }

nixosConfigurations

Takes in the equivalent of the self.nixosConfigurations flake attribute.

Type: attribute set

Default: { }

networking.domains.enable

Whether to enable networking.domains.

Type: boolean

Default: false

Example: true

networking.domains.baseDomains

Attribute set of domains and records for the subdomains to inherit.

Type: attribute set of (submodule)

Default: { }

networking.domains.baseDomains.<name>.a

Type: submodule

Default: { }

networking.domains.baseDomains.<name>.a.data

Commonly used to map a name to a list of IPv4 addresses.

Type: null or ((list of string) or string convertible to it)

Default: null

Example: "9.9.9.9"

networking.domains.baseDomains.<name>.a.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

networking.domains.baseDomains.<name>.aaaa

Type: submodule

Default: { }

networking.domains.baseDomains.<name>.aaaa.data

Commonly used to map a name to a list of IPv6 addresses.

Type: null or ((list of string) or string convertible to it)

Default: null

Example: "2620:fe::fe"

networking.domains.baseDomains.<name>.aaaa.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

networking.domains.baseDomains.<name>.alias

Type: submodule

Default: { }

networking.domains.baseDomains.<name>.alias.data

Maps one domain name to another and uses the dns resolver of your dns server for responses.

Type: null or string or list of string

Default: null

Example: "foo.example.com"

networking.domains.baseDomains.<name>.alias.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

networking.domains.baseDomains.<name>.caa

Type: submodule

Default: { }

networking.domains.baseDomains.<name>.caa.data

DNS Certification Authority Authorization, constraining acceptable CAs for a host/domain

Type: null or (submodule) or list of (null or (submodule))

Default: null

networking.domains.baseDomains.<name>.caa.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

networking.domains.baseDomains.<name>.cname

Type: submodule

Default: { }

networking.domains.baseDomains.<name>.cname.data

Same as alias but the requesting party will have to resolve the response which can lead to more latency.

Type: null or string

Default: null

Example: "foo.example.com"

networking.domains.baseDomains.<name>.cname.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

networking.domains.baseDomains.<name>.dname

Type: submodule

Default: { }

networking.domains.baseDomains.<name>.dname.data

Same as cname but also gets applied to any subdomain of the given domain

Type: null or string

Default: null

Example: "foo.example.com"

networking.domains.baseDomains.<name>.dname.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

networking.domains.baseDomains.<name>.mx

Type: submodule

Default: { }

networking.domains.baseDomains.<name>.mx.data

List of mail exchange servers that accept email for this domain.

Type: null or (submodule) or list of (null or (submodule))

Default: null

networking.domains.baseDomains.<name>.mx.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

networking.domains.baseDomains.<name>.ns

Type: submodule

Default: { }

networking.domains.baseDomains.<name>.ns.data

Nameserver responsible for your zone. Note, that this option technically allows for only one name server but I would strongly advise against that.

Type: null or ((list of string) or string convertible to it)

Default: null

Example:

[
  "ns1.example.com"
  "ns2.example.com"
  "ns3.example.com"
]

networking.domains.baseDomains.<name>.ns.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

networking.domains.baseDomains.<name>.soa

Type: submodule

Default: { }

networking.domains.baseDomains.<name>.soa.data

Specifies authoritative information about a DNS zone.

Type: null or (submodule)

Default: null

networking.domains.baseDomains.<name>.soa.data.expire

If a secondary server does not get a response from the primary server for this amount of time, it should stop responding to queries for the zone.

Type: signed integer

Example: ""

networking.domains.baseDomains.<name>.soa.data.mname

This is the name of the primary nameserver for the zone. Secondary servers that maintain duplicates of the zone’s DNS records receive updates to the zone from this primary server.

Type: string

Example: "ns.example.com"

networking.domains.baseDomains.<name>.soa.data.refresh

The length of time secondary servers should wait before asking primary servers for the SOA record to see if it has been updated.

Type: signed integer

Example: 86400

networking.domains.baseDomains.<name>.soa.data.retry

The length of time a server should wait for asking an unresponsive primary nameserver for an update again.

Type: signed integer

Example: ""

networking.domains.baseDomains.<name>.soa.data.rname

Email of zone administrators.

Type: string

Example: "noc@example.com"

networking.domains.baseDomains.<name>.soa.data.serial

A zone serial number is a version number for the SOA record (the higher the newer). When the serial number changes in a zone file, this alerts secondary nameservers that they should update their copies of the zone file via a zone transfer. Usually most dns-utilities working with zonefiles increment it automatically.

Type: signed integer

Example: ""

networking.domains.baseDomains.<name>.soa.data.ttl

Type: signed integer

Default: cfg.defaultTTL

Example: ""

networking.domains.baseDomains.<name>.soa.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

networking.domains.baseDomains.<name>.spf

Type: submodule

Default: { }

networking.domains.baseDomains.<name>.spf.data

Spf record won’t be implemented due to deprecation in RFC 7208, please use a txt record

Type: unspecified value

Default: null

networking.domains.baseDomains.<name>.spf.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

networking.domains.baseDomains.<name>.srv

Type: submodule

Default: { }

networking.domains.baseDomains.<name>.srv.data

Specification of data in the Domain Name System defining the location, i.e., the hostname and port number, of servers for specified services. It is defined in RFC 2782.

Type: null or (submodule) or list of (null or (submodule))

Default: null

networking.domains.baseDomains.<name>.srv.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

networking.domains.baseDomains.<name>.txt

Type: submodule

Default: { }

networking.domains.baseDomains.<name>.txt.data

Just any string, commonly used to transfer machine readable metadata.

Type: null or ((list of string) or string convertible to it)

Default: null

Example: "v=DMARC1; p=none"

networking.domains.baseDomains.<name>.txt.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

networking.domains.baseDomains.<name>.uri

Type: submodule

Default: { }

networking.domains.baseDomains.<name>.uri.data

Used for publishing mappings from hostnames to URIs.

Type: null or (submodule) or list of (null or (submodule))

Default: null

networking.domains.baseDomains.<name>.uri.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

networking.domains.subDomains

Attribute set of subdomains that inherit values from their matching domain.

Type: attribute set of (submodule)

Default: { }

networking.domains.subDomains.<name>.a

Type: submodule

Default: { }

networking.domains.subDomains.<name>.a.data

Commonly used to map a name to a list of IPv4 addresses.

Type: null or ((list of string) or string convertible to it)

Default: Automatically use the same record as the matching base domain

Example: "9.9.9.9"

networking.domains.subDomains.<name>.a.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

networking.domains.subDomains.<name>.aaaa

Type: submodule

Default: { }

networking.domains.subDomains.<name>.aaaa.data

Commonly used to map a name to a list of IPv6 addresses.

Type: null or ((list of string) or string convertible to it)

Default: Automatically use the same record as the matching base domain

Example: "2620:fe::fe"

networking.domains.subDomains.<name>.aaaa.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

networking.domains.subDomains.<name>.alias

Type: submodule

Default: { }

networking.domains.subDomains.<name>.alias.data

Maps one domain name to another and uses the dns resolver of your dns server for responses.

Type: null or string or list of string

Default: Automatically use the same record as the matching base domain

Example: "foo.example.com"

networking.domains.subDomains.<name>.alias.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

networking.domains.subDomains.<name>.caa

Type: submodule

Default: { }

networking.domains.subDomains.<name>.caa.data

DNS Certification Authority Authorization, constraining acceptable CAs for a host/domain

Type: null or (submodule) or list of (null or (submodule))

Default: Automatically use the same record as the matching base domain

networking.domains.subDomains.<name>.caa.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

networking.domains.subDomains.<name>.cname

Type: submodule

Default: { }

networking.domains.subDomains.<name>.cname.data

Same as alias but the requesting party will have to resolve the response which can lead to more latency.

Type: null or string or list of string

Default: Automatically use the same record as the matching base domain

Example: "foo.example.com"

networking.domains.subDomains.<name>.cname.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

networking.domains.subDomains.<name>.dname

Type: submodule

Default: { }

networking.domains.subDomains.<name>.dname.data

Same as cname but also gets applied to any subdomain of the given domain

Type: null or string or list of string

Default: Automatically use the same record as the matching base domain

Example: "foo.example.com"

networking.domains.subDomains.<name>.dname.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

networking.domains.subDomains.<name>.mx

Type: submodule

Default: { }

networking.domains.subDomains.<name>.mx.data

List of mail exchange servers that accept email for this domain.

Type: null or (submodule) or list of (null or (submodule))

Default: Automatically use the same record as the matching base domain

networking.domains.subDomains.<name>.mx.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

networking.domains.subDomains.<name>.ns

Type: submodule

Default: { }

networking.domains.subDomains.<name>.ns.data

Nameserver responsible for your zone. Note, that this option technically allows for only one name server but I would strongly advise against that.

Type: null or ((list of string) or string convertible to it)

Default: Automatically use the same record as the matching base domain

Example:

[
  "ns1.example.com"
  "ns2.example.com"
  "ns3.example.com"
]

networking.domains.subDomains.<name>.ns.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

networking.domains.subDomains.<name>.soa

Type: submodule

Default: { }

networking.domains.subDomains.<name>.soa.data

Specifies authoritative information about a DNS zone.

Type: null or (submodule)

Default: Automatically use the same record as the matching base domain

networking.domains.subDomains.<name>.soa.data.expire

If a secondary server does not get a response from the primary server for this amount of time, it should stop responding to queries for the zone.

Type: signed integer

Example: ""

networking.domains.subDomains.<name>.soa.data.mname

This is the name of the primary nameserver for the zone. Secondary servers that maintain duplicates of the zone’s DNS records receive updates to the zone from this primary server.

Type: string

Example: "ns.example.com"

networking.domains.subDomains.<name>.soa.data.refresh

The length of time secondary servers should wait before asking primary servers for the SOA record to see if it has been updated.

Type: signed integer

Example: 86400

networking.domains.subDomains.<name>.soa.data.retry

The length of time a server should wait for asking an unresponsive primary nameserver for an update again.

Type: signed integer

Example: ""

networking.domains.subDomains.<name>.soa.data.rname

Email of zone administrators.

Type: string

Example: "noc@example.com"

networking.domains.subDomains.<name>.soa.data.serial

A zone serial number is a version number for the SOA record (the higher the newer). When the serial number changes in a zone file, this alerts secondary nameservers that they should update their copies of the zone file via a zone transfer. Usually most dns-utilities working with zonefiles increment it automatically.

Type: signed integer

Example: ""

networking.domains.subDomains.<name>.soa.data.ttl

Type: signed integer

Default: cfg.defaultTTL

Example: ""

networking.domains.subDomains.<name>.soa.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

networking.domains.subDomains.<name>.spf

Type: submodule

Default: { }

networking.domains.subDomains.<name>.spf.data

Spf record won’t be implemented due to deprecation in RFC 7208, please use a txt record

Type: unspecified value

Default: Automatically use the same record as the matching base domain

networking.domains.subDomains.<name>.spf.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

networking.domains.subDomains.<name>.srv

Type: submodule

Default: { }

networking.domains.subDomains.<name>.srv.data

Specification of data in the Domain Name System defining the location, i.e., the hostname and port number, of servers for specified services. It is defined in RFC 2782.

Type: null or (submodule) or list of (null or (submodule))

Default: Automatically use the same record as the matching base domain

networking.domains.subDomains.<name>.srv.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

networking.domains.subDomains.<name>.txt

Type: submodule

Default: { }

networking.domains.subDomains.<name>.txt.data

Just any string, commonly used to transfer machine readable metadata.

Type: null or ((list of string) or string convertible to it)

Default: Automatically use the same record as the matching base domain

Example: "v=DMARC1; p=none"

networking.domains.subDomains.<name>.txt.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

networking.domains.subDomains.<name>.uri

Type: submodule

Default: { }

networking.domains.subDomains.<name>.uri.data

Used for publishing mappings from hostnames to URIs.

Type: null or (submodule) or list of (null or (submodule))

Default: Automatically use the same record as the matching base domain

networking.domains.subDomains.<name>.uri.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

zones

Takes in a attrset of domain apex and their entries.

Type: attribute set of attribute set of (submodule)

Default: { }

zones.<name>.<name>.a

Type: submodule

Default: { }

zones.<name>.<name>.a.data

Commonly used to map a name to a list of IPv4 addresses.

Type: null or ((list of string) or string convertible to it)

Default: null

Example: "9.9.9.9"

zones.<name>.<name>.a.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

zones.<name>.<name>.aaaa

Type: submodule

Default: { }

zones.<name>.<name>.aaaa.data

Commonly used to map a name to a list of IPv6 addresses.

Type: null or ((list of string) or string convertible to it)

Default: null

Example: "2620:fe::fe"

zones.<name>.<name>.aaaa.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

zones.<name>.<name>.alias

Type: submodule

Default: { }

zones.<name>.<name>.alias.data

Maps one domain name to another and uses the dns resolver of your dns server for responses.

Type: null or string or list of string

Default: null

Example: "foo.example.com"

zones.<name>.<name>.alias.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

zones.<name>.<name>.caa

Type: submodule

Default: { }

zones.<name>.<name>.caa.data

DNS Certification Authority Authorization, constraining acceptable CAs for a host/domain

Type: null or (submodule) or list of (null or (submodule))

Default: null

zones.<name>.<name>.caa.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

zones.<name>.<name>.cname

Type: submodule

Default: { }

zones.<name>.<name>.cname.data

Same as alias but the requesting party will have to resolve the response which can lead to more latency.

Type: null or string

Default: null

Example: "foo.example.com"

zones.<name>.<name>.cname.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

zones.<name>.<name>.dname

Type: submodule

Default: { }

zones.<name>.<name>.dname.data

Same as cname but also gets applied to any subdomain of the given domain

Type: null or string

Default: null

Example: "foo.example.com"

zones.<name>.<name>.dname.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

zones.<name>.<name>.mx

Type: submodule

Default: { }

zones.<name>.<name>.mx.data

List of mail exchange servers that accept email for this domain.

Type: null or (submodule) or list of (null or (submodule))

Default: null

zones.<name>.<name>.mx.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

zones.<name>.<name>.ns

Type: submodule

Default: { }

zones.<name>.<name>.ns.data

Nameserver responsible for your zone. Note, that this option technically allows for only one name server but I would strongly advise against that.

Type: null or ((list of string) or string convertible to it)

Default: null

Example:

[
  "ns1.example.com"
  "ns2.example.com"
  "ns3.example.com"
]

zones.<name>.<name>.ns.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

zones.<name>.<name>.soa

Type: submodule

Default: { }

zones.<name>.<name>.soa.data

Specifies authoritative information about a DNS zone.

Type: null or (submodule)

Default: null

zones.<name>.<name>.soa.data.expire

If a secondary server does not get a response from the primary server for this amount of time, it should stop responding to queries for the zone.

Type: signed integer

Example: ""

zones.<name>.<name>.soa.data.mname

This is the name of the primary nameserver for the zone. Secondary servers that maintain duplicates of the zone’s DNS records receive updates to the zone from this primary server.

Type: string

Example: "ns.example.com"

zones.<name>.<name>.soa.data.refresh

The length of time secondary servers should wait before asking primary servers for the SOA record to see if it has been updated.

Type: signed integer

Example: 86400

zones.<name>.<name>.soa.data.retry

The length of time a server should wait for asking an unresponsive primary nameserver for an update again.

Type: signed integer

Example: ""

zones.<name>.<name>.soa.data.rname

Email of zone administrators.

Type: string

Example: "noc@example.com"

zones.<name>.<name>.soa.data.serial

A zone serial number is a version number for the SOA record (the higher the newer). When the serial number changes in a zone file, this alerts secondary nameservers that they should update their copies of the zone file via a zone transfer. Usually most dns-utilities working with zonefiles increment it automatically.

Type: signed integer

Example: ""

zones.<name>.<name>.soa.data.ttl

Type: signed integer

Default: cfg.defaultTTL

Example: ""

zones.<name>.<name>.soa.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

zones.<name>.<name>.spf

Type: submodule

Default: { }

zones.<name>.<name>.spf.data

Spf record won’t be implemented due to deprecation in RFC 7208, please use a txt record

Type: unspecified value

Default: null

zones.<name>.<name>.spf.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

zones.<name>.<name>.srv

Type: submodule

Default: { }

zones.<name>.<name>.srv.data

Specification of data in the Domain Name System defining the location, i.e., the hostname and port number, of servers for specified services. It is defined in RFC 2782.

Type: null or (submodule) or list of (null or (submodule))

Default: null

zones.<name>.<name>.srv.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

zones.<name>.<name>.txt

Type: submodule

Default: { }

zones.<name>.<name>.txt.data

Just any string, commonly used to transfer machine readable metadata.

Type: null or ((list of string) or string convertible to it)

Default: null

Example: "v=DMARC1; p=none"

zones.<name>.<name>.txt.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

zones.<name>.<name>.uri

Type: submodule

Default: { }

zones.<name>.<name>.uri.data

Used for publishing mappings from hostnames to URIs.

Type: null or (submodule) or list of (null or (submodule))

Default: null

zones.<name>.<name>.uri.ttl

The time to live (TTL) is a field on DNS records that tells you how long the record is valid (in seconds) and thus when it will be updated.

Type: signed integer

Default: Automatically use the same ttl as the matching base domain

Example: 86400

Utils

These are a ton of helper functions that are used in NixOS-DNS internally, but you can also use them when working with dns in nix. Please take a look at the upcoming pages in this chapter for more information. Sadly no usage examples since I had trouble getting them to render properly with nixdoc and mdBook.

debug

utils.debug.host

Type: utils.debug.host :: nixosConfiguration -> AttrSet

Takes in one nixosConfiguration and returns a set of all the merged nixos-dns values.

host

: The host you want to debug

utils.debug.config

Type: utils.debug.config :: (AttrSet of dnsConfig) -> AttrSet

Function that returns the set of all the merged hosts and extraConfig

dnsConfig

: The dnsConfig module set

domains

utils.domains.getParts

Type: utils.domains.getParts :: String -> [ String ]

Convert a string like "example.org" to a list like [ "example" "org" ]

domains

: String of a domain

utils.domains.comparePart

Type: utils.domains.compareParts :: String, Null -> String, Null -> Int

Compare domain parts and give them a value If sub and base match they are valued 1 If sub and base don't match but base is null return 0 And in every other case return -1

sub

: the sub domain part you want to compare

base

: the base domain part you want to compare

utils.domains.comparableParts

Type: utils.domains.comparableParts :: String -> String -> { sub :: [ Null, String ], base [ Null, String ] }

uses fillList to generate two lists of domain parts, that are easily comparable. Will return attrSet like:

{
  sub = [ "my" "fancy" "example" "com" ]
  base = [ null null "example" "com" ]
}

subDomain

: subDomain you want to compare

baseDomain

: baseDomain you want to compare

utils.domains.rate

Type: utils.domains.rate :: [ String ] -> [ String ] -> [ Int ]

This returns a list like [ 0 (-1) 1 1 ] Which contains the order comparison of a sub domain and a base domain

subDomain

: expects a deconstructed domain like [ "example" "com" ]

baseDomain

: expects a deconstructed domain like [ "my" "example" "com" ]

utils.domains.construct

Type: utils.domains.construct :: [ String ] -> String

Expects a list of domain parts like [ "ns" "example" "com" ] and builds a domain from it, in this case: ns.example.com

parts

: list of domain parts to construct

utils.domains.validateSubDomain

Type: utils.domains.validateSubDomain :: [ String ] -> [ String ] -> { valid :: Bool, value :: Int }

This returns a attrSet like

{
  valid = true;
  value = 2;
}

with valid telling you if the sub domain corresponds to the base domain and value telling you how close it is (higher is better) let's take for example: [ "my" "example" "com" ] as sub domain and [ "example" "com" ] as base domain, this would return the attrSet shown above. Because [ "example" "com" ] will expand to [ null "example" "com" ] and then get rated like:

"my" == null = 0
"example" == "example" = 1
"com" == "com" = 1

the domain is valid since there is no negative value and the total value is 2

subDomain

: takes the same input as ratedDomain

baseDomain

: takes the same input as ratedDomain

utils.domains.getMostSpecific

Type: utils.domains.getMostSpecific :: String -> [ String ] -> String, Null

This function takes a sub domain and a list of domains, and will find the most similar domain from the list. It does this by comparing the domain parts and not singe letters so if we have sub domain.example.com and [ sub.example.com example.com ] then we would get example.com as a result. If the sub domain doesn't have a matching one in the list the function will return null

subDomain

: a string of a domain like "example.com"

baseDomains

: Function argument

utils.domains.mapBaseToSub

Type: utils.domains.mapBaseToSub :: String -> Attr -> String -> Any

This Functions uses getMostSpecific to get the value of a corresponding key for a sub domain

subDomain

: takes a attrSet like the one provided by networking.domains.subDomains

baseDomains

: takes a attrSet like the one provided by `networking.domains.baseDomains

value

: the key from which to get the value

utils.domains.getDomainsFromNixosConfigurations

Type: utils.domains.getDomainsFromNixosConfigurations :: Attr -> Attr

Be care full when using this, since you might end up overwriting previous results because if a key is defined multiple times only the last value will remain except if the value is a list then all of the content will be merged

nixosConfigurations

: Function argument

utils.domains.getDnsConfig

Type: utils.domains.getDnsConfig :: Attr -> Attr

Expects a attribute-set like:

{
inherit (self) nixosConfigurations darwinConfigurations;
extraConfig = import ./dns.nix;
}

it will do special casing for the keys nixosConfigurations (and potentially darwinConfiguratiosn) and every other key is expected to have a attrs that looks like the output of utils.debug it will then go ahead and merge all the dns configs into one.

config

: Function argument

general

utils.general.recursiveUpdateLists

Type: utils.general.recursiveUpdateLists :: [ Attr ] -> Attr

Function that merges sets the same as lib.recursiveUpdate. But if a value is a list it merges the list instead of overwriting it. Stolen from here

attrList

: List of sets to merge

utils.general.fillList

Type: utils.general.recursiveUpdateLists :: [ Any ] -> Int -> [ Any ]

Prepends a list with a specific value X amount of times For example say we have [ "my" "fancy" "example" "com" ] And [ "example" "com" ] but want to compare them, then we can use this function like this:

let
  lenSub = (builtins.length [ "my" "fancy" "example" "com" ]);
  lenBase = (builtins.length [ "example" "com" ]);
in
  fillList subDomain (lenBase - lenSub) null

which will result in: [ null null "example" "com" ]

list

: list to prepend

amount

: how often

value

: with what

generate

This is a bit of a special thing since unlike every other utils.* namespace this is not a set but a function returning a set. The only function input is pkgs and it expects the nixpkgs package set. This is done in this way to keep this functions pure.

utils.generate.zoneFiles

Type: utils.generate.zoneFiles :: Attr -> [ Files ]

Generates zonefiles from dnsConfig

config

: expects the dnsConfig module output as a input

utils.generate.linkZoneFiles

Type: utils.generate.linkZoneFiles :: Attr -> [ Files ]

config

: takes the output from utils.domains.getDnsConfig

utils.generate.octodnsConfig

Takes a Attrset like

  {
    inherit dnsConfig;
    config = { };
    zones = { };

    # optionally
    manager = { };
  }

Everything except for dnsConfig is a 1:1 map of the octodns config yaml described in their docs.

config

: The required config

helper

octodns

utils.octodns.fakeSOA

Type: utils.octodns.fakeSOA :: Attr -> Attr

Just adds a dummy SOA record. It won't actually be used by anything. But the octodns bind module has a check for the validity of a zone-file and a zone-file MUST have a SOA record. Anyways, octodns will just ignore its existence and only sync supported records.

dnsConfig

: takes the dnsConfig module

utils.octodns.makeConfigAttrs

Type: utils.octodns.makeConfigAttrs :: Attr -> Attr

Same thing as generate.octodnsConfig but instead of returning a derivation it returns a set ready for converting it to a file.

settings

: Takes the same attrset input as generate.octodnsConfig

zonefiles

utils.zonefiles.formatTxtRecord

Type: utils.zonefiles.formatTxtRecord :: String -> String

Converts a string into a valid txt record so it's compliant with RFC 4408 This means it splits the string every 255 chars and surrounds it with quotation marks

txtString

: The String of a txt resource record

utils.zonefiles.convertRecordToStr

Type: utils.zonefiles.convertRecordToStr :: String -> Any -> String

attributeset Takes any record from the module and converts it to a fitting zonefile string

record

: Record type, like a or caa

value

: Record value, like "198.51.100.42"

utils.zonefiles.mkZoneString

Type: utils.zonefiles.mkZoneString :: Attr -> String

Converts a zone attributeset into a zonefile and returns a multiline string

entries

: Takes dnsConfig."your-domain.invalid"

utils.zonefiles.write

Type: utils.zonefiles.write :: String -> Attr -> File

Returns a zone-file from NixOS-DNS values Can nicely be used with lib.mapAttrsToList

domainName

: takes "your-domain.invalid"

domainValues

: takes dnsConfig."your-domain.invalid"

Debugging

nixos-dns

In the example/flake.nix you will find:

  # nix eval .#dnsDebugHost
  dnsDebugHost = nixos-dns.utils.debug.host self.nixosConfigurations.host1;

  # nix eval .#dnsDebugConfig
  dnsDebugConfig = nixos-dns.utils.debug.config dnsConfig;

Executing either of the two will print a attrset of all the merged values that will be used in your config. You can just copy them in to your own flake and change dnsConfig/host1 to the one you actually want to debug.

Note

You can pipe the output of nix eval to nixfmt for pretty printing and into bat for syntax highlighting That could look Like bat -pP -l=nix <(nix eval .#dnsDebugHost | nixfmt)

zone files

You can use named-checkzone from the bind package like:

# named-checkzone zonename zonefile
named-checkzone example.com result/example.com

to check the validity of your zone file.

octodns config

You can use octodns-dump as described in the octodns usage section. Other then that you are pretty much on your own, sorry. (but feel free to open a issue)

Contributing/Development

First of all thank you for considering to contribute to this Project. If you want to just fix a small thing or add a tiny function feel free to open a PR and I'll probably just merge it after review. You can of course also open a issue. If you are thinking about doing a larger thing, for example adding dnscontrol support consider opening a issue first or doing a draft PR so we can talk about implantation details beforehand.

ToolingUsage Example
nixpkgs-fmt as formatternix fmt
statix as linterstatix check
nix-unit for unit testsnix-unit --flake .#tests
nixdoc for documentationnix build .#docs && xdg-open result/index.html

all of these are in the projects nix devshell so just run nix develop or direnv allow and they will be available in your shell.

modules

Uses the same module system as nixpkgs. Documentation builds fail if any description field is empty, so be sure to add one. If a default module value is not a primary data type but tries to evaluate a function add the defaultText string, otherwise documentation builds will fail.

Please document breaking changes in the CHANGELOG.md

utils

Every function is documented using nixdoc. Please refer to the nixdoc readme for a howto or copy existing functions.

And every function has at least one unit test, ensuring that it works. You can find the tests in utils/test-*.nix

Modifying a existing function

  • update unit-tests
  • update documentation
  • add breaking changes to CHANGELOG.md

Adding a new function

  • add a unit-test
  • add documentation

Deleting a function

  • remove corresponding unit-tests
  • add breaking changes to CHANGELOG.md

docs

The docs are being built using mdBook, the build process is abstracted away into a nix derivation that outputs a static rendering of the book. You can find the book files in docs/book, all the files there get copied into the book and can be written like any other mdBook. While building the book, the deviations docs/utils.nix and docs/modules.nix also get built which generate the markdown for the utility functions and modules using nixdoc and the modules system builtin documentation system.

You can build the docs locally by doing:

nix build .#docs && xdg-open result/index.html

When adding any examples please use the resources linked in the table below:

Resources reserved for documentationRelated RFC
DomainsRFC2606
IPv6RFC3849
IPv4RFC5737