2023-08-25 20:53:31 +08:00
|
|
|
inputs:
|
|
|
|
{
|
2023-09-01 21:05:26 +08:00
|
|
|
options.nixos.services.nginx = let inherit (inputs.lib) mkOption types; in
|
|
|
|
{
|
|
|
|
enable = mkOption { type = types.bool; default = false; };
|
|
|
|
transparentProxy =
|
|
|
|
{
|
|
|
|
enable = mkOption { type = types.bool; default = true; };
|
2023-09-18 23:33:40 +08:00
|
|
|
externalIp = mkOption { type = types.listOf types.nonEmptyStr; };
|
2023-09-01 21:05:26 +08:00
|
|
|
map = mkOption { type = types.attrsOf types.ints.unsigned; default = {};};
|
|
|
|
};
|
|
|
|
httpProxy = mkOption
|
|
|
|
{
|
|
|
|
type = types.attrsOf (types.submodule { options =
|
|
|
|
{
|
|
|
|
rewriteHttps = mkOption { type = types.bool; default = false; };
|
|
|
|
http2 = mkOption { type = types.bool; default = true; };
|
|
|
|
addAuth = mkOption { type = types.bool; default = false; };
|
|
|
|
detectAuth = mkOption { type = types.bool; default = false; };
|
2023-09-16 15:34:27 +08:00
|
|
|
locations = mkOption
|
|
|
|
{
|
|
|
|
type = types.attrsOf (types.submodule { options =
|
|
|
|
{
|
|
|
|
upstream = mkOption { type = types.nonEmptyStr; };
|
|
|
|
websocket = mkOption { type = types.bool; default = false; };
|
|
|
|
setHeaders = mkOption { type = types.attrsOf types.str; default = {}; };
|
|
|
|
};});
|
|
|
|
};
|
2023-09-01 21:05:26 +08:00
|
|
|
};});
|
|
|
|
default = {};
|
|
|
|
};
|
2023-09-15 20:59:18 +08:00
|
|
|
streamProxy =
|
|
|
|
{
|
|
|
|
enable = mkOption { type = types.bool; default = false; };
|
|
|
|
port = mkOption { type = types.ints.unsigned; default = 5575; };
|
|
|
|
map = mkOption
|
|
|
|
{
|
|
|
|
type = types.attrsOf (types.oneOf
|
|
|
|
[
|
|
|
|
types.nonEmptyStr
|
|
|
|
(types.submodule { options =
|
|
|
|
{
|
|
|
|
upstream = mkOption { type = types.nonEmptyStr; };
|
|
|
|
rewriteHttps = mkOption { type = types.bool; default = false; };
|
|
|
|
};})
|
|
|
|
]);
|
|
|
|
default = {};
|
|
|
|
};
|
|
|
|
};
|
2023-09-01 21:05:26 +08:00
|
|
|
};
|
|
|
|
config =
|
|
|
|
let
|
|
|
|
inherit (inputs.lib) mkMerge mkIf;
|
|
|
|
inherit (inputs.localLib) stripeTabs attrsToList;
|
|
|
|
inherit (inputs.config.nixos.services) nginx;
|
|
|
|
inherit (builtins) map listToAttrs concatStringsSep toString filter attrValues;
|
|
|
|
in mkMerge
|
|
|
|
[
|
|
|
|
(mkIf nginx.enable
|
|
|
|
{
|
|
|
|
services =
|
|
|
|
{
|
|
|
|
nginx =
|
|
|
|
{
|
|
|
|
enable = true;
|
|
|
|
enableReload = true;
|
|
|
|
eventsConfig =
|
|
|
|
''
|
|
|
|
worker_connections 524288;
|
|
|
|
use epoll;
|
|
|
|
'';
|
|
|
|
commonHttpConfig =
|
|
|
|
''
|
|
|
|
geoip2 ${inputs.config.services.geoipupdate.settings.DatabaseDirectory}/GeoLite2-Country.mmdb {
|
|
|
|
$geoip2_data_country_code country iso_code;
|
|
|
|
}
|
|
|
|
log_format http '[$time_local] $remote_addr-$geoip2_data_country_code "$host"'
|
|
|
|
' $request_length $bytes_sent $status "$request" referer: "$http_referer" ua: "$http_user_agent"';
|
|
|
|
access_log syslog:server=unix:/dev/log http;
|
|
|
|
proxy_ssl_server_name on;
|
|
|
|
proxy_ssl_session_reuse off;
|
|
|
|
send_timeout 10m;
|
|
|
|
'';
|
|
|
|
proxyTimeout = "10m";
|
|
|
|
virtualHosts = listToAttrs (map
|
|
|
|
(site:
|
|
|
|
{
|
|
|
|
inherit (site) name;
|
|
|
|
value =
|
|
|
|
{
|
|
|
|
serverName = site.name;
|
2023-09-16 15:34:27 +08:00
|
|
|
listen = [ { addr = "127.0.0.1"; port = (if site.value.http2 then 443 else 3065); ssl = true; } ]
|
|
|
|
++ (if site.value.rewriteHttps then [ { addr = "0.0.0.0"; port = 80; } ] else []);
|
2023-09-01 21:05:26 +08:00
|
|
|
useACMEHost = site.name;
|
2023-09-16 15:34:27 +08:00
|
|
|
locations = listToAttrs (map
|
|
|
|
(location:
|
|
|
|
{
|
|
|
|
inherit (location) name;
|
|
|
|
value =
|
|
|
|
{
|
|
|
|
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.setHeaders))
|
|
|
|
++ (if site.value.detectAuth then ["proxy_hide_header Authorization;"] else [])
|
|
|
|
++ (
|
|
|
|
if site.value.addAuth then
|
|
|
|
["include ${inputs.config.sops.templates."nginx/addAuth/${site.name}-template".path};"]
|
|
|
|
else [])
|
|
|
|
);
|
|
|
|
};
|
|
|
|
})
|
|
|
|
(attrsToList site.value.locations));
|
2023-09-01 21:05:26 +08:00
|
|
|
forceSSL = site.value.rewriteHttps;
|
|
|
|
http2 = site.value.http2;
|
2023-09-16 15:34:27 +08:00
|
|
|
basicAuthFile =
|
|
|
|
if site.value.detectAuth then inputs.config.sops.secrets."nginx/detectAuth/${site.name}".path
|
|
|
|
else null;
|
2023-09-01 21:05:26 +08:00
|
|
|
};
|
|
|
|
})
|
|
|
|
(attrsToList nginx.httpProxy));
|
|
|
|
recommendedZstdSettings = true;
|
|
|
|
recommendedTlsSettings = true;
|
|
|
|
recommendedProxySettings = true;
|
|
|
|
recommendedOptimisation = true;
|
|
|
|
recommendedGzipSettings = true;
|
|
|
|
recommendedBrotliSettings = true;
|
|
|
|
clientMaxBodySize = "0";
|
|
|
|
package =
|
|
|
|
let
|
|
|
|
nginx-geoip2 =
|
|
|
|
{
|
|
|
|
name = "ngx_http_geoip2_module";
|
|
|
|
src = inputs.pkgs.fetchFromGitHub
|
|
|
|
{
|
|
|
|
owner = "leev";
|
|
|
|
repo = "ngx_http_geoip2_module";
|
|
|
|
rev = "a607a41a8115fecfc05b5c283c81532a3d605425";
|
|
|
|
hash = "sha256-CkmaeEa1iEAabJEDu3FhBUR7QF38koGYlyx+pyKZV9Y=";
|
|
|
|
};
|
|
|
|
meta.license = [];
|
|
|
|
};
|
|
|
|
in
|
|
|
|
(inputs.pkgs.nginxMainline.override (prev: { modules = prev.modules ++ [ nginx-geoip2 ]; }))
|
|
|
|
.overrideAttrs (prev: { buildInputs = prev.buildInputs ++ [ inputs.pkgs.libmaxminddb ]; });
|
2023-09-15 20:59:18 +08:00
|
|
|
streamConfig =
|
|
|
|
''
|
|
|
|
geoip2 ${inputs.config.services.geoipupdate.settings.DatabaseDirectory}/GeoLite2-Country.mmdb
|
|
|
|
{
|
|
|
|
$geoip2_data_country_code country iso_code;
|
|
|
|
}
|
2023-09-15 21:12:33 +08:00
|
|
|
resolver 8.8.8.8;
|
2023-09-15 20:59:18 +08:00
|
|
|
'';
|
2023-09-15 21:12:33 +08:00
|
|
|
# todo: use host dns
|
|
|
|
resolver.addresses = [ "8.8.8.8" ];
|
2023-09-01 21:05:26 +08:00
|
|
|
};
|
|
|
|
geoipupdate =
|
|
|
|
{
|
|
|
|
enable = true;
|
|
|
|
settings =
|
|
|
|
{
|
|
|
|
AccountID = 901296;
|
|
|
|
LicenseKey = inputs.config.sops.secrets."nginx/maxmind-license".path;
|
|
|
|
EditionIDs = [ "GeoLite2-ASN" "GeoLite2-City" "GeoLite2-Country" ];
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
sops =
|
|
|
|
{
|
|
|
|
templates = listToAttrs (map
|
|
|
|
(site:
|
|
|
|
{
|
|
|
|
name = "nginx/addAuth/${site.name}-template";
|
|
|
|
value =
|
|
|
|
{
|
|
|
|
content =
|
|
|
|
let placeholder = inputs.config.sops.placeholder."nginx/addAuth/${site.name}";
|
|
|
|
in ''proxy_set_header Authorization "Basic ${placeholder}";'';
|
|
|
|
owner = inputs.config.users.users.nginx.name;
|
|
|
|
};
|
|
|
|
})
|
|
|
|
(filter (site: site.value.addAuth) (attrsToList nginx.httpProxy)));
|
|
|
|
secrets = { "nginx/maxmind-license".owner = inputs.config.users.users.nginx.name; }
|
|
|
|
// (listToAttrs (map
|
|
|
|
(site: { name = "nginx/detectAuth/${site.name}"; value.owner = inputs.config.users.users.nginx.name; })
|
|
|
|
(filter (site: site.value.detectAuth) (attrsToList nginx.httpProxy))))
|
|
|
|
// (listToAttrs (map
|
|
|
|
(site: { name = "nginx/addAuth/${site.name}"; value = {}; })
|
|
|
|
(filter (site: site.value.addAuth) (attrsToList nginx.httpProxy))));
|
|
|
|
};
|
|
|
|
systemd.services.nginx.serviceConfig =
|
|
|
|
{
|
|
|
|
CapabilityBoundingSet = [ "CAP_NET_ADMIN" ];
|
|
|
|
AmbientCapabilities = [ "CAP_NET_ADMIN" ];
|
|
|
|
LimitNPROC = 65536;
|
|
|
|
LimitNOFILE = 524288;
|
|
|
|
};
|
|
|
|
nixos.services.acme =
|
|
|
|
{
|
|
|
|
enable = true;
|
|
|
|
certs = map (cert: cert.name) (attrsToList nginx.httpProxy);
|
|
|
|
};
|
|
|
|
security.acme.certs = listToAttrs (map
|
|
|
|
(cert: { inherit (cert) name; value.group = inputs.config.services.nginx.group; })
|
|
|
|
(attrsToList nginx.httpProxy));
|
|
|
|
})
|
|
|
|
(mkIf nginx.transparentProxy.enable
|
|
|
|
{
|
|
|
|
services.nginx.streamConfig =
|
|
|
|
''
|
2023-09-15 20:59:18 +08:00
|
|
|
log_format transparent_proxy '[$time_local] $remote_addr-$geoip2_data_country_code '
|
|
|
|
'"$ssl_preread_server_name"->$transparent_proxy_backend $bytes_sent $bytes_received';
|
|
|
|
map $ssl_preread_server_name $transparent_proxy_backend
|
2023-09-01 21:05:26 +08:00
|
|
|
{
|
|
|
|
${concatStringsSep "\n" (map
|
|
|
|
(x: '' "${x.name}" 127.0.0.1:${toString x.value};'')
|
|
|
|
(
|
|
|
|
(attrsToList nginx.transparentProxy.map)
|
|
|
|
++ (map
|
|
|
|
(site: { name = site.name; value = (if site.value.http2 then 443 else 3065); })
|
|
|
|
(attrsToList nginx.httpProxy)
|
|
|
|
)
|
|
|
|
))}
|
|
|
|
default 127.0.0.1:443;
|
|
|
|
}
|
|
|
|
server
|
|
|
|
{
|
2023-09-18 23:33:40 +08:00
|
|
|
${concatStringsSep "\n " (map (ip: "listen ${ip}:443;") nginx.transparentProxy.externalIp)}
|
2023-09-01 21:05:26 +08:00
|
|
|
ssl_preread on;
|
|
|
|
proxy_bind $remote_addr transparent;
|
2023-09-15 20:59:18 +08:00
|
|
|
proxy_pass $transparent_proxy_backend;
|
2023-09-01 21:05:26 +08:00
|
|
|
proxy_connect_timeout 1s;
|
|
|
|
proxy_socket_keepalive on;
|
|
|
|
proxy_buffer_size 128k;
|
2023-09-15 20:59:18 +08:00
|
|
|
access_log syslog:server=unix:/dev/log transparent_proxy;
|
2023-09-01 21:05:26 +08:00
|
|
|
}
|
|
|
|
'';
|
|
|
|
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
|
|
|
systemd.services.nginx-proxy =
|
|
|
|
let
|
|
|
|
ipset = "${inputs.pkgs.ipset}/bin/ipset";
|
|
|
|
iptables = "${inputs.pkgs.iptables}/bin/iptables";
|
|
|
|
ip = "${inputs.pkgs.iproute}/bin/ip";
|
|
|
|
start = inputs.pkgs.writeShellScript "nginx-proxy.start"
|
|
|
|
(
|
|
|
|
''
|
|
|
|
${ipset} create nginx_proxy_port bitmap:port range 0-65535
|
|
|
|
${iptables} -t mangle -N nginx_proxy_mark
|
|
|
|
${iptables} -t mangle -A OUTPUT -j nginx_proxy_mark
|
|
|
|
${iptables} -t mangle -A nginx_proxy_mark -s 127.0.0.1 -p tcp \
|
|
|
|
-m set --match-set nginx_proxy_port src -j MARK --set-mark 2/2
|
|
|
|
${iptables} -t mangle -N nginx_proxy
|
|
|
|
${iptables} -t mangle -A PREROUTING -j nginx_proxy
|
|
|
|
${iptables} -t mangle -A nginx_proxy -s 127.0.0.1 -p tcp \
|
|
|
|
-m set --match-set nginx_proxy_port src -j MARK --set-mark 2/2
|
|
|
|
${ip} rule add fwmark 2/2 table 200
|
|
|
|
${ip} route add local 0.0.0.0/0 dev lo table 200
|
|
|
|
''
|
|
|
|
+ concatStringsSep "\n" (map
|
|
|
|
(port: ''${ipset} add nginx_proxy_port ${toString port}'')
|
|
|
|
(inputs.lib.unique ((attrValues nginx.transparentProxy.map) ++ [ 443 3065 ])))
|
|
|
|
);
|
|
|
|
stop = inputs.pkgs.writeShellScript "nginx-proxy.stop"
|
|
|
|
''
|
|
|
|
${iptables} -t mangle -F nginx_proxy_mark
|
|
|
|
${iptables} -t mangle -D OUTPUT -j nginx_proxy_mark
|
|
|
|
${iptables} -t mangle -X nginx_proxy_mark
|
|
|
|
${iptables} -t mangle -F nginx_proxy
|
|
|
|
${iptables} -t mangle -D PREROUTING -j nginx_proxy
|
|
|
|
${iptables} -t mangle -X nginx_proxy
|
|
|
|
${ip} rule del fwmark 2/2 table 200
|
|
|
|
${ip} route del local 0.0.0.0/0 dev lo table 200
|
|
|
|
${ipset} destroy nginx_proxy_port
|
|
|
|
'';
|
|
|
|
in
|
|
|
|
{
|
|
|
|
description = "nginx transparent proxy";
|
|
|
|
after = [ "network.target" ];
|
|
|
|
serviceConfig =
|
|
|
|
{
|
|
|
|
Type = "simple";
|
|
|
|
RemainAfterExit = true;
|
|
|
|
ExecStart = start;
|
|
|
|
ExecStop = stop;
|
|
|
|
};
|
|
|
|
wants = [ "network.target" ];
|
|
|
|
wantedBy= [ "multi-user.target" ];
|
|
|
|
};
|
|
|
|
})
|
2023-09-15 20:59:18 +08:00
|
|
|
(mkIf nginx.streamProxy.enable
|
|
|
|
{
|
|
|
|
services.nginx =
|
|
|
|
{
|
|
|
|
streamConfig =
|
|
|
|
''
|
|
|
|
log_format stream_proxy '[$time_local] $remote_addr-$geoip2_data_country_code '
|
|
|
|
'"$ssl_preread_server_name"->$stream_proxy_backend $bytes_sent $bytes_received';
|
|
|
|
map $ssl_preread_server_name $stream_proxy_backend
|
|
|
|
{
|
|
|
|
${concatStringsSep "\n" (map
|
|
|
|
(x: '' "${x.name}" "${x.value.upstream or x.value}";'')
|
|
|
|
(attrsToList nginx.streamProxy.map))}
|
|
|
|
}
|
|
|
|
server
|
|
|
|
{
|
|
|
|
listen 127.0.0.1:${toString nginx.streamProxy.port};
|
|
|
|
ssl_preread on;
|
|
|
|
proxy_pass $stream_proxy_backend;
|
|
|
|
proxy_connect_timeout 10s;
|
|
|
|
proxy_socket_keepalive on;
|
|
|
|
proxy_buffer_size 128k;
|
|
|
|
access_log syslog:server=unix:/dev/log stream_proxy;
|
|
|
|
}
|
|
|
|
'';
|
|
|
|
virtualHosts = listToAttrs (map
|
|
|
|
(site:
|
|
|
|
{
|
|
|
|
inherit (site) name;
|
|
|
|
value =
|
|
|
|
{
|
|
|
|
serverName = site.name;
|
|
|
|
listen = [ { addr = "0.0.0.0"; port = 80; } ];
|
2023-09-16 00:22:17 +08:00
|
|
|
locations."/".return = "301 https://${site.name}$request_uri";
|
2023-09-15 20:59:18 +08:00
|
|
|
};
|
|
|
|
})
|
|
|
|
(filter (site: site.value.rewriteHttps or false) (attrsToList nginx.streamProxy.map)));
|
|
|
|
};
|
|
|
|
nixos.services.nginx.transparentProxy.map = listToAttrs (map
|
|
|
|
(site: { name = site.name; value = nginx.streamProxy.port; })
|
|
|
|
(attrsToList nginx.streamProxy.map));
|
|
|
|
})
|
2023-09-01 21:05:26 +08:00
|
|
|
];
|
2023-08-25 20:53:31 +08:00
|
|
|
}
|