at 16.09-beta 12 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.nginx; 7 virtualHosts = mapAttrs (vhostName: vhostConfig: 8 vhostConfig // (optionalAttrs vhostConfig.enableACME { 9 sslCertificate = "/var/lib/acme/${vhostName}/fullchain.pem"; 10 sslCertificateKey = "/var/lib/acme/${vhostName}/key.pem"; 11 }) 12 ) cfg.virtualHosts; 13 14 configFile = pkgs.writeText "nginx.conf" '' 15 user ${cfg.user} ${cfg.group}; 16 error_log stderr; 17 daemon off; 18 19 ${cfg.config} 20 21 ${optionalString (cfg.httpConfig == "" && cfg.config == "") '' 22 events {} 23 24 http { 25 include ${cfg.package}/conf/mime.types; 26 include ${cfg.package}/conf/fastcgi.conf; 27 28 ${optionalString (cfg.recommendedOptimisation) '' 29 # optimisation 30 sendfile on; 31 tcp_nopush on; 32 tcp_nodelay on; 33 keepalive_timeout 65; 34 types_hash_max_size 2048; 35 ''} 36 37 ssl_protocols ${cfg.sslProtocols}; 38 ssl_ciphers ${cfg.sslCiphers}; 39 ${optionalString (cfg.sslDhparam != null) "ssl_dhparam ${cfg.sslDhparam};"} 40 41 ${optionalString (cfg.recommendedTlsSettings) '' 42 ssl_session_cache shared:SSL:42m; 43 ssl_session_timeout 23m; 44 ssl_ecdh_curve secp384r1; 45 ssl_prefer_server_ciphers on; 46 ssl_stapling on; 47 ssl_stapling_verify on; 48 ''} 49 50 ${optionalString (cfg.recommendedGzipSettings) '' 51 gzip on; 52 gzip_disable "msie6"; 53 gzip_proxied any; 54 gzip_comp_level 9; 55 gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; 56 ''} 57 58 ${optionalString (cfg.recommendedProxySettings) '' 59 proxy_set_header Host $host; 60 proxy_set_header X-Real-IP $remote_addr; 61 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 62 proxy_set_header X-Forwarded-Proto $scheme; 63 proxy_set_header X-Forwarded-Host $host; 64 proxy_set_header X-Forwarded-Server $host; 65 proxy_set_header Accept-Encoding ""; 66 67 proxy_redirect off; 68 proxy_connect_timeout 90; 69 proxy_send_timeout 90; 70 proxy_read_timeout 90; 71 proxy_http_version 1.0; 72 ''} 73 74 client_max_body_size ${cfg.clientMaxBodySize}; 75 76 server_tokens ${if cfg.serverTokens then "on" else "off"}; 77 78 ${vhosts} 79 80 ${optionalString cfg.statusPage '' 81 server { 82 listen 80; 83 listen [::]:80; 84 85 server_name localhost; 86 87 location /nginx_status { 88 stub_status on; 89 access_log off; 90 allow 127.0.0.1; 91 allow ::1; 92 deny all; 93 } 94 } 95 ''} 96 97 ${cfg.appendHttpConfig} 98 }''} 99 100 ${optionalString (cfg.httpConfig != "") '' 101 events {} 102 http { 103 include ${cfg.package}/conf/mime.types; 104 include ${cfg.package}/conf/fastcgi.conf; 105 ${cfg.httpConfig} 106 }''} 107 108 ${cfg.appendConfig} 109 ''; 110 111 vhosts = concatStringsSep "\n" (mapAttrsToList (serverName: vhost: 112 let 113 ssl = vhost.enableSSL || vhost.forceSSL; 114 port = if vhost.port != null then vhost.port else (if ssl then 443 else 80); 115 listenString = toString port + optionalString ssl " ssl http2" 116 + optionalString vhost.default " default"; 117 acmeLocation = optionalString vhost.enableACME '' 118 location /.well-known/acme-challenge { 119 try_files $uri @acme-fallback; 120 root ${vhost.acmeRoot}; 121 auth_basic off; 122 } 123 location @acme-fallback { 124 auth_basic off; 125 proxy_pass http://${vhost.acmeFallbackHost}; 126 } 127 ''; 128 in '' 129 ${optionalString vhost.forceSSL '' 130 server { 131 listen 80 ${optionalString vhost.default "default"}; 132 listen [::]:80 ${optionalString vhost.default "default"}; 133 134 server_name ${serverName} ${concatStringsSep " " vhost.serverAliases}; 135 ${acmeLocation} 136 location / { 137 return 301 https://$host${optionalString (port != 443) ":${port}"}$request_uri; 138 } 139 } 140 ''} 141 142 server { 143 listen ${listenString}; 144 listen [::]:${listenString}; 145 146 server_name ${serverName} ${concatStringsSep " " vhost.serverAliases}; 147 ${acmeLocation} 148 ${optionalString (vhost.root != null) "root ${vhost.root};"} 149 ${optionalString (vhost.globalRedirect != null) '' 150 return 301 http${optionalString ssl "s"}://${vhost.globalRedirect}$request_uri; 151 ''} 152 ${optionalString ssl '' 153 ssl_certificate ${vhost.sslCertificate}; 154 ssl_certificate_key ${vhost.sslCertificateKey}; 155 ''} 156 157 ${optionalString (vhost.basicAuth != {}) (mkBasicAuth serverName vhost.basicAuth)} 158 159 ${mkLocations vhost.locations} 160 161 ${vhost.extraConfig} 162 } 163 '' 164 ) virtualHosts); 165 mkLocations = locations: concatStringsSep "\n" (mapAttrsToList (location: config: '' 166 location ${location} { 167 ${optionalString (config.proxyPass != null) "proxy_pass ${config.proxyPass};"} 168 ${optionalString (config.index != null) "index ${config.index};"} 169 ${optionalString (config.tryFiles != null) "try_files ${config.tryFiles};"} 170 ${optionalString (config.root != null) "root ${config.root};"} 171 ${config.extraConfig} 172 } 173 '') locations); 174 mkBasicAuth = serverName: authDef: let 175 htpasswdFile = pkgs.writeText "${serverName}.htpasswd" ( 176 concatStringsSep "\n" (mapAttrsToList (user: password: '' 177 ${user}:{PLAIN}${password} 178 '') authDef) 179 ); 180 in '' 181 auth_basic secured; 182 auth_basic_user_file ${htpasswdFile}; 183 ''; 184in 185 186{ 187 options = { 188 services.nginx = { 189 enable = mkEnableOption "Nginx Web Server"; 190 191 statusPage = mkOption { 192 default = false; 193 type = types.bool; 194 description = " 195 Enable status page reachable from localhost on http://127.0.0.1/nginx_status. 196 "; 197 }; 198 199 recommendedTlsSettings = mkOption { 200 default = false; 201 type = types.bool; 202 description = " 203 Enable recommended TLS settings. 204 "; 205 }; 206 207 recommendedOptimisation = mkOption { 208 default = false; 209 type = types.bool; 210 description = " 211 Enable recommended optimisation settings. 212 "; 213 }; 214 215 recommendedGzipSettings = mkOption { 216 default = false; 217 type = types.bool; 218 description = " 219 Enable recommended gzip settings. 220 "; 221 }; 222 223 recommendedProxySettings = mkOption { 224 default = false; 225 type = types.bool; 226 description = " 227 Enable recommended proxy settings. 228 "; 229 }; 230 231 package = mkOption { 232 default = pkgs.nginx; 233 defaultText = "pkgs.nginx"; 234 type = types.package; 235 description = " 236 Nginx package to use. 237 "; 238 }; 239 240 config = mkOption { 241 default = ""; 242 description = " 243 Verbatim nginx.conf configuration. 244 This is mutually exclusive with the structured configuration 245 via virtualHosts and the recommendedXyzSettings configuration 246 options. See appendConfig for appending to the generated http block. 247 "; 248 }; 249 250 appendConfig = mkOption { 251 type = types.lines; 252 default = ""; 253 description = '' 254 Configuration lines appended to the generated Nginx 255 configuration file. Commonly used by different modules 256 providing http snippets. <option>appendConfig</option> 257 can be specified more than once and it's value will be 258 concatenated (contrary to <option>config</option> which 259 can be set only once). 260 ''; 261 }; 262 263 httpConfig = mkOption { 264 type = types.lines; 265 default = ""; 266 description = " 267 Configuration lines to be set inside the http block. 268 This is mutually exclusive with the structured configuration 269 via virtualHosts and the recommendedXyzSettings configuration 270 options. See appendHttpConfig for appending to the generated http block. 271 "; 272 }; 273 274 appendHttpConfig = mkOption { 275 type = types.lines; 276 default = ""; 277 description = " 278 Configuration lines to be appended to the generated http block. 279 This is mutually exclusive with using config and httpConfig for 280 specifying the whole http block verbatim. 281 "; 282 }; 283 284 stateDir = mkOption { 285 default = "/var/spool/nginx"; 286 description = " 287 Directory holding all state for nginx to run. 288 "; 289 }; 290 291 user = mkOption { 292 type = types.str; 293 default = "nginx"; 294 description = "User account under which nginx runs."; 295 }; 296 297 group = mkOption { 298 type = types.str; 299 default = "nginx"; 300 description = "Group account under which nginx runs."; 301 }; 302 303 serverTokens = mkOption { 304 type = types.bool; 305 default = false; 306 description = "Show nginx version in headers and error pages."; 307 }; 308 309 clientMaxBodySize = mkOption { 310 type = types.string; 311 default = "10m"; 312 description = "Set nginx global client_max_body_size."; 313 }; 314 315 sslCiphers = mkOption { 316 type = types.str; 317 default = "EECDH+aRSA+AESGCM:EDH+aRSA:EECDH+aRSA:+AES256:+AES128:+SHA1:!CAMELLIA:!SEED:!3DES:!DES:!RC4:!eNULL"; 318 description = "Ciphers to choose from when negotiating tls handshakes."; 319 }; 320 321 sslProtocols = mkOption { 322 type = types.str; 323 default = "TLSv1.2"; 324 example = "TLSv1 TLSv1.1 TLSv1.2"; 325 description = "Allowed TLS protocol versions."; 326 }; 327 328 sslDhparam = mkOption { 329 type = types.nullOr types.path; 330 default = null; 331 example = "/path/to/dhparams.pem"; 332 description = "Path to DH parameters file."; 333 }; 334 335 virtualHosts = mkOption { 336 type = types.attrsOf (types.submodule (import ./vhost-options.nix { 337 inherit lib; 338 })); 339 default = { 340 localhost = {}; 341 }; 342 example = literalExample '' 343 { 344 "hydra.example.com" = { 345 forceSSL = true; 346 enableACME = true; 347 locations."/" = { 348 proxyPass = "http://localhost:3000"; 349 }; 350 }; 351 }; 352 ''; 353 description = "Declarative vhost config"; 354 }; 355 }; 356 }; 357 358 config = mkIf cfg.enable { 359 # TODO: test user supplied config file pases syntax test 360 361 systemd.services.nginx = { 362 description = "Nginx Web Server"; 363 after = [ "network.target" ]; 364 wantedBy = [ "multi-user.target" ]; 365 preStart = 366 '' 367 mkdir -p ${cfg.stateDir}/logs 368 chmod 700 ${cfg.stateDir} 369 chown -R ${cfg.user}:${cfg.group} ${cfg.stateDir} 370 ''; 371 serviceConfig = { 372 ExecStart = "${cfg.package}/bin/nginx -c ${configFile} -p ${cfg.stateDir}"; 373 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; 374 Restart = "always"; 375 RestartSec = "10s"; 376 StartLimitInterval = "1min"; 377 }; 378 }; 379 380 security.acme.certs = filterAttrs (n: v: v != {}) ( 381 mapAttrs (vhostName: vhostConfig: 382 optionalAttrs vhostConfig.enableACME { 383 webroot = vhostConfig.acmeRoot; 384 extraDomains = genAttrs vhostConfig.serverAliases (alias: null); 385 } 386 ) virtualHosts 387 ); 388 389 users.extraUsers = optionalAttrs (cfg.user == "nginx") (singleton 390 { name = "nginx"; 391 group = cfg.group; 392 uid = config.ids.uids.nginx; 393 }); 394 395 users.extraGroups = optionalAttrs (cfg.group == "nginx") (singleton 396 { name = "nginx"; 397 gid = config.ids.gids.nginx; 398 }); 399 }; 400}