diff --git a/hosts/disk/site.nix b/hosts/disk/site.nix new file mode 100644 index 0000000..b7fe2c7 --- /dev/null +++ b/hosts/disk/site.nix @@ -0,0 +1,7 @@ +{ self, ... }: { + imports = [ + (self + /modules/acme) + (self + /modules/nginx.nix) + (self + /modules/site.nix) + ]; +} diff --git a/hosts/disk/site6.nix b/hosts/disk/site6.nix deleted file mode 100644 index 5bf000c..0000000 --- a/hosts/disk/site6.nix +++ /dev/null @@ -1,7 +0,0 @@ -{ self, ... }: { - imports = [ - # (self + /modules/acme) - # (self + /modules/nginx.nix) - # (self + /modules/site.nix) - ]; -} diff --git a/modules/acme/default.nix b/modules/acme/default.nix index 9ddd324..a802747 100644 --- a/modules/acme/default.nix +++ b/modules/acme/default.nix @@ -2,10 +2,12 @@ inherit (config.networking) domain; inherit (lib) mkValue; in { - options.acmeGroup = mkValue "nginx"; + options.acmeUsers = mkValue []; config.secrets.acmeEnvironment.file = ./environment.age; + config.users.groups.acme.members = config.acmeUsers; + config.security.acme = { acceptTerms = true; @@ -18,7 +20,7 @@ in { certs.${domain} = { extraDomainNames = [ "*.${domain}" ]; - group = config.acmeGroup; + group = "acme"; }; }; } diff --git a/modules/mail/default.nix b/modules/mail/default.nix index 295301e..2d68cdc 100644 --- a/modules/mail/default.nix +++ b/modules/mail/default.nix @@ -1,6 +1,6 @@ { self, config, lib, ... }: let - inherit (lib) const enabled genAttrs head mkDefault; inherit (config.networking) domain; + inherit (lib) const enabled genAttrs head mkDefault; in { imports = [(self + /modules/acme)]; @@ -14,7 +14,7 @@ in { paths = [ config.mailserver.dkimKeyDirectory config.mailserver.mailDirectory ]; }; - acmeGroup = "mail"; + acmeUsers = [ "mail" ]; mailserver = enabled { domains = mkDefault [ domain ]; diff --git a/modules/nginx.nix b/modules/nginx.nix new file mode 100644 index 0000000..cb4f287 --- /dev/null +++ b/modules/nginx.nix @@ -0,0 +1,56 @@ +{ config, lib, pkgs, ... }: let + inherit (lib) enabled mkConst; +in { + options.nginxSslTemplate = mkConst { + forceSSL = true; + quic = true; + useACMEHost = config.networking.domain; + }; + + options.nginxHeaders = mkConst '' + add_header Strict-Transport-Security $hsts_header; + + add_header Content-Security-Policy "script-src 'self'; object-src 'none'; base-uri 'none';" always; + + add_header Referrer-Policy no-referrer; + + add_header X-Frame-Options DENY; + + add_header X-Content-Type-Options nosniff; + ''; + + config.networking.firewall = { + allowedTCPPorts = [ 443 80 ]; + allowedUDPPorts = [ 443 ]; + }; + + config.services.prometheus.exporters.nginx = enabled { + listenAddress = "[::]"; + }; + + config.acmeUsers = [ "nginx" ]; + + config.services.nginx = enabled { + package = pkgs.nginxQuic; + + statusPage = true; + + recommendedBrotliSettings = true; + recommendedGzipSettings = true; + recommendedZstdSettings = true; + + recommendedOptimisation = true; + recommendedProxySettings = true; + recommendedTlsSettings = true; + + commonHttpConfig = '' + map $scheme $hsts_header { + https "max-age=31536000; includeSubdomains; preload"; + } + + ${config.nginxHeaders} + + proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict"; + ''; + }; +} diff --git a/modules/site.nix b/modules/site.nix new file mode 100644 index 0000000..9416f33 --- /dev/null +++ b/modules/site.nix @@ -0,0 +1,60 @@ +{ config, lib, ... }: let + inherit (config.networking) domain; + inherit (lib) enabled merge; + + sitePath = "/var/www/site"; + + notFoundLocationConfig = { + extraConfig = "error_page 404 /404.html;"; + locations."/404".extraConfig = "internal;"; + }; +in { + services.nginx = enabled { + appendHttpConfig = '' + map $http_origin $allow_origin { + ~^https://.+\.${domain}$ $http_origin; + } + + map $http_origin $allow_methods { + ~^https://.+\.${domain}$ "GET, HEAD, OPTIONS"; + } + ''; + + virtualHosts.${domain} = merge config.nginxSslTemplate notFoundLocationConfig { + root = sitePath; + + locations."/".tryFiles = "$uri $uri.html $uri/index.html =404"; + + locations."/assets/".extraConfig = let + nginxHeaders' = '' + add_header Access-Control-Allow-Origin $allow_origin; + add_header Access-Control-Allow-Methods $allow_methods; + ''; + in '' + ${config.nginxHeaders} + ${nginxHeaders'} + + if ($request_method = OPTIONS) { + ${config.nginxHeaders} + ${nginxHeaders'} + add_header Content-Type text/plain; + add_header Content-Length 0; + return 204; + } + + expires 24h; + ''; + }; + + virtualHosts."www.${domain}" = merge config.nginxSslTemplate { + locations."/".extraConfig = "return 301 https://${domain}$request_uri;"; + }; + + virtualHosts._ = merge config.nginxSslTemplate notFoundLocationConfig { + root = sitePath; + + locations."/".extraConfig = "return 404;"; + locations."/assets/".extraConfig = "return 301 https://${domain}$request_uri;"; + }; + }; +}