diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 22f1fde43eaf..567fbe9c0b6a 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -964,6 +964,7 @@ ./services/web-servers/shellinabox.nix ./services/web-servers/tomcat.nix ./services/web-servers/traefik.nix + ./services/web-servers/trafficserver.nix ./services/web-servers/ttyd.nix ./services/web-servers/uwsgi.nix ./services/web-servers/varnish/default.nix diff --git a/nixos/modules/services/web-servers/trafficserver.nix b/nixos/modules/services/web-servers/trafficserver.nix new file mode 100644 index 000000000000..db0e2ac0bd05 --- /dev/null +++ b/nixos/modules/services/web-servers/trafficserver.nix @@ -0,0 +1,318 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.trafficserver; + user = config.users.users.trafficserver.name; + group = config.users.groups.trafficserver.name; + + getManualUrl = name: "https://docs.trafficserver.apache.org/en/latest/admin-guide/files/${name}.en.html"; + getConfPath = name: "${pkgs.trafficserver}/etc/trafficserver/${name}"; + + yaml = pkgs.formats.yaml { }; + + fromYAML = f: + let + jsonFile = pkgs.runCommand "in.json" + { + nativeBuildInputs = [ pkgs.remarshal ]; + } '' + yaml2json < "${f}" > "$out" + ''; + in + builtins.fromJSON (builtins.readFile jsonFile); + + mkYamlConf = name: cfg: + if cfg != null then { + "trafficserver/${name}.yaml".source = yaml.generate "${name}.yaml" cfg; + } else { + "trafficserver/${name}.yaml".text = ""; + }; + + mkRecordLines = path: value: + if isAttrs value then + lib.mapAttrsToList (n: v: mkRecordLines (path ++ [ n ]) v) value + else if isInt value then + "CONFIG ${concatStringsSep "." path} INT ${toString value}" + else if isFloat value then + "CONFIG ${concatStringsSep "." path} FLOAT ${toString value}" + else + "CONFIG ${concatStringsSep "." path} STRING ${toString value}"; + + mkRecordsConfig = cfg: concatStringsSep "\n" (flatten (mkRecordLines [ ] cfg)); + mkPluginConfig = cfg: concatStringsSep "\n" (map (p: "${p.path} ${p.arg}") cfg); +in +{ + options.services.trafficserver = { + enable = mkEnableOption "Apache Traffic Server"; + + cache = mkOption { + type = types.lines; + default = ""; + example = "dest_domain=example.com suffix=js action=never-cache"; + description = '' + Caching rules that overrule the origin's caching policy. + + Consult the upstream + documentation for more details. + ''; + }; + + hosting = mkOption { + type = types.lines; + default = ""; + example = "domain=example.com volume=1"; + description = '' + Partition the cache according to origin server or domain + + Consult the + upstream documentation for more details. + ''; + }; + + ipAllow = mkOption { + type = types.nullOr yaml.type; + default = fromYAML (getConfPath "ip_allow.yaml"); + defaultText = "upstream defaults"; + example = literalExample { + ip_allow = [{ + apply = "in"; + ip_addrs = "127.0.0.1"; + action = "allow"; + methods = "ALL"; + }]; + }; + description = '' + Control client access to Traffic Server and Traffic Server connections + to upstream servers. + + Consult the upstream + documentation for more details. + ''; + }; + + logging = mkOption { + type = types.nullOr yaml.type; + default = fromYAML (getConfPath "logging.yaml"); + defaultText = "upstream defaults"; + example = literalExample { }; + description = '' + Configure logs. + + Consult the upstream + documentation for more details. + ''; + }; + + parent = mkOption { + type = types.lines; + default = ""; + example = '' + dest_domain=. method=get parent="p1.example:8080; p2.example:8080" round_robin=true + ''; + description = '' + Identify the parent proxies used in an cache hierarchy. + + Consult the upstream + documentation for more details. + ''; + }; + + plugins = mkOption { + default = [ ]; + + description = '' + Controls run-time loadable plugins available to Traffic Server, as + well as their configuration. + + Consult the upstream + documentation for more details. + ''; + + type = with types; + listOf (submodule { + options.path = mkOption { + type = str; + example = "xdebug.so"; + description = '' + Path to plugin. The path can either be absolute, or relative to + the plugin directory. + ''; + }; + options.arg = mkOption { + type = str; + default = ""; + example = "--header=ATS-My-Debug"; + description = "arguments to pass to the plugin"; + }; + }); + }; + + records = mkOption { + type = with types; + let valueType = (attrsOf (oneOf [ int float str valueType ])) // { + description = "Traffic Server records value"; + }; + in + valueType; + default = { }; + example = literalExample { proxy.config.proxy_name = "my_server"; }; + description = '' + List of configurable variables used by Traffic Server. + + Consult the + upstream documentation for more details. + ''; + }; + + remap = mkOption { + type = types.lines; + default = ""; + example = "map http://from.example http://origin.example"; + description = '' + URL remapping rules used by Traffic Server. + + Consult the + upstream documentation for more details. + ''; + }; + + splitDns = mkOption { + type = types.lines; + default = ""; + example = '' + 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" + dest_domain=!internal.corp.example named=255.255.255.253 + ''; + description = '' + Specify the DNS server that Traffic Server should use under specific + conditions. + + Consult the + upstream documentation for more details. + ''; + }; + + sslMulticert = mkOption { + type = types.lines; + default = ""; + example = "dest_ip=* ssl_cert_name=default.pem"; + description = '' + Configure SSL server certificates to terminate the SSL sessions. + + Consult the + upstream documentation for more details. + ''; + }; + + sni = mkOption { + type = types.nullOr yaml.type; + default = null; + example = literalExample { + sni = [{ + fqdn = "no-http2.example.com"; + https = "off"; + }]; + }; + description = '' + Configure aspects of TLS connection handling for both inbound and + outbound connections. + + Consult the upstream + documentation for more details. + ''; + }; + + storage = mkOption { + type = types.lines; + default = "/var/cache/trafficserver 256M"; + example = "/dev/disk/by-id/XXXXX volume=1"; + description = '' + List all the storage that make up the Traffic Server cache. + + Consult the + upstream documentation for more details. + ''; + }; + + strategies = mkOption { + type = types.nullOr yaml.type; + default = null; + description = '' + Specify the next hop proxies used in an cache hierarchy and the + algorithms used to select the next proxy. + + Consult the + upstream documentation for more details. + ''; + }; + + volume = mkOption { + type = types.nullOr yaml.type; + default = ""; + example = "volume=1 scheme=http size=20%"; + description = '' + Manage cache space more efficiently and restrict disk usage by + creating cache volumes of different sizes. + + Consult the + upstream documentation for more details. + ''; + }; + }; + + config = mkIf cfg.enable { + environment.etc = { + "trafficserver/cache.config".text = cfg.cache; + "trafficserver/hosting.config".text = cfg.hosting; + "trafficserver/parent.config".text = cfg.parent; + "trafficserver/plugin.config".text = mkPluginConfig cfg.plugins; + "trafficserver/records.config".text = mkRecordsConfig cfg.records; + "trafficserver/remap.config".text = cfg.remap; + "trafficserver/splitdns.config".text = cfg.splitDns; + "trafficserver/ssl_multicert.config".text = cfg.sslMulticert; + "trafficserver/storage.config".text = cfg.storage; + "trafficserver/volume.config".text = cfg.volume; + } // (mkYamlConf "ip_allow" cfg.ipAllow) + // (mkYamlConf "logging" cfg.logging) + // (mkYamlConf "sni" cfg.sni) + // (mkYamlConf "strategies" cfg.strategies); + + environment.systemPackages = [ pkgs.trafficserver ]; + systemd.packages = [ pkgs.trafficserver ]; + + # Traffic Server does privilege handling independently of systemd, and + # therefore should be started as root + systemd.services.trafficserver = { + enable = true; + wantedBy = [ "multi-user.target" ]; + }; + + # These directories can't be created by systemd because: + # + # 1. Traffic Servers starts as root and switches to an unprivileged user + # afterwards. The runtime directories defined below are assumed to be + # owned by that user. + # 2. The bin/trafficserver script assumes these directories exist. + systemd.tmpfiles.rules = [ + "d '/run/trafficserver' - ${user} ${group} - -" + "d '/var/cache/trafficserver' - ${user} ${group} - -" + "d '/var/lib/trafficserver' - ${user} ${group} - -" + "d '/var/log/trafficserver' - ${user} ${group} - -" + ]; + + services.trafficserver = { + records.proxy.config.admin.user_id = user; + records.proxy.config.body_factory.template_sets_dir = + "${pkgs.trafficserver}/etc/trafficserver/body_factory"; + }; + + users.users.trafficserver = { + description = "Apache Traffic Server"; + isSystemUser = true; + inherit group; + }; + users.groups.trafficserver = { }; + }; +}