1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.trafficserver;
7 user = config.users.users.trafficserver.name;
8 group = config.users.groups.trafficserver.name;
9
10 getManualUrl = name: "https://docs.trafficserver.apache.org/en/latest/admin-guide/files/${name}.en.html";
11 getConfPath = name: "${pkgs.trafficserver}/etc/trafficserver/${name}";
12
13 yaml = pkgs.formats.yaml { };
14
15 fromYAML = f:
16 let
17 jsonFile = pkgs.runCommand "in.json"
18 {
19 nativeBuildInputs = [ pkgs.remarshal ];
20 } ''
21 yaml2json < "${f}" > "$out"
22 '';
23 in
24 builtins.fromJSON (builtins.readFile jsonFile);
25
26 mkYamlConf = name: cfg:
27 if cfg != null then {
28 "trafficserver/${name}.yaml".source = yaml.generate "${name}.yaml" cfg;
29 } else {
30 "trafficserver/${name}.yaml".text = "";
31 };
32
33 mkRecordLines = path: value:
34 if isAttrs value then
35 lib.mapAttrsToList (n: v: mkRecordLines (path ++ [ n ]) v) value
36 else if isInt value then
37 "CONFIG ${concatStringsSep "." path} INT ${toString value}"
38 else if isFloat value then
39 "CONFIG ${concatStringsSep "." path} FLOAT ${toString value}"
40 else
41 "CONFIG ${concatStringsSep "." path} STRING ${toString value}";
42
43 mkRecordsConfig = cfg: concatStringsSep "\n" (flatten (mkRecordLines [ ] cfg));
44 mkPluginConfig = cfg: concatStringsSep "\n" (map (p: "${p.path} ${p.arg}") cfg);
45in
46{
47 options.services.trafficserver = {
48 enable = mkEnableOption "Apache Traffic Server";
49
50 cache = mkOption {
51 type = types.lines;
52 default = "";
53 example = "dest_domain=example.com suffix=js action=never-cache";
54 description = ''
55 Caching rules that overrule the origin's caching policy.
56
57 Consult the <link xlink:href="${getManualUrl "cache.config"}">upstream
58 documentation</link> for more details.
59 '';
60 };
61
62 hosting = mkOption {
63 type = types.lines;
64 default = "";
65 example = "domain=example.com volume=1";
66 description = ''
67 Partition the cache according to origin server or domain
68
69 Consult the <link xlink:href="${getManualUrl "hosting.config"}">
70 upstream documentation</link> for more details.
71 '';
72 };
73
74 ipAllow = mkOption {
75 type = types.nullOr yaml.type;
76 default = fromYAML (getConfPath "ip_allow.yaml");
77 defaultText = "upstream defaults";
78 example = literalExample {
79 ip_allow = [{
80 apply = "in";
81 ip_addrs = "127.0.0.1";
82 action = "allow";
83 methods = "ALL";
84 }];
85 };
86 description = ''
87 Control client access to Traffic Server and Traffic Server connections
88 to upstream servers.
89
90 Consult the <link xlink:href="${getManualUrl "ip_allow.yaml"}">upstream
91 documentation</link> for more details.
92 '';
93 };
94
95 logging = mkOption {
96 type = types.nullOr yaml.type;
97 default = fromYAML (getConfPath "logging.yaml");
98 defaultText = "upstream defaults";
99 example = literalExample { };
100 description = ''
101 Configure logs.
102
103 Consult the <link xlink:href="${getManualUrl "logging.yaml"}">upstream
104 documentation</link> for more details.
105 '';
106 };
107
108 parent = mkOption {
109 type = types.lines;
110 default = "";
111 example = ''
112 dest_domain=. method=get parent="p1.example:8080; p2.example:8080" round_robin=true
113 '';
114 description = ''
115 Identify the parent proxies used in an cache hierarchy.
116
117 Consult the <link xlink:href="${getManualUrl "parent.config"}">upstream
118 documentation</link> for more details.
119 '';
120 };
121
122 plugins = mkOption {
123 default = [ ];
124
125 description = ''
126 Controls run-time loadable plugins available to Traffic Server, as
127 well as their configuration.
128
129 Consult the <link xlink:href="${getManualUrl "plugin.config"}">upstream
130 documentation</link> for more details.
131 '';
132
133 type = with types;
134 listOf (submodule {
135 options.path = mkOption {
136 type = str;
137 example = "xdebug.so";
138 description = ''
139 Path to plugin. The path can either be absolute, or relative to
140 the plugin directory.
141 '';
142 };
143 options.arg = mkOption {
144 type = str;
145 default = "";
146 example = "--header=ATS-My-Debug";
147 description = "arguments to pass to the plugin";
148 };
149 });
150 };
151
152 records = mkOption {
153 type = with types;
154 let valueType = (attrsOf (oneOf [ int float str valueType ])) // {
155 description = "Traffic Server records value";
156 };
157 in
158 valueType;
159 default = { };
160 example = literalExample { proxy.config.proxy_name = "my_server"; };
161 description = ''
162 List of configurable variables used by Traffic Server.
163
164 Consult the <link xlink:href="${getManualUrl "records.config"}">
165 upstream documentation</link> for more details.
166 '';
167 };
168
169 remap = mkOption {
170 type = types.lines;
171 default = "";
172 example = "map http://from.example http://origin.example";
173 description = ''
174 URL remapping rules used by Traffic Server.
175
176 Consult the <link xlink:href="${getManualUrl "remap.config"}">
177 upstream documentation</link> for more details.
178 '';
179 };
180
181 splitDns = mkOption {
182 type = types.lines;
183 default = "";
184 example = ''
185 dest_domain=internal.corp.example named="255.255.255.255:212 255.255.255.254" def_domain=corp.example search_list="corp.example corp1.example"
186 dest_domain=!internal.corp.example named=255.255.255.253
187 '';
188 description = ''
189 Specify the DNS server that Traffic Server should use under specific
190 conditions.
191
192 Consult the <link xlink:href="${getManualUrl "splitdns.config"}">
193 upstream documentation</link> for more details.
194 '';
195 };
196
197 sslMulticert = mkOption {
198 type = types.lines;
199 default = "";
200 example = "dest_ip=* ssl_cert_name=default.pem";
201 description = ''
202 Configure SSL server certificates to terminate the SSL sessions.
203
204 Consult the <link xlink:href="${getManualUrl "ssl_multicert.config"}">
205 upstream documentation</link> for more details.
206 '';
207 };
208
209 sni = mkOption {
210 type = types.nullOr yaml.type;
211 default = null;
212 example = literalExample {
213 sni = [{
214 fqdn = "no-http2.example.com";
215 https = "off";
216 }];
217 };
218 description = ''
219 Configure aspects of TLS connection handling for both inbound and
220 outbound connections.
221
222 Consult the <link xlink:href="${getManualUrl "sni.yaml"}">upstream
223 documentation</link> for more details.
224 '';
225 };
226
227 storage = mkOption {
228 type = types.lines;
229 default = "/var/cache/trafficserver 256M";
230 example = "/dev/disk/by-id/XXXXX volume=1";
231 description = ''
232 List all the storage that make up the Traffic Server cache.
233
234 Consult the <link xlink:href="${getManualUrl "storage.config"}">
235 upstream documentation</link> for more details.
236 '';
237 };
238
239 strategies = mkOption {
240 type = types.nullOr yaml.type;
241 default = null;
242 description = ''
243 Specify the next hop proxies used in an cache hierarchy and the
244 algorithms used to select the next proxy.
245
246 Consult the <link xlink:href="${getManualUrl "strategies.yaml"}">
247 upstream documentation</link> for more details.
248 '';
249 };
250
251 volume = mkOption {
252 type = types.nullOr yaml.type;
253 default = "";
254 example = "volume=1 scheme=http size=20%";
255 description = ''
256 Manage cache space more efficiently and restrict disk usage by
257 creating cache volumes of different sizes.
258
259 Consult the <link xlink:href="${getManualUrl "volume.config"}">
260 upstream documentation</link> for more details.
261 '';
262 };
263 };
264
265 config = mkIf cfg.enable {
266 environment.etc = {
267 "trafficserver/cache.config".text = cfg.cache;
268 "trafficserver/hosting.config".text = cfg.hosting;
269 "trafficserver/parent.config".text = cfg.parent;
270 "trafficserver/plugin.config".text = mkPluginConfig cfg.plugins;
271 "trafficserver/records.config".text = mkRecordsConfig cfg.records;
272 "trafficserver/remap.config".text = cfg.remap;
273 "trafficserver/splitdns.config".text = cfg.splitDns;
274 "trafficserver/ssl_multicert.config".text = cfg.sslMulticert;
275 "trafficserver/storage.config".text = cfg.storage;
276 "trafficserver/volume.config".text = cfg.volume;
277 } // (mkYamlConf "ip_allow" cfg.ipAllow)
278 // (mkYamlConf "logging" cfg.logging)
279 // (mkYamlConf "sni" cfg.sni)
280 // (mkYamlConf "strategies" cfg.strategies);
281
282 environment.systemPackages = [ pkgs.trafficserver ];
283 systemd.packages = [ pkgs.trafficserver ];
284
285 # Traffic Server does privilege handling independently of systemd, and
286 # therefore should be started as root
287 systemd.services.trafficserver = {
288 enable = true;
289 wantedBy = [ "multi-user.target" ];
290 };
291
292 # These directories can't be created by systemd because:
293 #
294 # 1. Traffic Servers starts as root and switches to an unprivileged user
295 # afterwards. The runtime directories defined below are assumed to be
296 # owned by that user.
297 # 2. The bin/trafficserver script assumes these directories exist.
298 systemd.tmpfiles.rules = [
299 "d '/run/trafficserver' - ${user} ${group} - -"
300 "d '/var/cache/trafficserver' - ${user} ${group} - -"
301 "d '/var/lib/trafficserver' - ${user} ${group} - -"
302 "d '/var/log/trafficserver' - ${user} ${group} - -"
303 ];
304
305 services.trafficserver = {
306 records.proxy.config.admin.user_id = user;
307 records.proxy.config.body_factory.template_sets_dir =
308 "${pkgs.trafficserver}/etc/trafficserver/body_factory";
309 };
310
311 users.users.trafficserver = {
312 description = "Apache Traffic Server";
313 isSystemUser = true;
314 inherit group;
315 };
316 users.groups.trafficserver = { };
317 };
318}