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}