{ name, hostInfo, dsl, lib, containers, ... }: let inherit (dsl) mkContainer collectContainers; 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; 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; }; discovered = collectContainers "nginx" containers; discoveredVhosts = map ( { container, data }: { inherit (data) domain; } // lib.optionalAttrs (data ? port) { upstream = up container data.port; } // lib.optionalAttrs (data ? websockets) { inherit (data) websockets; } // lib.optionalAttrs (data ? redirect) { inherit (data) redirect; } // lib.optionalAttrs (data ? listenHttp) { inherit (data) listenHttp; } // lib.optionalAttrs (data ? extraLocations) { inherit (data) extraLocations; } ) discovered; extraVhosts = [ { domain = "utils.floweytf.com"; redirect = false; listenHttp = true; extraLocations = { "/static/" = { alias = "/usr/share/nginx/static/"; extraConfig = "autoindex on;"; }; }; } ]; upstreamContainers = map (e: e.container) (lib.filter ({ data, ... }: data ? port) discovered); in let setup = mkNginxConfig { email = "floweydev@gmail.com"; resolver = hostAddr; vhosts = discoveredVhosts ++ extraVhosts; }; c = mkContainer { inherit name; net.access = setup.access ++ [ { to = "@internet"; } ]; svc.autoStart = true; svc.depends = upstreamContainers; 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