{ name, hostInfo, dsl, lib, containers, ... }: let inherit (dsl) mkContainer; inherit (hostInfo.paths) data; mkNginxConfig = { email, vhosts, resolver, }: let mkVhostEntries = { domain, upstream ? null, websockets ? true, redirect ? true, listenHttp ? false, extraLocations ? { }, }: let httpsListen = { addr = "0.0.0.0"; port = 443; ssl = true; }; httpListen = { addr = "0.0.0.0"; port = 80; ssl = false; }; defaultLoc = lib.optionalAttrs (upstream != null) { "/" = { proxyWebsockets = websockets; # avoid startup error when containers aren't registered yet # proxyPass is left unset for that reason, so recommendedProxySettings # won't auto-include its headers here - set them manually to match extraConfig = '' set $backend "${upstream.container.name}:${toString upstream.port}"; proxy_pass http://$backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Server $hostname; ''; }; }; acmePrimaryOnMain = !redirect && listenHttp; mainVhost = lib.nameValuePair domain ( { listen = [ httpsListen ] ++ lib.optional listenHttp httpListen; locations = defaultLoc // extraLocations; } // ( if acmePrimaryOnMain then { addSSL = true; enableACME = true; } else { onlySSL = true; useACMEHost = domain; } ) ); redirectVhost = lib.nameValuePair "${domain}-redirect" { serverName = domain; enableACME = true; listen = [ httpListen ]; locations = { "/" = { return = "301 https://$host$request_uri"; }; }; }; in [ mainVhost ] ++ lib.optional redirect redirectVhost; in { access = map (v: { to = v.upstream.container.name; tcp = [ v.upstream.port ]; }) (lib.filter (v: v.upstream or null != null) vhosts); containerConfig = { lib, config, ... }: { services.nginx = { enable = true; recommendedTlsSettings = true; recommendedOptimisation = true; recommendedGzipSettings = true; recommendedProxySettings = true; clientMaxBodySize = "100M"; appendHttpConfig = '' access_log syslog:server=unix:/dev/log,tag=nginx combined; ''; resolver = { addresses = [ resolver ]; ipv6 = false; valid = "30s"; }; virtualHosts = lib.listToAttrs (lib.concatLists (map mkVhostEntries vhosts)) // { "_" = { listen = [ { addr = "0.0.0.0"; port = 443; extraParameters = [ "ssl" "default_server" ]; } { addr = "0.0.0.0"; port = 80; extraParameters = [ "default_server" ]; } ]; extraConfig = "ssl_reject_handshake on;"; locations."/".return = "404"; }; }; }; security.acme = { acceptTerms = true; defaults.email = email; }; systemd.services = lib.mapAttrs' ( cert: _: lib.nameValuePair "acme-${cert}" { after = [ "container-ready.target" ]; wants = [ "container-ready.target" ]; } ) config.security.acme.certs // { nginx.serviceConfig.FailureAction = "exit-force"; }; }; }; up = container: port: { inherit container port; }; nginxSetup = hostAddr: mkNginxConfig { email = "floweydev@gmail.com"; resolver = hostAddr; vhosts = [ { domain = "devenv.floweytf.com"; upstream = up containers.devenv 8080; } { domain = "maven.floweytf.com"; upstream = up containers.maven 8080; } { domain = "git.floweytf.com"; upstream = up containers.git 3000; } { domain = "utils.floweytf.com"; redirect = false; listenHttp = true; extraLocations = { "/static/" = { alias = "/usr/share/nginx/static/"; extraConfig = "autoindex on;"; }; }; } { # no upstream, vhost only exists for certs domain = "mail.floweytf.com"; } ]; }; in let setup = nginxSetup hostAddr; c = mkContainer { inherit name; net.access = setup.access ++ [ { to = "@internet"; } ]; svc.autoStart = true; svc.depends = lib.attrVals [ "devenv" "maven" "git" ] containers; net.forward.tcp = [ 80 443 ]; resources.fs."/var/lib/acme" = { hostPath = "${data}/core/nginx/acme"; isReadOnly = false; }; inherit (setup) containerConfig; }; inherit (c) hostAddr; in c