mirror of
https://github.com/oqyude/nixos.git
synced 2026-10-07 20:47:14 +03:00
refactoring
This commit is contained in:
@@ -10,26 +10,27 @@ let
|
||||
certDomain = xlib.services."3x-ui".certDomain or null;
|
||||
certMounts =
|
||||
if certDomain == null then [ ]
|
||||
else [
|
||||
# Let's Encrypt cert for the panel domain — mounted read-only so
|
||||
# 3x-ui can serve the panel over its own TLS. webCertFile /
|
||||
# webKeyFile in the x-ui settings table must point at
|
||||
# /root/cert/fullchain.pem and /root/cert/key.pem respectively.
|
||||
"/var/lib/acme/${certDomain}/fullchain.pem:/root/cert/fullchain.pem:ro"
|
||||
"/var/lib/acme/${certDomain}/key.pem:/root/cert/key.pem:ro"
|
||||
];
|
||||
else
|
||||
# LE cert mounted read-only so 3x-ui can terminate TLS itself.
|
||||
# The 3x-ui settings table must point webCertFile / webKeyFile at
|
||||
# /root/cert/fullchain.pem and /root/cert/key.pem.
|
||||
map (f: "/var/lib/acme/${certDomain}/${f}:/root/cert/${f}:ro") [
|
||||
"fullchain.pem"
|
||||
"key.pem"
|
||||
];
|
||||
basePorts = [
|
||||
# 2049/tcp — 3x-ui web panel
|
||||
# 2096/tcp — subscription endpoint
|
||||
# 14380-15379/tcp+udp — Xray inbounds (matches firewall open range)
|
||||
"0.0.0.0:2049:2049/tcp"
|
||||
"0.0.0.0:2096:2096/tcp"
|
||||
"0.0.0.0:14380-15379:14380-15379/tcp"
|
||||
"0.0.0.0:14380-15379:14380-15379/udp"
|
||||
];
|
||||
realityPorts =
|
||||
# Only vds needs the 15380→443 forwarding that lets nginx stream
|
||||
# pass-through Xray REALITY while Xray itself sees the connection
|
||||
# arriving on 443 (matching its REALITY inbound config).
|
||||
lib.optional xlib.services."3x-ui".reality443Forwarding
|
||||
"0.0.0.0:15380:443/tcp";
|
||||
# VDS-only: nginx stream forwards host:443 → host:15380 → container:443,
|
||||
# so Xray inside the container sees its REALITY inbound on its real
|
||||
# configured port 443.
|
||||
realityPorts = lib.optional xlib.services."3x-ui".reality443Forwarding "0.0.0.0:15380:443/tcp";
|
||||
in
|
||||
{
|
||||
virtualisation = {
|
||||
@@ -55,15 +56,8 @@ in
|
||||
"${panel}/db/:/etc/x-ui:rw"
|
||||
] ++ certMounts;
|
||||
log-driver = "journald";
|
||||
# Port-forwarded networking (replaces --network=host).
|
||||
# Common across all nodes that import this module:
|
||||
# 2049/tcp — 3x-ui web panel
|
||||
# 2096/tcp — subscription endpoint
|
||||
# 14380-15379/tcp+udp — Xray inbounds (matches firewall open range)
|
||||
# Vds-only (xlib.services.3x-ui.reality443Forwarding = true):
|
||||
# 15380→443/tcp — Xray REALITY inbound (nginx stream on 443 → 15380)
|
||||
# Adding a new inbound through the 3x-ui panel on a port outside
|
||||
# this range will require extending this list and rebuilding.
|
||||
# the 14380-15379 range requires extending basePorts and rebuilding.
|
||||
ports = basePorts ++ realityPorts;
|
||||
};
|
||||
};
|
||||
@@ -72,21 +66,12 @@ in
|
||||
systemd = {
|
||||
services = {
|
||||
"podman-3xui_app" = {
|
||||
serviceConfig = {
|
||||
Restart = lib.mkOverride 90 "always";
|
||||
};
|
||||
partOf = [
|
||||
"podman-compose-3x-ui-root.target"
|
||||
];
|
||||
wantedBy = [
|
||||
"podman-compose-3x-ui-root.target"
|
||||
];
|
||||
serviceConfig.Restart = lib.mkOverride 90 "always";
|
||||
partOf = [ "podman-compose-3x-ui-root.target" ];
|
||||
wantedBy = [ "podman-compose-3x-ui-root.target" ];
|
||||
};
|
||||
# Update
|
||||
"podman-update-3xui_app" = {
|
||||
path = [
|
||||
pkgs.podman
|
||||
];
|
||||
path = [ pkgs.podman ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
TimeoutSec = 300;
|
||||
@@ -96,29 +81,10 @@ in
|
||||
systemctl restart podman-3xui_app.service
|
||||
'';
|
||||
};
|
||||
# Builds
|
||||
# "podman-build-3xui_app" = {
|
||||
# path = [
|
||||
# pkgs.podman
|
||||
# pkgs.git
|
||||
# ];
|
||||
# serviceConfig = {
|
||||
# Type = "oneshot";
|
||||
# TimeoutSec = 300;
|
||||
# };
|
||||
# script = ''
|
||||
# cd /mnt/containers/3x-ui
|
||||
# podman build -t compose2nix/3xui_app -f ./Dockerfile .
|
||||
# '';
|
||||
# };
|
||||
};
|
||||
# Root service
|
||||
# When started, this will automatically create all resources and start
|
||||
# the containers. When stopped, this will teardown all resources.
|
||||
# Starts/stops together with all 3x-ui compose resources.
|
||||
targets."podman-compose-3x-ui-root" = {
|
||||
unitConfig = {
|
||||
Description = "Root target generated by compose2nix.";
|
||||
};
|
||||
unitConfig.Description = "Root target generated by compose2nix.";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
timers."podman-update-3xui_app" = {
|
||||
@@ -128,15 +94,15 @@ in
|
||||
Persistent = true;
|
||||
};
|
||||
};
|
||||
# Folders
|
||||
tmpfiles.rules = [
|
||||
"d ${xlib.dirs.services-mnt-folder} 0755 root root -"
|
||||
"d ${xlib.dirs.services-nodes-folder} 0755 root root -"
|
||||
"d ${xlib.dirs.services-nodes-folder}/${xlib.device.hostname} 0755 root root -"
|
||||
"d ${panel} 0755 root root -"
|
||||
"d ${panel}/db 0755 root root -"
|
||||
"d ${panel}/cert 0755 root root -"
|
||||
"Z ${panel} 0755 root root -"
|
||||
(xlib.helpers.mkTmpfile "d" xlib.dirs.services-mnt-folder "0755" "root" "root")
|
||||
(xlib.helpers.mkTmpfile "d" xlib.dirs.services-nodes-folder "0755" "root" "root")
|
||||
(xlib.helpers.mkTmpfile "d" "${xlib.dirs.services-nodes-folder}/${xlib.device.hostname}" "0755" "root" "root")
|
||||
(xlib.helpers.mkTmpfile "d" panel "0755" "root" "root")
|
||||
(xlib.helpers.mkTmpfile "d" "${panel}/db" "0755" "root" "root")
|
||||
(xlib.helpers.mkTmpfile "d" "${panel}/cert" "0755" "root" "root")
|
||||
# Relabel panel dir for SELinux so containers can access it.
|
||||
(xlib.helpers.mkTmpfile "Z" panel "0755" "root" "root")
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
+123
-321
@@ -5,17 +5,18 @@
|
||||
xlib,
|
||||
...
|
||||
}:
|
||||
# Standard reverse-proxy: HTTP/S termination upstream, backend on the LAN.
|
||||
# x.zeroq.su is the 3x-ui controller panel — see 3x-ui.nix for the
|
||||
# /subs/, /subsjs/, /clash/ routing logic.
|
||||
let
|
||||
server = "192.168.1.20";
|
||||
|
||||
# Standard TLS proxy vhost: "/" -> http://server:port
|
||||
# Returns { name = domain; value = vhost; } for builtins.listToAttrs
|
||||
mkProxy =
|
||||
{
|
||||
domain,
|
||||
port,
|
||||
addSSL ? false,
|
||||
body ? false,
|
||||
extraConfig ? "",
|
||||
}:
|
||||
{
|
||||
name = domain;
|
||||
@@ -28,20 +29,17 @@ let
|
||||
}
|
||||
// lib.optionalAttrs (!addSSL) { forceSSL = true; }
|
||||
// lib.optionalAttrs addSSL { addSSL = true; }
|
||||
// lib.optionalAttrs body {
|
||||
extraConfig = ''
|
||||
client_max_body_size 5G;
|
||||
'';
|
||||
};
|
||||
// lib.optionalAttrs (extraConfig != "") { inherit extraConfig; };
|
||||
};
|
||||
|
||||
# Simple proxy sites
|
||||
bigUploads = "client_max_body_size 5G;";
|
||||
|
||||
sites = [
|
||||
{
|
||||
domain = "immich.zeroq.su";
|
||||
port = 2283;
|
||||
addSSL = true;
|
||||
body = true;
|
||||
extraConfig = bigUploads;
|
||||
}
|
||||
{
|
||||
domain = "kuma.zeroq.su";
|
||||
@@ -71,337 +69,141 @@ let
|
||||
{
|
||||
domain = "calibre.zeroq.su";
|
||||
port = 8083;
|
||||
body = true;
|
||||
extraConfig = bigUploads;
|
||||
}
|
||||
# {
|
||||
# domain = "mc.zeroq.su";
|
||||
# port = 25565;
|
||||
# }
|
||||
{
|
||||
domain = "nix-cache.zeroq.su";
|
||||
port = 5000;
|
||||
body = true;
|
||||
extraConfig = bigUploads;
|
||||
}
|
||||
{
|
||||
domain = "pdf.zeroq.su";
|
||||
port = 8446;
|
||||
body = true;
|
||||
extraConfig = bigUploads;
|
||||
}
|
||||
];
|
||||
in
|
||||
{
|
||||
services = {
|
||||
nginx = {
|
||||
enable = true;
|
||||
recommendedGzipSettings = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedProxySettings = true;
|
||||
recommendedTlsSettings = true;
|
||||
virtualHosts = (builtins.listToAttrs (map mkProxy sites)) // {
|
||||
"nextcloud.private" = {
|
||||
forceSSL = false;
|
||||
enableACME = false;
|
||||
listen = [
|
||||
{
|
||||
addr = "100.64.0.0";
|
||||
port = 10000;
|
||||
}
|
||||
{
|
||||
addr = "192.168.1.20";
|
||||
port = 10000;
|
||||
}
|
||||
{
|
||||
addr = "127.0.0.1";
|
||||
port = 10000;
|
||||
}
|
||||
];
|
||||
};
|
||||
"office.zeroq.su" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
};
|
||||
"pdf.private" = {
|
||||
forceSSL = false;
|
||||
enableACME = false;
|
||||
listen = [
|
||||
{
|
||||
addr = "0.0.0.0";
|
||||
port = 80;
|
||||
}
|
||||
{
|
||||
addr = "100.64.0.0";
|
||||
port = 8446;
|
||||
}
|
||||
{
|
||||
addr = "192.168.1.20";
|
||||
port = 8446;
|
||||
}
|
||||
{
|
||||
addr = "127.0.0.1";
|
||||
port = 8446;
|
||||
}
|
||||
];
|
||||
extraConfig = ''
|
||||
client_max_body_size 5G;
|
||||
'';
|
||||
};
|
||||
"x.zeroq.su" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
locations = {
|
||||
"/" = {
|
||||
proxyPass = "http://${server}:2049";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
"/subs/" = {
|
||||
proxyPass = "http://${server}:2096";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
"/subsjs/" = {
|
||||
proxyPass = "http://${server}:2096";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
"/clash/" = {
|
||||
proxyPass = "http://${server}:2096";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
recommendedGzipSettings = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedProxySettings = true;
|
||||
recommendedTlsSettings = true;
|
||||
virtualHosts = (builtins.listToAttrs (map mkProxy sites)) // {
|
||||
"nextcloud.private" = {
|
||||
forceSSL = false;
|
||||
enableACME = false;
|
||||
listen = [
|
||||
{
|
||||
addr = "100.64.0.0";
|
||||
port = 10000;
|
||||
}
|
||||
{
|
||||
addr = "192.168.1.20";
|
||||
port = 10000;
|
||||
}
|
||||
{
|
||||
addr = "127.0.0.1";
|
||||
port = 10000;
|
||||
}
|
||||
];
|
||||
};
|
||||
"office.zeroq.su" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
};
|
||||
"pdf.private" = {
|
||||
forceSSL = false;
|
||||
enableACME = false;
|
||||
listen = [
|
||||
{
|
||||
addr = "0.0.0.0";
|
||||
port = 80;
|
||||
}
|
||||
{
|
||||
addr = "100.64.0.0";
|
||||
port = 8446;
|
||||
}
|
||||
{
|
||||
addr = "192.168.1.20";
|
||||
port = 8446;
|
||||
}
|
||||
{
|
||||
addr = "127.0.0.1";
|
||||
port = 8446;
|
||||
}
|
||||
];
|
||||
extraConfig = bigUploads;
|
||||
};
|
||||
"x.zeroq.su" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
locations = {
|
||||
"/" = {
|
||||
proxyPass = "http://${server}:2049";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
# "talk.zeroq.su" = {
|
||||
# forceSSL = true;
|
||||
# enableACME = true;
|
||||
# # locations = {
|
||||
# # "/" = {
|
||||
# # proxyPass = "http://127.0.0.1:7880";
|
||||
# # proxyWebsockets = true;
|
||||
# # };
|
||||
# # };
|
||||
# extraConfig = ''
|
||||
# client_max_body_size 5G;
|
||||
# '';
|
||||
# };
|
||||
# "turn.zeroq.su" = {
|
||||
# forceSSL = true;
|
||||
# enableACME = true;
|
||||
# locations = {
|
||||
# "/" = {
|
||||
# proxyPass = "http://127.0.0.1:5349";
|
||||
# proxyWebsockets = true;
|
||||
# };
|
||||
# };
|
||||
# extraConfig = ''
|
||||
# client_max_body_size 5G;
|
||||
# '';
|
||||
# };
|
||||
# "ca.home.arpa" = {
|
||||
# forceSSL = true;
|
||||
# enableACME = true;
|
||||
# locations."/" = {
|
||||
# proxyPass = "http://127.0.0.1:9000";
|
||||
# proxyWebsockets = true;
|
||||
# };
|
||||
# extraConfig = ''
|
||||
# client_max_body_size 5G;
|
||||
# '';
|
||||
# };
|
||||
# "n8n.zeroq.su" = {
|
||||
# forceSSL = true;
|
||||
# enableACME = true;
|
||||
# locations."/" = {
|
||||
# proxyPass = "http://${server}:5678";
|
||||
# proxyWebsockets = true;
|
||||
# };
|
||||
# extraConfig = ''
|
||||
# client_max_body_size 5G;
|
||||
# '';
|
||||
# };
|
||||
# "kuma.home.arpa" = {
|
||||
# forceSSL = true;
|
||||
# enableACME = true;
|
||||
# locations."/" = {
|
||||
# proxyPass = "http://127.0.0.1:4001";
|
||||
# proxyWebsockets = true;
|
||||
# };
|
||||
# };
|
||||
# "flux.home.arpa" = {
|
||||
# addSSL = true;
|
||||
# enableACME = true;
|
||||
# locations."/" = {
|
||||
# proxyPass = "http://127.0.0.1:6061";
|
||||
# proxyWebsockets = true;
|
||||
# };
|
||||
# };
|
||||
# "navidrome.home.arpa" = {
|
||||
# addSSL = true;
|
||||
# enableACME = true;
|
||||
# locations."/" = {
|
||||
# proxyPass = "http://127.0.0.1:4533";
|
||||
# proxyWebsockets = true;
|
||||
# };
|
||||
# };
|
||||
# "immich.home.arpa" = {
|
||||
# addSSL = true;
|
||||
# enableACME = true;
|
||||
# locations."/" = {
|
||||
# proxyPass = "http://127.0.0.1:2283";
|
||||
# proxyWebsockets = true;
|
||||
# };
|
||||
# extraConfig = ''
|
||||
# client_max_body_size 5G;
|
||||
# '';
|
||||
# };
|
||||
# "agent.zeroq.su" = {
|
||||
# forceSSL = true;
|
||||
# enableACME = true;
|
||||
# locations."/" = {
|
||||
# proxyPass = "http://${server}:3000";
|
||||
# proxyWebsockets = true;
|
||||
# };
|
||||
# };
|
||||
# "node-red.zeroq.su" = {
|
||||
# forceSSL = true;
|
||||
# enableACME = true;
|
||||
# kTLS = true;
|
||||
# locations."/" = {
|
||||
# proxyPass = "http://${server}:1880";
|
||||
# proxyWebsockets = true;
|
||||
# };
|
||||
# extraConfig = ''
|
||||
# client_max_body_size 5G;
|
||||
# '';
|
||||
# };
|
||||
"zeroq.su" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
root = pkgs.writeTextDir "index.html" ''
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
<pre>What are you doing here?</pre>
|
||||
</body>
|
||||
</html>
|
||||
'';
|
||||
locations = {
|
||||
"/guest/" = {
|
||||
proxyPass = "http://${server}:80";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
# "/.well-known/discord" = {
|
||||
# extraConfig = ''
|
||||
# default_type text/plain;
|
||||
# return 200 "dh=c2d103553a4cfdaa1b7952a87a7d8120a1e167cc";
|
||||
# '';
|
||||
# };
|
||||
"/subs/" = {
|
||||
proxyPass = "http://${server}:2096";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
"vetymae.opencodes.zeroq.su" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://100.86.62.4:4096";
|
||||
"/subsjs/" = {
|
||||
proxyPass = "http://${server}:2096";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
"/clash/" = {
|
||||
proxyPass = "http://${server}:2096";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
"lamet.opencodes.zeroq.su" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://100.106.21.39:6061";
|
||||
};
|
||||
"zeroq.su" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
root = pkgs.writeTextDir "index.html" ''
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
<pre>What are you doing here?</pre>
|
||||
</body>
|
||||
</html>
|
||||
'';
|
||||
locations."/guest/" = {
|
||||
proxyPass = "http://${server}:80";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
"vetymae.opencodes.zeroq.su" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://100.86.62.4:4096";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
"lamet.opencodes.zeroq.su" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://100.106.21.39:6061";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
"nextcloud.zeroq.su" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
locations = {
|
||||
"/" = {
|
||||
proxyPass = "http://${server}:10000";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
"/whiteboard" = {
|
||||
proxyPass = "http://${server}:3002";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
# "n8n.zeroq.su" = {
|
||||
# forceSSL = true;
|
||||
# enableACME = true;
|
||||
# locations."/" = {
|
||||
# proxyPass = "http://${server}:5678";
|
||||
# proxyWebsockets = true;
|
||||
# };
|
||||
# };
|
||||
# "office.zeroq.su" = {
|
||||
# enableACME = true;
|
||||
# forceSSL = true;
|
||||
# locations = {
|
||||
# "/" = {
|
||||
# proxyPass = "http://${server}:8090";
|
||||
# proxyWebsockets = true;
|
||||
# };
|
||||
# };
|
||||
# };
|
||||
"nextcloud.zeroq.su" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
locations = {
|
||||
"/" = {
|
||||
proxyPass = "http://${server}:10000";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
"/whiteboard" = {
|
||||
proxyPass = "http://${server}:3002";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
extraConfig = ''
|
||||
client_max_body_size 5G;
|
||||
'';
|
||||
};
|
||||
# "calibre.home.arpa" = {
|
||||
# forceSSL = true;
|
||||
# enableACME = true;
|
||||
# locations."/" = {
|
||||
# proxyPass = "http://127.0.0.1:8083";
|
||||
# proxyWebsockets = true;
|
||||
# };
|
||||
# extraConfig = ''
|
||||
# client_max_body_size 5G;
|
||||
# '';
|
||||
# };
|
||||
# "dns.home.arpa" = {
|
||||
# forceSSL = true;
|
||||
# enableACME = true;
|
||||
# locations."/" = {
|
||||
# proxyPass = "http://127.0.0.1:53";
|
||||
# };
|
||||
# extraConfig = ''
|
||||
# client_max_body_size 5G;
|
||||
# '';
|
||||
# };
|
||||
# "glances.home.arpa" = {
|
||||
# forceSSL = true;
|
||||
# enableACME = true;
|
||||
# locations."/" = {
|
||||
# proxyPass = "http://127.0.0.1:61208";
|
||||
# };
|
||||
# };
|
||||
# "syncthing.home.arpa" = {
|
||||
# addSSL = true;
|
||||
# enableACME = true;
|
||||
# locations."/" = {
|
||||
# proxyPass = "http://127.0.0.1:8384";
|
||||
# };
|
||||
# };
|
||||
# "zeroq.home.arpa" = {
|
||||
# forceSSL = true;
|
||||
# enableACME = true;
|
||||
# root = pkgs.writeTextDir "index.html" ''
|
||||
# <!doctype html>
|
||||
# <html>
|
||||
# <body>
|
||||
# <pre>This server is running in backend.</pre>
|
||||
# </body>
|
||||
# </html>
|
||||
# '';
|
||||
# listen = [
|
||||
# {
|
||||
# addr = "100.64.0.0";
|
||||
# port = 80;
|
||||
# }
|
||||
# {
|
||||
# addr = "192.168.1.20";
|
||||
# port = 80;
|
||||
# }
|
||||
# ];
|
||||
# };
|
||||
extraConfig = bigUploads;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
+50
-70
@@ -4,92 +4,72 @@
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
# VDS nginx differs from server's in one key way: port 443 is owned
|
||||
# by an nginx stream block that does SNI-based TCP routing, not by
|
||||
# http { server {} } blocks. This lets a single host (pubray1.zeroq.su)
|
||||
# serve both the 3x-ui panel and an Xray REALITY inbound over TLS,
|
||||
# sharing the same port.
|
||||
#
|
||||
# Routing:
|
||||
# pubray1.zeroq.su → 127.0.0.1:2049 (3x-ui panel; LE cert mounted
|
||||
# into the container terminates TLS)
|
||||
# pubrayx1.zeroq.su → 127.0.0.1:15380 (Xray REALITY; Xray sees its
|
||||
# real configured port 443 via DNAT)
|
||||
# default → 127.0.0.1:15380 (REALITY fallback for any
|
||||
# other SNI / IP-direct)
|
||||
#
|
||||
# ssl_preread reads SNI from the ClientHello and forwards the rest of
|
||||
# the TLS stream as-is, so Xray sees a real port-443 connection even
|
||||
# though podman DNATs it via host:15380.
|
||||
let
|
||||
server = "100.64.0.0";
|
||||
# TCP-level SNI routing on 443:
|
||||
# pubray1.zeroq.su → 3x-ui panel (TLS passthrough to 127.0.0.1:2049,
|
||||
# 3x-ui terminates TLS using the cert mounted
|
||||
# from /var/lib/acme/pubray1.zeroq.su/)
|
||||
# pubrayx1.zeroq.su → Xray REALITY (TLS passthrough to 127.0.0.1:15380,
|
||||
# which podman DNATs to container:443)
|
||||
# default → Xray (any other SNI / IP-direct hits the REALITY
|
||||
# fallback, which is what we want)
|
||||
# nginx stream does not inspect TLS — the SNI is read from the ClientHello
|
||||
# via ssl_preread, then the entire TCP stream (including the rest of the
|
||||
# TLS handshake) is forwarded as-is to the chosen upstream. So Xray sees
|
||||
# the client connect on its real configured port (443) even though the
|
||||
# host-side port from podman's perspective is 15380.
|
||||
# Goes inside the auto-generated `stream {}` block (services.nginx.streamConfig).
|
||||
# pubray1.zeroq.su → 3x-ui panel; everything else (including any SNI
|
||||
# a REALITY client uses, e.g. media.mediavitrina.ru) → Xray.
|
||||
streamConfig = ''
|
||||
stream {
|
||||
ssl_preread on;
|
||||
ssl_preread on;
|
||||
|
||||
# pubray1.zeroq.su SNI → 3x-ui panel (TLS terminated inside the
|
||||
# container using the LE cert mounted from /var/lib/acme/).
|
||||
# Everything else (including any sni the REALITY client uses,
|
||||
# e.g. media.mediavitrina.ru) → Xray. So a single domain
|
||||
# pubray1.zeroq.su serves both panel and Xray — the SNI in the
|
||||
# TLS ClientHello disambiguates.
|
||||
map $ssl_preread_server_name $sni_backend {
|
||||
default xray;
|
||||
pubray1.zeroq.su panel;
|
||||
}
|
||||
map $ssl_preread_server_name $sni_backend {
|
||||
default xray;
|
||||
pubray1.zeroq.su panel;
|
||||
}
|
||||
|
||||
upstream panel {
|
||||
server 127.0.0.1:2049;
|
||||
}
|
||||
upstream panel {
|
||||
server 127.0.0.1:2049;
|
||||
}
|
||||
|
||||
upstream xray {
|
||||
server 127.0.0.1:15380;
|
||||
}
|
||||
upstream xray {
|
||||
server 127.0.0.1:15380;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443;
|
||||
proxy_pass $sni_backend;
|
||||
proxy_timeout 600s;
|
||||
proxy_connect_timeout 5s;
|
||||
}
|
||||
server {
|
||||
listen 443;
|
||||
proxy_pass $sni_backend;
|
||||
proxy_timeout 600s;
|
||||
proxy_connect_timeout 5s;
|
||||
}
|
||||
'';
|
||||
in
|
||||
{
|
||||
users.users.nginx.extraGroups = [ "acme" ];
|
||||
services = {
|
||||
nginx = {
|
||||
enable = true;
|
||||
# No virtualHosts.<name>.locations for /, /subs/, /subsjs/, /clash/
|
||||
# anymore — port 443 is now owned by the stream block, and the
|
||||
# 3x-ui panel serves those paths itself once TLS is forwarded to it.
|
||||
# Recommended HTTP options retained (still apply to the port-80
|
||||
# ACME server block).
|
||||
recommendedGzipSettings = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedProxySettings = true;
|
||||
recommendedTlsSettings = true;
|
||||
virtualHosts = {
|
||||
# ACME only — nginx owns no HTTP server on 443 anymore. The
|
||||
# cert at /var/lib/acme/pubray1.zeroq.su/ is consumed by the
|
||||
# 3x-ui container (mounted into /root/cert/) so it can do its
|
||||
# own TLS termination on 2049. Don't add forceSSL here: that
|
||||
# would generate an HTTPS server block on 443 that conflicts
|
||||
# with the stream listener above.
|
||||
"pubray1.zeroq.su" = {
|
||||
enableACME = true;
|
||||
};
|
||||
};
|
||||
# Top-level nginx.conf snippet — `stream {}` lives outside `http {}`,
|
||||
# so it can't go through virtualHosts / appendHttpConfig. The NixOS
|
||||
# nginx module exposes appendConfig for this.
|
||||
appendConfig = streamConfig;
|
||||
};
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
recommendedGzipSettings = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedProxySettings = true;
|
||||
recommendedTlsSettings = true;
|
||||
# ACME only — 443 is owned by the stream block, not by http { server {} }.
|
||||
# Don't add forceSSL: it would generate an HTTPS server block that
|
||||
# conflicts with the stream listener.
|
||||
virtualHosts."pubray1.zeroq.su".enableACME = true;
|
||||
# Lands inside the auto-generated `stream {}` block.
|
||||
streamConfig = streamConfig;
|
||||
};
|
||||
security.acme = {
|
||||
acceptTerms = true;
|
||||
defaults = {
|
||||
email = "oqyude@gmail.com";
|
||||
};
|
||||
defaults.email = "oqyude@gmail.com";
|
||||
};
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
80
|
||||
443
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user