1{
2 lib,
3 config,
4 pkgs,
5 ...
6}:
7
8let
9 cfg = config.services.acme-dns;
10 format = pkgs.formats.toml { };
11 inherit (lib)
12 literalExpression
13 mkEnableOption
14 mkOption
15 mkPackageOption
16 types
17 ;
18 domain = "acme-dns.example.com";
19in
20{
21 options.services.acme-dns = {
22 enable = mkEnableOption "acme-dns";
23
24 package = mkPackageOption pkgs "acme-dns" { };
25
26 settings = mkOption {
27 description = ''
28 Free-form settings written directly to the `acme-dns.cfg` file.
29 Refer to <https://github.com/joohoi/acme-dns/blob/master/README.md#configuration> for supported values.
30 '';
31
32 default = { };
33
34 type = types.submodule {
35 freeformType = format.type;
36 options = {
37 general = {
38 listen = mkOption {
39 type = types.str;
40 description = "IP+port combination to bind and serve the DNS server on.";
41 default = "[::]:53";
42 example = "127.0.0.1:53";
43 };
44
45 protocol = mkOption {
46 type = types.enum [
47 "both"
48 "both4"
49 "both6"
50 "udp"
51 "udp4"
52 "udp6"
53 "tcp"
54 "tcp4"
55 "tcp6"
56 ];
57 description = "Protocols to serve DNS responses on.";
58 default = "both";
59 };
60
61 domain = mkOption {
62 type = types.str;
63 description = "Domain name to serve the requests off of.";
64 example = domain;
65 };
66
67 nsname = mkOption {
68 type = types.str;
69 description = "Zone name server.";
70 example = domain;
71 };
72
73 nsadmin = mkOption {
74 type = types.str;
75 description = "Zone admin email address for `SOA`.";
76 example = "admin.example.com";
77 };
78
79 records = mkOption {
80 type = types.listOf types.str;
81 description = "Predefined DNS records served in addition to the `_acme-challenge` TXT records.";
82 example = literalExpression ''
83 [
84 # replace with your acme-dns server's public IPv4
85 "${domain}. A 198.51.100.1"
86 # replace with your acme-dns server's public IPv6
87 "${domain}. AAAA 2001:db8::1"
88 # ${domain} should resolve any *.${domain} records
89 "${domain}. NS ${domain}."
90 ]
91 '';
92 };
93 };
94
95 database = {
96 engine = mkOption {
97 type = types.enum [
98 "sqlite3"
99 "postgres"
100 ];
101 description = "Database engine to use.";
102 default = "sqlite3";
103 };
104 connection = mkOption {
105 type = types.str;
106 description = "Database connection string.";
107 example = "postgres://user:password@localhost/acmedns";
108 default = "/var/lib/acme-dns/acme-dns.db";
109 };
110 };
111
112 api = {
113 ip = mkOption {
114 type = types.str;
115 description = "IP to bind the HTTP API on.";
116 default = "[::]";
117 example = "127.0.0.1";
118 };
119
120 port = mkOption {
121 type = types.port;
122 description = "Listen port for the HTTP API.";
123 default = 8080;
124 # acme-dns expects this value to be a string
125 apply = toString;
126 };
127
128 disable_registration = mkOption {
129 type = types.bool;
130 description = "Whether to disable the HTTP registration endpoint.";
131 default = false;
132 example = true;
133 };
134
135 tls = mkOption {
136 type = types.enum [
137 "letsencrypt"
138 "letsencryptstaging"
139 "cert"
140 "none"
141 ];
142 description = "TLS backend to use.";
143 default = "none";
144 };
145 };
146
147 logconfig = {
148 loglevel = mkOption {
149 type = types.enum [
150 "error"
151 "warning"
152 "info"
153 "debug"
154 ];
155 description = "Level to log on.";
156 default = "info";
157 };
158 };
159 };
160 };
161 };
162 };
163
164 config = lib.mkIf cfg.enable {
165 systemd.packages = [ cfg.package ];
166 systemd.services.acme-dns = {
167 wantedBy = [ "multi-user.target" ];
168 serviceConfig = {
169 ExecStart = [
170 ""
171 "${lib.getExe cfg.package} -c ${format.generate "acme-dns.toml" cfg.settings}"
172 ];
173 StateDirectory = "acme-dns";
174 WorkingDirectory = "%S/acme-dns";
175 DynamicUser = true;
176 };
177 };
178 };
179}