Merge branch 'nginx'

This commit is contained in:
2023-11-15 19:30:07 +08:00
4 changed files with 279 additions and 230 deletions

View File

@@ -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;
};
};
};
}

View File

@@ -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

View File

@@ -514,7 +514,7 @@ inputs:
https."${xrayServer.serverName}" =
{
listen.main = { proxyProtocol = false; addToTransparentProxy = false; };
location."/".return = "400";
location."/".return.return = "400";
};
};
};

View File

@@ -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