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