{ config, lib, ... }: let cfg = config.satellite.nginx; in { options.satellite.nginx = { domain = lib.mkOption { description = "Root domain to use as a default for configurations."; type = lib.types.str; }; at = lib.mkOption { description = "Per-subdomain nginx configuration"; type = lib.types.attrsOf (lib.types.submodule ({ name, config, ... }: { options.name = lib.mkOption { description = "Attribute name leading to this submodule"; type = lib.types.str; }; config.name = name; options.host = lib.mkOption { description = "Host to route requests from"; type = lib.types.str; default = "${name}.${cfg.domain}"; }; options.url = lib.mkOption { description = "External https url used to access this host"; type = lib.types.str; }; config.url = "https://${config.host}"; options.port = lib.mkOption { description = "Port to proxy requests to"; type = lib.types.nullOr lib.types.port; default = null; }; options.files = lib.mkOption { description = "Path to serve files from"; type = lib.types.nullOr lib.types.path; default = null; }; })); default = { }; }; }; config = { assertions = let assertSingleTarget = config: { assertion = (config.port == null) == (config.files != null); message = '' Precisely one of the options 'satellite.nginx.at.${config.name}.port' and 'satellite.nginx.at.${config.name}.files' must be specified. ''; }; in lib.mapAttrsToList (_: assertSingleTarget) cfg.at; services.nginx.virtualHosts = let mkNginxConfig = { host, port, files, ... }: { name = host; value = let extra = if port != null then { locations."/" = { proxyPass = "http://localhost:${toString port}"; proxyWebsockets = true; }; } else { root = files; }; in { enableACME = true; acmeRoot = null; forceSSL = true; } // extra; }; in lib.attrsets.mapAttrs' (_: mkNginxConfig) cfg.at; }; }