diff --git a/modules/services/httpapi/default.nix b/modules/services/httpapi/default.nix new file mode 100644 index 00000000..dce9ca36 --- /dev/null +++ b/modules/services/httpapi/default.nix @@ -0,0 +1,25 @@ +inputs: +{ + options.nixos.services.httpua = let inherit (inputs.lib) mkOption types; in + { + enable = mkOption { type = types.bool; default = false; }; + hostname = mkOption { type = types.nonEmptyStr; default = "ua.chn.moe"; }; + }; + config = + let + inherit (inputs.config.nixos.services) httpua; + inherit (inputs.lib) mkIf; + inherit (builtins) toString; + in mkIf httpua.enable + { + nixos.services = + { + phpfpm.instances.httpua = {}; + nginx.http.${httpua.hostname}.php = + { + root = toString ./.; + fastcgiPass = inputs.config.nixos.services.phpfpm.instances.httpua.fastcgi; + }; + }; + }; +} diff --git a/modules/services/nginx/default.nix b/modules/services/nginx/default.nix index b8577eec..6576f152 100644 --- a/modules/services/nginx/default.nix +++ b/modules/services/nginx/default.nix @@ -79,7 +79,15 @@ inputs: }; root = mkOption { type = types.nullOr types.nonEmptyStr; default = null; }; index = mkOption { type = types.nullOr (types.nonEmptyListOf types.nonEmptyStr); default = null; }; - detectAuth = mkOption { type = types.nullOr (types.nonEmptyListOf types.nonEmptyStr); default = null; }; + detectAuth = mkOption + { + type = types.nullOr (types.submodule { options = + { + text = mkOption { type = types.nonEmptyStr; default = "Restricted Content"; }; + users = types.nonEmptyListOf types.nonEmptyStr; + };}); + default = null; + }; rewriteHttps = mkOption { type = types.bool; default = true; }; }; listen = mkOption @@ -103,7 +111,15 @@ inputs: # should be set to non null value if global root is null root = mkOption { type = types.nullOr types.nonEmptyStr; default = null; }; # htpasswd -n username - detectAuth = mkOption { type = types.nullOr (types.nonEmptyListOf types.nonEmptyStr); default = null; }; + detectAuth = mkOption + { + type = types.nullOr (types.submodule { options = + { + text = mkOption { type = types.nonEmptyStr; default = "Restricted Content"; }; + users = types.nonEmptyListOf types.nonEmptyStr; + };}); + default = null; + }; }; in { @@ -144,7 +160,14 @@ inputs: };}); default = null; }; - return = mkOption { type = types.nullOr types.nonEmptyStr; default = null; }; + return = mkOption + { + type = types.nullOr (types.submodule { options = + { + return = mkOption { type = types.nonEmptyStr; }; + };}); + default = null; + }; cgi = mkOption { type = types.nullOr (types.submodule { options = @@ -414,7 +437,7 @@ inputs: (filter (site: site.value.rewriteHttps or false) (attrsToList nginx.streamProxy.map))); }; } - # https + # https assertions { # only one type should be specified in each location assertions = @@ -445,265 +468,264 @@ inputs: (attrsToList site.value.location))) (filter (site: site.value.global.root == null) (attrsToList nginx.https))))) ); - services = - { - nginx.virtualHosts = listToAttrs (map + } + # https + ( + let + # merge different types of locations + sites = map (site: { - name = site.value.global.configName; + inherit (site) name; value = { - serverName = site.name; - root = mkIf (site.value.global.root != null) site.value.global.root; - basicAuthFile = mkIf (site.value.global.detectAuth != null) - inputs.config.sops.templates."nginx/templates/detectAuth/${escapeURL site.name}-global".path; - extraConfig = mkIf (site.value.global.index != null) - "index ${concatStringsSep " " site.value.global.index};"; - listen = map - (listen: + inherit (site.value) global; + listens = attrValues site.value.listen; + locations = map + (location: { - addr = if listen.value.proxyProtocol then "0.0.0.0" else "127.0.0.1"; - port = with nginx.global; httpsPort - + (if listen.value.http2 then httpsPortShift.http2 else 0) - + (if listen.value.proxyProtocol then httpsPortShift.proxyProtocol else 0); - ssl = true; - # TODO: use proxy_protocol in 23.11 - extraParameters = - (if listen.value.proxyProtocol then [ "proxy_protocol" ] else []) - ++ (if listen.value.http2 then [ "http2" ] else []); + inherit (location) name; + value = + let _ = builtins.head (filter (type: type.value != null) (attrsToList location.value)); + in _.value // { type = _.name; }; }) - (attrsToList site.value.listen); - # do not automatically add http2 listen - http2 = false; - onlySSL = true; - # TODO: disable well-known in 23.11 - useACMEHost = site.name; - locations = listToAttrs (map + (attrsToList site.value.location); + }; + }) + (attrsToList nginx.https); + in + { + services = + { + nginx.virtualHosts = listToAttrs (map + (site: + { + name = site.value.global.configName; + value = + { + serverName = site.name; + root = mkIf (site.value.global.root != null) site.value.global.root; + basicAuthFile = mkIf (site.value.global.detectAuth != null) + inputs.config.sops.templates."nginx/templates/detectAuth/${escapeURL site.name}-global".path; + extraConfig = concatStringsSep "\n" + ( + ( + let inherit (site.value.global) index; in + if (index != null) then [ "index ${concatStringsSep " " index};" ] else [] + ) + ++ ( + let inherit (site.value.global) detectAuth; in + if (detectAuth != null) then [ ''auth_basic "${detectAuth.text}"'' ] else [] + ) + ); + listen = map + (listen: + { + addr = if listen.proxyProtocol then "0.0.0.0" else "127.0.0.1"; + port = with nginx.global; httpsPort + + (if listen.http2 then httpsPortShift.http2 else 0) + + (if listen.proxyProtocol then httpsPortShift.proxyProtocol else 0); + ssl = true; + # TODO: use proxy_protocol in 23.11 + extraParameters = + (if listen.proxyProtocol then [ "proxy_protocol" ] else []) + ++ (if listen.http2 then [ "http2" ] else []); + }) + site.value.listens; + # do not automatically add http2 listen + http2 = false; + onlySSL = true; + # TODO: disable well-known in 23.11 + useACMEHost = site.name; + locations = listToAttrs (map (location: { inherit (location) name; value = { - basicAuthFile = - let - detectAuthList = filter - (detectAuth: detectAuth != null) - (map - (type: location.value.${type}.detectAuth or null) - nginx.global.httpsLocationTypes); - in mkIf (detectAuthList != []) - inputs.config.sops.templates - ."nginx/templates/detectAuth/${escapeURL site.name}/${escapeURL location.name}".path; - root = - let - rootList = filter - (root: root != null) - (map - (type: location.value.${type}.root or null) - nginx.global.httpsLocationTypes); - in mkIf (rootList != []) - (builtins.head rootList); + basicAuthFile = mkIf (location.value.detectAuth or null != null) + inputs.config.sops.templates + ."nginx/templates/detectAuth/${escapeURL site.name}/${escapeURL location.name}".path; + root = mkIf (location.value.root or null != null) location.value.root; } - // ( - if (location.value.proxy != null) then + // { + proxy = { - proxyPass = location.value.proxy.upstream; - proxyWebsockets = location.value.proxy.websocket; + proxyPass = location.value.upstream; + proxyWebsockets = location.value.websocket; recommendedProxySettings = false; recommendedProxySettingsNoHost = true; extraConfig = concatStringsSep "\n" ( (map (header: ''proxy_set_header ${header.name} "${header.value}";'') - (attrsToList location.value.proxy.setHeaders)) + (attrsToList location.value.setHeaders)) ++ ( - if location.value.proxy.detectAuth != null || site.value.global.detectAuth != null + if location.value.detectAuth != null || site.value.global.detectAuth != null then [ "proxy_hide_header Authorization;" ] else [] ) ++ ( - if location.value.proxy.addAuth != null then - let authFile = "nginx/templates/addAuth/${location.value.proxy.addAuth}"; + if location.value.addAuth != null then + let authFile = "nginx/templates/addAuth/${location.value.addAuth}"; in [ "include ${inputs.config.sops.templates.${authFile}.path};" ] else []) ); - } - else if (location.value.static != null) then + }; + static = { - index = mkIf (location.value.static.index != []) - (concatStringsSep " " location.value.static.index); - tryFiles = mkIf (location.value.static.tryFiles != []) - (concatStringsSep " " location.value.static.tryFiles); - } - else if (location.value.php != null) then - { - extraConfig = - '' - fastcgi_pass ${location.value.php.fastcgiPass}; - fastcgi_split_path_info ^(.+\.php)(/.*)$; - fastcgi_param PATH_INFO $fastcgi_path_info; - include ${inputs.config.services.nginx.package}/conf/fastcgi.conf; - ''; - } - else if (location.value.return != null) then - { - return = location.value.return; - } - else if (location.value.cgi != null) then - { - extraConfig = - '' - include ${inputs.config.services.nginx.package}/conf/fastcgi.conf; - fastcgi_pass unix:${inputs.config.services.fcgiwrap.socketAddress}; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - ''; - } - else {} - ); + index = mkIf (location.value.index != []) (concatStringsSep " " location.value.index); + tryFiles = mkIf (location.value.tryFiles != []) (concatStringsSep " " location.value.tryFiles); + }; + php.extraConfig = + '' + fastcgi_pass ${location.value.fastcgiPass}; + fastcgi_split_path_info ^(.+\.php)(/.*)$; + fastcgi_param PATH_INFO $fastcgi_path_info; + include ${inputs.config.services.nginx.package}/conf/fastcgi.conf; + ''; + return.return = location.value.return; + cgi.extraConfig = + '' + include ${inputs.config.services.nginx.package}/conf/fastcgi.conf; + fastcgi_pass unix:${inputs.config.services.fcgiwrap.socketAddress}; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + ''; + }.${location.value.type}; }) - (attrsToList site.value.location)); - }; - }) - (attrsToList nginx.https)); - fcgiwrap = mkIf - ( - filter (site: site != []) (map - (site: filter (location: location.value.cgi != null) (attrsToList site.value.location)) - (attrsToList nginx.https)) - != [] - ) - { - enable = true; - user = inputs.config.users.users.nginx.name; - group = inputs.config.users.users.nginx.group; - }; - }; - nixos.services = - { - nginx = - let - # { name = domain; value = listen = { http2 = xxx, proxyProtocol = xxx, addToTransparentProxy = true }; } - listens = filter - (site: site.value.addToTransparentProxy) - (concatLists (map - (site: map - (listen: { inherit (site) name; inherit (listen) value; }) - (attrsToList site.value.listen)) - (attrsToList nginx.https))); - in - { - transparentProxy.map = listToAttrs (map - (site: - { - inherit (site) name; - value = with nginx.global; httpsPort + (if site.value.http2 then httpsPortShift.http2 else 0); - }) - (filter (listen: !listen.value.proxyProtocol) listens)); - streamProxy.map = listToAttrs (map - (site: - { - inherit (site) name; - value = - { - upstream.port = with nginx.global; httpsPort + httpsPortShift.proxyProtocol - + (if site.value.http2 then httpsPortShift.http2 else 0); - proxyProtocol = true; - rewriteHttps = mkDefault false; - }; - }) - (filter (listen: listen.value.proxyProtocol) listens)); - http = listToAttrs (map - (site: { inherit (site) name; value.rewriteHttps = {}; }) - (filter (site: site.value.global.rewriteHttps) (attrsToList nginx.https))); - }; - acme = - { - enable = true; - cert = listToAttrs (map - (site: { inherit (site) name; value.group = inputs.config.services.nginx.group; }) - (attrsToList nginx.https)); - }; - }; - sops = - let - locations = + site.value.locations); + }; + }) + sites); + fcgiwrap = mkIf ( - (concatLists (map + filter (site: site != []) (map + (site: filter (location: location.value.type == "cgi") site.value.locations) + sites) + != [] + ) + { + enable = true; + user = inputs.config.users.users.nginx.name; + group = inputs.config.users.users.nginx.group; + }; + }; + nixos.services = + { + nginx = + let + # { name = domain; value = listen = { http2 = xxx, proxyProtocol = xxx }; } + listens = filter + (listen: listen.value.addToTransparentProxy) + (concatLists (map + (site: map + (listen: { inherit (site) name; value = listen; }) + site.value.listens) + sites)); + in + { + transparentProxy.map = listToAttrs (map + (site: + { + inherit (site) name; + value = with nginx.global; httpsPort + (if site.value.http2 then httpsPortShift.http2 else 0); + }) + (filter (listen: !listen.value.proxyProtocol) listens)); + streamProxy.map = listToAttrs (map + (site: + { + inherit (site) name; + value = + { + upstream.port = with nginx.global; httpsPort + httpsPortShift.proxyProtocol + + (if site.value.http2 then httpsPortShift.http2 else 0); + proxyProtocol = true; + rewriteHttps = mkDefault false; + }; + }) + (filter (listen: listen.value.proxyProtocol) listens)); + http = listToAttrs (map + (site: { inherit (site) name; value.rewriteHttps = {}; }) + (filter (site: site.value.global.rewriteHttps) sites)); + }; + acme = + { + enable = true; + cert = listToAttrs (map + (site: { inherit (site) name; value.group = inputs.config.services.nginx.group; }) + sites); + }; + }; + sops = + let + detectAuthUsers = concatLists (map + (site: + ( + (map + (location: + { + name = "${escapeURL site.name}/${escapeURL location.name}"; + value = location.value.detectAuth.users; + }) + (filter (location: location.value.detectAuth or null != null) site.value.locations)) + ++ (if site.value.global.detectAuth != null then + [ { name = "${escapeURL site.name}-global"; value = site.value.global.detectAuth.users; } ] + else []) + )) + sites); + addAuth = concatLists (map (site: map (location: { - domain = site.name; - location = location.name; - detectAuth = concatLists (map - (type: - if !(location.value.${type} ? detectAuth) || (location.value.${type}.detectAuth == null) - then [] - else location.value.${type}.detectAuth - ) - nginx.global.httpsLocationTypes); - addAuth = location.value.proxy.addAuth or null; + name = "${escapeURL site.name}/${escapeURL location.name}"; + value = location.value.addAuth; }) - (attrsToList site.value.location)) - (attrsToList nginx.https))) - ++ (map - (site: - { - domain = site.name; - detectAuth = if site.value.global.detectAuth == null then [] else site.value.global.detectAuth; - addAuth = null; - }) - (attrsToList nginx.https)) - ); - in - { - templates = listToAttrs - ( - (map - (location: - { - name = - if (location ? location) then - "nginx/templates/detectAuth/${escapeURL location.domain}/${escapeURL location.location}" - else - "nginx/templates/detectAuth/${escapeURL location.domain}-global"; - value = + (filter (location: location.value.addAuth or null != null) site.value.locations) + ) + sites); + in + { + templates = listToAttrs + ( + (map + (detectAuth: { - owner = inputs.config.users.users.nginx.name; - content = concatStringsSep "\n" (map - (secret: inputs.config.sops.placeholder."nginx/detectAuth/${secret}") - location.detectAuth); - }; - }) - (filter (location: location.detectAuth != []) locations)) - ++ (map - (location: - { - name = "nginx/templates/addAuth/${escapeURL location.domain}/${escapeURL location.location}"; - value = + name = "nginx/templates/detectAuth/${detectAuth.name}"; + value = + { + owner = inputs.config.users.users.nginx.name; + content = concatStringsSep "\n" (map + (user: "${user}:{PLAIN}${inputs.config.sops.placeholder."nginx/detectAuth/${user}"}") + detectAuth.value); + }; + }) + detectAuthUsers) + ++ (map + (addAuth: { - owner = inputs.config.users.users.nginx.name; - content = - let placeholder = inputs.config.sops.placeholder."nginx/addAuth/${location.addAuth}"; - in ''proxy_set_header Authorization "Basic ${placeholder}";''; - }; - }) - (filter (location: (location.addAuth or null) != null) locations)) - ); - secrets = listToAttrs - ( - (map - (secret: { name = "nginx/detectAuth/${secret}"; value = {}; }) - (inputs.lib.unique (concatLists (map - (location: if location.detectAuth == null then [] else location.detectAuth) - locations)))) - ++ (map - (secret: { name = "nginx/addAuth/${secret}"; value = {}; }) - (inputs.lib.unique (filter - (secret: secret != null) - (map (location: location.addAuth) locations)))) - ); - }; - } + name = "nginx/templates/addAuth/${addAuth.name}"; + value = + { + owner = inputs.config.users.users.nginx.name; + content = + let placeholder = inputs.config.sops.placeholder."nginx/addAuth/${addAuth.value}"; + in ''proxy_set_header Authorization "Basic ${placeholder}";''; + }; + }) + addAuth) + ); + secrets = listToAttrs + ( + (map + (secret: { name = "nginx/detectAuth/${secret}"; value = {}; }) + (inputs.lib.unique (concatLists (map (detectAuth: detectAuth.value) detectAuthUsers)))) + ++ (map + (secret: { name = "nginx/addAuth/${secret}"; value = {}; }) + (inputs.lib.unique (map (addAuth: addAuth.value) addAuth))) + ); + }; + } + ) # http { assertions = map diff --git a/modules/services/xray.nix b/modules/services/xray.nix index d7b1073d..b1a66ca7 100644 --- a/modules/services/xray.nix +++ b/modules/services/xray.nix @@ -514,7 +514,7 @@ inputs: https."${xrayServer.serverName}" = { listen.main = { proxyProtocol = false; addToTransparentProxy = false; }; - location."/".return = "400"; + location."/".return.return = "400"; }; }; }; diff --git a/secrets/vps6.yaml b/secrets/vps6.yaml index c0dd5497..fabc5e5f 100644 --- a/secrets/vps6.yaml +++ b/secrets/vps6.yaml @@ -36,7 +36,9 @@ xray-server: user14: ENC[AES256_GCM,data:WFhrirjRUEZlOaCLGvHzvRPyp5O+035k0bNFqCvs0UTdT0+y,iv:C2vvOexQwFFkQyvFd8tf7lca2ZZIF3hbSiOHa2RFfGU=,tag:zowYrIut44mRiq6/h0r4fQ==,type:str] #ENC[AES256_GCM,data:t9mAcEcdBg==,iv:hzqb80+FtfsNP8ofYMyT0PwT8T8B3HYSGZUOrnk3SjM=,tag:0mbDe6S0bqbC/SffMr0AAg==,type:comment] user15: ENC[AES256_GCM,data:Sfc4BWiQ5dz7K0kwlp/1e8x/ahPTnbTvSvFjz9R5KQL52uaO,iv:kzap3jQgm9P22teMkYJHlySh2azLBBuy/kpm+ylxIhM=,tag:2fOBw+McYdT3r+qoF/Wkzw==,type:str] + #ENC[AES256_GCM,data:S7Iodket2fLLhcDDuWgv6fVAbcg=,iv:2XlrHA0A36xrmEv7kqtL8i8EYnNpq7cjRMmsF+mPu4s=,tag:M6JvHYU6jqqinPoHcgnEZA==,type:comment] user16: ENC[AES256_GCM,data:ijz4n66TY2tGpKLvGr7I6n+cOP6BfgpJdHmcPy2oTPGCvhR0,iv:RK8wi3Cj9XFVTqqt00DLru12Hiu/WJU8lV/v9MF5deI=,tag:6SHR8Yb2dO1rRY/xV5u9yw==,type:str] + #ENC[AES256_GCM,data:inAhj6SP8p4KahuZ+aSjPfnEcOY=,iv:eB6OvUkQvfdAkNuf95K7jAjZZ8i+nbsnsH3WEdRWFhw=,tag:dgw+RFY2cm6jF+R5z3Z+XA==,type:comment] user17: ENC[AES256_GCM,data:Wz7tWzASeIKE9TzicUIwyOnjZDDICYvDAUu/scHrQoFjoOlE,iv:A2gPFSiIXaf1dQkFlXjw5yesKtv3qOVcIXzM2QspvDk=,tag:JWCVx2FJS84v2iMdzBxhlQ==,type:str] user18: ENC[AES256_GCM,data:xQMRt+YC1Kn0Qxtis9QVIypq4uHNLq2sWKxxQe515Kfg+zzw,iv:28nQibxqzx5Q17UkEwK0zYhu6mFJ8LUk78xxlQrIqFY=,tag:B7N/fC81v8VBTsDdIZDvDw==,type:str] user19: ENC[AES256_GCM,data:Qjajmu6cfACT4eho6BK56zRd7BSXxo4fUeJ2RRawopVFZESJ,iv:QZN81pQxspe76V90NQxzsKmMwtvaC1qwuvd5a6WbrdU=,tag:/+LYeQLqvwM60DgIPtZzKA==,type:str] @@ -93,8 +95,8 @@ sops: ZXFTU3ZCaW1pTVh0RUJzdDdGdHlPYTgK2mlgcX2kEc8+2UDdBnhUm6IIuh8V6agW ooxH9OEPXUVI/4JcDo4v8ZUhAyU1ehLH0Ef7PJCChOZe2KZmWSNbhA== -----END AGE ENCRYPTED FILE----- - lastmodified: "2023-11-10T08:56:29Z" - mac: ENC[AES256_GCM,data:8FYqZcIX80p3ZonWY+JH64PwVtrBQkvAKw2vcBjha9+32S0oqA3P1tVP5ITa81t8ys1aQjAG+j8ShPioday46DnVB84FfKOelEsP5y3l5Em3QCli8EzaVY2Xk7JrMTvnZjIs+nhiBwGTC+BNxe5AXlUyT9m6SzVPwtUF280Bqug=,iv:dMoGufEPbC8M+k/5L6+RdRbaUBetJ1gXohxiFxNnfE0=,tag:CvVugdQ31tocWoPxQ1NwOA==,type:str] + lastmodified: "2023-11-15T03:24:39Z" + mac: ENC[AES256_GCM,data:vUekGOHfSM9qBZCvbUYA/PUZh9zoxDg7EgVsi35jJt+g8EYeG8LGCkEdoHXhoiTayLNUNPpYSLid1xnHGOg43wtpBZfR/UC8OafcOu+hIGdojxTfnEWsQk0lT3Qt/AyYA0PbakDxx6JiH8RRADCb2+ZZ3V2PuB6gE7Rc7tWpvOE=,iv:gIIySdB1CGeSn7tamGa0UxkXtdHfF1J5Ta12BgYB0Yg=,tag:Lqx49Aa/0QbAUwdkm4b0uQ==,type:str] pgp: [] unencrypted_suffix: _unencrypted version: 3.7.3