at 22.05-pre 6.4 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.caddy; 7 vhostToConfig = vhostName: vhostAttrs: '' 8 ${vhostName} ${builtins.concatStringsSep " " vhostAttrs.serverAliases} { 9 ${vhostAttrs.extraConfig} 10 } 11 ''; 12 configFile = pkgs.writeText "Caddyfile" (builtins.concatStringsSep "\n" 13 ([ cfg.config ] ++ (mapAttrsToList vhostToConfig cfg.virtualHosts))); 14 15 formattedConfig = pkgs.runCommand "formattedCaddyFile" { } '' 16 ${cfg.package}/bin/caddy fmt ${configFile} > $out 17 ''; 18 19 tlsConfig = { 20 apps.tls.automation.policies = [{ 21 issuers = [{ 22 inherit (cfg) ca email; 23 module = "acme"; 24 }]; 25 }]; 26 }; 27 28 adaptedConfig = pkgs.runCommand "caddy-config-adapted.json" { } '' 29 ${cfg.package}/bin/caddy adapt \ 30 --config ${formattedConfig} --adapter ${cfg.adapter} > $out 31 ''; 32 tlsJSON = pkgs.writeText "tls.json" (builtins.toJSON tlsConfig); 33 34 # merge the TLS config options we expose with the ones originating in the Caddyfile 35 configJSON = 36 if cfg.ca != null then 37 let tlsConfigMerge = '' 38 {"apps": 39 {"tls": 40 {"automation": 41 {"policies": 42 (if .[0].apps.tls.automation.policies == .[1]?.apps.tls.automation.policies 43 then .[0].apps.tls.automation.policies 44 else (.[0].apps.tls.automation.policies + .[1]?.apps.tls.automation.policies) 45 end) 46 } 47 } 48 } 49 }''; 50 in 51 pkgs.runCommand "caddy-config.json" { } '' 52 ${pkgs.jq}/bin/jq -s '.[0] * ${tlsConfigMerge}' ${adaptedConfig} ${tlsJSON} > $out 53 '' 54 else 55 adaptedConfig; 56in 57{ 58 imports = [ 59 (mkRemovedOptionModule [ "services" "caddy" "agree" ] "this option is no longer necessary for Caddy 2") 60 ]; 61 62 options.services.caddy = { 63 enable = mkEnableOption "Caddy web server"; 64 65 config = mkOption { 66 default = ""; 67 example = '' 68 example.com { 69 encode gzip 70 log 71 root /srv/http 72 } 73 ''; 74 type = types.lines; 75 description = '' 76 Verbatim Caddyfile to use. 77 Caddy v2 supports multiple config formats via adapters (see <option>services.caddy.adapter</option>). 78 ''; 79 }; 80 81 virtualHosts = mkOption { 82 type = types.attrsOf (types.submodule (import ./vhost-options.nix { 83 inherit config lib; 84 })); 85 default = { }; 86 example = literalExpression '' 87 { 88 "hydra.example.com" = { 89 serverAliases = [ "www.hydra.example.com" ]; 90 extraConfig = '''''' 91 encode gzip 92 log 93 root /srv/http 94 ''''''; 95 }; 96 }; 97 ''; 98 description = "Declarative vhost config"; 99 }; 100 101 102 user = mkOption { 103 default = "caddy"; 104 type = types.str; 105 description = "User account under which caddy runs."; 106 }; 107 108 group = mkOption { 109 default = "caddy"; 110 type = types.str; 111 description = "Group account under which caddy runs."; 112 }; 113 114 adapter = mkOption { 115 default = "caddyfile"; 116 example = "nginx"; 117 type = types.str; 118 description = '' 119 Name of the config adapter to use. 120 See https://caddyserver.com/docs/config-adapters for the full list. 121 ''; 122 }; 123 124 resume = mkOption { 125 default = false; 126 type = types.bool; 127 description = '' 128 Use saved config, if any (and prefer over configuration passed with <option>services.caddy.config</option>). 129 ''; 130 }; 131 132 ca = mkOption { 133 default = "https://acme-v02.api.letsencrypt.org/directory"; 134 example = "https://acme-staging-v02.api.letsencrypt.org/directory"; 135 type = types.nullOr types.str; 136 description = '' 137 Certificate authority ACME server. The default (Let's Encrypt 138 production server) should be fine for most people. Set it to null if 139 you don't want to include any authority (or if you want to write a more 140 fine-graned configuration manually) 141 ''; 142 }; 143 144 email = mkOption { 145 default = ""; 146 type = types.str; 147 description = "Email address (for Let's Encrypt certificate)"; 148 }; 149 150 dataDir = mkOption { 151 default = "/var/lib/caddy"; 152 type = types.path; 153 description = '' 154 The data directory, for storing certificates. Before 17.09, this 155 would create a .caddy directory. With 17.09 the contents of the 156 .caddy directory are in the specified data directory instead. 157 158 Caddy v2 replaced CADDYPATH with XDG directories. 159 See https://caddyserver.com/docs/conventions#file-locations. 160 ''; 161 }; 162 163 package = mkOption { 164 default = pkgs.caddy; 165 defaultText = literalExpression "pkgs.caddy"; 166 type = types.package; 167 description = '' 168 Caddy package to use. 169 ''; 170 }; 171 }; 172 173 config = mkIf cfg.enable { 174 systemd.services.caddy = { 175 description = "Caddy web server"; 176 # upstream unit: https://github.com/caddyserver/dist/blob/master/init/caddy.service 177 after = [ "network-online.target" ]; 178 wants = [ "network-online.target" ]; # systemd-networkd-wait-online.service 179 wantedBy = [ "multi-user.target" ]; 180 startLimitIntervalSec = 14400; 181 startLimitBurst = 10; 182 serviceConfig = { 183 ExecStart = "${cfg.package}/bin/caddy run ${optionalString cfg.resume "--resume"} --config ${configJSON}"; 184 ExecReload = "${cfg.package}/bin/caddy reload --config ${configJSON}"; 185 Type = "simple"; 186 User = cfg.user; 187 Group = cfg.group; 188 Restart = "on-abnormal"; 189 AmbientCapabilities = "cap_net_bind_service"; 190 CapabilityBoundingSet = "cap_net_bind_service"; 191 NoNewPrivileges = true; 192 LimitNPROC = 512; 193 LimitNOFILE = 1048576; 194 PrivateTmp = true; 195 PrivateDevices = true; 196 ProtectHome = true; 197 ProtectSystem = "full"; 198 ReadWriteDirectories = cfg.dataDir; 199 KillMode = "mixed"; 200 KillSignal = "SIGQUIT"; 201 TimeoutStopSec = "5s"; 202 }; 203 }; 204 205 users.users = optionalAttrs (cfg.user == "caddy") { 206 caddy = { 207 group = cfg.group; 208 uid = config.ids.uids.caddy; 209 home = cfg.dataDir; 210 createHome = true; 211 }; 212 }; 213 214 users.groups = optionalAttrs (cfg.group == "caddy") { 215 caddy.gid = config.ids.gids.caddy; 216 }; 217 218 }; 219}