main
 1{lib}: let
 2  fromClearOrSecret = addr:
 3    if addr.clearText != null
 4    then {
 5      kind = "clear";
 6      value = addr.clearText;
 7    }
 8    else if addr.secret.name != null && addr.secret.file != null
 9    then {
10      kind = "secret";
11      name = addr.secret.name;
12      file = addr.secret.file;
13    }
14    else null;
15
16  mkClear = value:
17    if value != null
18    then {
19      kind = "clear";
20      inherit value;
21    }
22    else null;
23
24  resolve = host: domain: conf: let
25    prefer = conf.prefer or null;
26    mode =
27      if prefer != null
28      then prefer
29      else host.settings.services.direct-dns.mode or null;
30    v4 =
31      if mode == "direct"
32      then fromClearOrSecret host.address.ipv4
33      else if mode == "tailscale"
34      then mkClear host.address.ipv4.tailscale
35      else null;
36    v6 =
37      if mode == "direct"
38      then fromClearOrSecret host.address.ipv6
39      else if mode == "tailscale"
40      then mkClear host.address.ipv6.tailscale
41      else null;
42  in
43    assert lib.assertMsg (mode != "direct" || v4 != null || v6 != null)
44    "direct-dns: domain '${domain}' on host '${host.name}' prefers 'direct' but the host has no public IPv4/IPv6 configured."; {
45      inherit domain mode v4 v6;
46      hostName = host.name;
47    };
48in {
49  mkDirectEntries = reverseProxy:
50    lib.unique (
51      lib.concatMap (
52        r:
53          lib.mapAttrsToList (resolve r.source.host) (
54            lib.filterAttrs (_: conf: !(conf.tailscale or false)) r.value
55          )
56      )
57      reverseProxy
58    );
59}