mirror of
https://github.com/oqyude/nixos.git
synced 2026-10-08 04:57:13 +03:00
Compare commits
7
Commits
0bbb19b429
...
e1d276097d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1d276097d | ||
|
|
7f5ea81f37 | ||
|
|
11af2c150a | ||
|
|
26e53e96bd | ||
|
|
0c2b45ea6f | ||
|
|
2cd636b6d4 | ||
|
|
2bc02c316d |
@@ -7,6 +7,30 @@
|
|||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
panel = "${xlib.dirs.services-nodes-folder}/${xlib.device.hostname}/3x-ui";
|
panel = "${xlib.dirs.services-nodes-folder}/${xlib.device.hostname}/3x-ui";
|
||||||
|
certDomain = xlib.services."3x-ui".certDomain or null;
|
||||||
|
certMounts =
|
||||||
|
if certDomain == null then [ ]
|
||||||
|
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"
|
||||||
|
];
|
||||||
|
# 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
|
in
|
||||||
{
|
{
|
||||||
virtualisation = {
|
virtualisation = {
|
||||||
@@ -30,11 +54,11 @@ in
|
|||||||
volumes = [
|
volumes = [
|
||||||
"${panel}/cert/:/root/cert:rw"
|
"${panel}/cert/:/root/cert:rw"
|
||||||
"${panel}/db/:/etc/x-ui:rw"
|
"${panel}/db/:/etc/x-ui:rw"
|
||||||
];
|
] ++ certMounts;
|
||||||
log-driver = "journald";
|
log-driver = "journald";
|
||||||
extraOptions = [
|
# Adding a new inbound through the 3x-ui panel on a port outside
|
||||||
"--network=host"
|
# the 14380-15379 range requires extending basePorts and rebuilding.
|
||||||
];
|
ports = basePorts ++ realityPorts;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -42,21 +66,12 @@ in
|
|||||||
systemd = {
|
systemd = {
|
||||||
services = {
|
services = {
|
||||||
"podman-3xui_app" = {
|
"podman-3xui_app" = {
|
||||||
serviceConfig = {
|
serviceConfig.Restart = lib.mkOverride 90 "always";
|
||||||
Restart = lib.mkOverride 90 "always";
|
partOf = [ "podman-compose-3x-ui-root.target" ];
|
||||||
|
wantedBy = [ "podman-compose-3x-ui-root.target" ];
|
||||||
};
|
};
|
||||||
partOf = [
|
|
||||||
"podman-compose-3x-ui-root.target"
|
|
||||||
];
|
|
||||||
wantedBy = [
|
|
||||||
"podman-compose-3x-ui-root.target"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
# Update
|
|
||||||
"podman-update-3xui_app" = {
|
"podman-update-3xui_app" = {
|
||||||
path = [
|
path = [ pkgs.podman ];
|
||||||
pkgs.podman
|
|
||||||
];
|
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "oneshot";
|
Type = "oneshot";
|
||||||
TimeoutSec = 300;
|
TimeoutSec = 300;
|
||||||
@@ -66,29 +81,10 @@ in
|
|||||||
systemctl restart podman-3xui_app.service
|
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
|
# Starts/stops together with all 3x-ui compose resources.
|
||||||
# When started, this will automatically create all resources and start
|
|
||||||
# the containers. When stopped, this will teardown all resources.
|
|
||||||
targets."podman-compose-3x-ui-root" = {
|
targets."podman-compose-3x-ui-root" = {
|
||||||
unitConfig = {
|
unitConfig.Description = "Root target generated by compose2nix.";
|
||||||
Description = "Root target generated by compose2nix.";
|
|
||||||
};
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
};
|
};
|
||||||
timers."podman-update-3xui_app" = {
|
timers."podman-update-3xui_app" = {
|
||||||
@@ -98,15 +94,15 @@ in
|
|||||||
Persistent = true;
|
Persistent = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
# Folders
|
|
||||||
tmpfiles.rules = [
|
tmpfiles.rules = [
|
||||||
"d ${xlib.dirs.services-mnt-folder} 0755 root root -"
|
(xlib.helpers.mkTmpfile "d" xlib.dirs.services-mnt-folder "0755" "root" "root")
|
||||||
"d ${xlib.dirs.services-nodes-folder} 0755 root root -"
|
(xlib.helpers.mkTmpfile "d" xlib.dirs.services-nodes-folder "0755" "root" "root")
|
||||||
"d ${xlib.dirs.services-nodes-folder}/${xlib.device.hostname} 0755 root root -"
|
(xlib.helpers.mkTmpfile "d" "${xlib.dirs.services-nodes-folder}/${xlib.device.hostname}" "0755" "root" "root")
|
||||||
"d ${panel} 0755 root root -"
|
(xlib.helpers.mkTmpfile "d" panel "0755" "root" "root")
|
||||||
"d ${panel}/db 0755 root root -"
|
(xlib.helpers.mkTmpfile "d" "${panel}/db" "0755" "root" "root")
|
||||||
"d ${panel}/cert 0755 root root -"
|
(xlib.helpers.mkTmpfile "d" "${panel}/cert" "0755" "root" "root")
|
||||||
"Z ${panel} 0755 root root -"
|
# Relabel panel dir for SELinux so containers can access it.
|
||||||
|
(xlib.helpers.mkTmpfile "Z" panel "0755" "root" "root")
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,35 @@ in
|
|||||||
default = helpers;
|
default = helpers;
|
||||||
description = "Shared helper functions (see lib/xlib.nix).";
|
description = "Shared helper functions (see lib/xlib.nix).";
|
||||||
};
|
};
|
||||||
|
services."3x-ui" = {
|
||||||
|
# Domain whose Let's Encrypt cert (at /var/lib/acme/<domain>/)
|
||||||
|
# gets mounted read-only into the 3x-ui container so the panel
|
||||||
|
# can terminate TLS itself. Set null if 3x-ui serves plain HTTP
|
||||||
|
# and TLS is terminated by an upstream nginx.
|
||||||
|
certDomain = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
example = "pubray1.zeroq.su";
|
||||||
|
description = ''
|
||||||
|
Domain whose LE cert should be mounted into the 3x-ui
|
||||||
|
container at /root/cert/fullchain.pem and key.pem.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
# Publish host:15380 → container:443. Only nodes that host an
|
||||||
|
# Xray REALITY inbound on container:443 need this (so nginx
|
||||||
|
# stream can forward TLS to Xray via 127.0.0.1:15380 while Xray
|
||||||
|
# itself sees incoming connections on its configured port 443).
|
||||||
|
# Set false on nodes that only run the 3x-ui panel.
|
||||||
|
reality443Forwarding = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
When true, publish host:15380 → container:443 so Xray
|
||||||
|
inside the container can serve REALITY on its real
|
||||||
|
configured port 443 (nginx stream forwards 443 → 15380).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,12 @@
|
|||||||
# ./trilium.nix
|
# ./trilium.nix
|
||||||
# ./zerotier.nix
|
# ./zerotier.nix
|
||||||
];
|
];
|
||||||
|
# Server's 3x-ui is the controller panel at x.zeroq.su (nginx HTTP
|
||||||
|
# terminates TLS upstream, no SNI-routing on 443 needed here because
|
||||||
|
# there are other vhosts on the same port). Cert is still mounted in
|
||||||
|
# case 3x-ui is later reconfigured to terminate TLS itself (e.g. for
|
||||||
|
# direct node-API access); nginx doesn't have to use it.
|
||||||
|
xlib.services."3x-ui".certDomain = "x.zeroq.su";
|
||||||
systemd.tmpfiles.rules = [
|
systemd.tmpfiles.rules = [
|
||||||
(xlib.helpers.mkTmpfile "d" "/mnt" "0755" "root" "root")
|
(xlib.helpers.mkTmpfile "d" "/mnt" "0755" "root" "root")
|
||||||
(xlib.helpers.mkTmpfile "d" xlib.dirs.services-mnt-folder "0755" "root" "root")
|
(xlib.helpers.mkTmpfile "d" xlib.dirs.services-mnt-folder "0755" "root" "root")
|
||||||
|
|||||||
+15
-213
@@ -5,17 +5,18 @@
|
|||||||
xlib,
|
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
|
let
|
||||||
server = "192.168.1.20";
|
server = "192.168.1.20";
|
||||||
|
|
||||||
# Standard TLS proxy vhost: "/" -> http://server:port
|
|
||||||
# Returns { name = domain; value = vhost; } for builtins.listToAttrs
|
|
||||||
mkProxy =
|
mkProxy =
|
||||||
{
|
{
|
||||||
domain,
|
domain,
|
||||||
port,
|
port,
|
||||||
addSSL ? false,
|
addSSL ? false,
|
||||||
body ? false,
|
extraConfig ? "",
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
name = domain;
|
name = domain;
|
||||||
@@ -28,20 +29,17 @@ let
|
|||||||
}
|
}
|
||||||
// lib.optionalAttrs (!addSSL) { forceSSL = true; }
|
// lib.optionalAttrs (!addSSL) { forceSSL = true; }
|
||||||
// lib.optionalAttrs addSSL { addSSL = true; }
|
// lib.optionalAttrs addSSL { addSSL = true; }
|
||||||
// lib.optionalAttrs body {
|
// lib.optionalAttrs (extraConfig != "") { inherit extraConfig; };
|
||||||
extraConfig = ''
|
|
||||||
client_max_body_size 5G;
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
# Simple proxy sites
|
bigUploads = "client_max_body_size 5G;";
|
||||||
|
|
||||||
sites = [
|
sites = [
|
||||||
{
|
{
|
||||||
domain = "immich.zeroq.su";
|
domain = "immich.zeroq.su";
|
||||||
port = 2283;
|
port = 2283;
|
||||||
addSSL = true;
|
addSSL = true;
|
||||||
body = true;
|
extraConfig = bigUploads;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
domain = "kuma.zeroq.su";
|
domain = "kuma.zeroq.su";
|
||||||
@@ -71,27 +69,22 @@ let
|
|||||||
{
|
{
|
||||||
domain = "calibre.zeroq.su";
|
domain = "calibre.zeroq.su";
|
||||||
port = 8083;
|
port = 8083;
|
||||||
body = true;
|
extraConfig = bigUploads;
|
||||||
}
|
}
|
||||||
# {
|
|
||||||
# domain = "mc.zeroq.su";
|
|
||||||
# port = 25565;
|
|
||||||
# }
|
|
||||||
{
|
{
|
||||||
domain = "nix-cache.zeroq.su";
|
domain = "nix-cache.zeroq.su";
|
||||||
port = 5000;
|
port = 5000;
|
||||||
body = true;
|
extraConfig = bigUploads;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
domain = "pdf.zeroq.su";
|
domain = "pdf.zeroq.su";
|
||||||
port = 8446;
|
port = 8446;
|
||||||
body = true;
|
extraConfig = bigUploads;
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
services = {
|
services.nginx = {
|
||||||
nginx = {
|
|
||||||
enable = true;
|
enable = true;
|
||||||
recommendedGzipSettings = true;
|
recommendedGzipSettings = true;
|
||||||
recommendedOptimisation = true;
|
recommendedOptimisation = true;
|
||||||
@@ -141,9 +134,7 @@ in
|
|||||||
port = 8446;
|
port = 8446;
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
extraConfig = ''
|
extraConfig = bigUploads;
|
||||||
client_max_body_size 5G;
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
"x.zeroq.su" = {
|
"x.zeroq.su" = {
|
||||||
forceSSL = true;
|
forceSSL = true;
|
||||||
@@ -167,109 +158,6 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
# "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" = {
|
"zeroq.su" = {
|
||||||
forceSSL = true;
|
forceSSL = true;
|
||||||
enableACME = true;
|
enableACME = true;
|
||||||
@@ -281,18 +169,10 @@ in
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
'';
|
'';
|
||||||
locations = {
|
locations."/guest/" = {
|
||||||
"/guest/" = {
|
|
||||||
proxyPass = "http://${server}:80";
|
proxyPass = "http://${server}:80";
|
||||||
proxyWebsockets = true;
|
proxyWebsockets = true;
|
||||||
};
|
};
|
||||||
# "/.well-known/discord" = {
|
|
||||||
# extraConfig = ''
|
|
||||||
# default_type text/plain;
|
|
||||||
# return 200 "dh=c2d103553a4cfdaa1b7952a87a7d8120a1e167cc";
|
|
||||||
# '';
|
|
||||||
# };
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
"vetymae.opencodes.zeroq.su" = {
|
"vetymae.opencodes.zeroq.su" = {
|
||||||
forceSSL = true;
|
forceSSL = true;
|
||||||
@@ -310,24 +190,6 @@ in
|
|||||||
proxyWebsockets = true;
|
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" = {
|
"nextcloud.zeroq.su" = {
|
||||||
forceSSL = true;
|
forceSSL = true;
|
||||||
enableACME = true;
|
enableACME = true;
|
||||||
@@ -341,67 +203,7 @@ in
|
|||||||
proxyWebsockets = true;
|
proxyWebsockets = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
extraConfig = ''
|
extraConfig = bigUploads;
|
||||||
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;
|
|
||||||
# }
|
|
||||||
# ];
|
|
||||||
# };
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,6 +13,12 @@
|
|||||||
# ./netbird.nix
|
# ./netbird.nix
|
||||||
# ./xray.nix
|
# ./xray.nix
|
||||||
];
|
];
|
||||||
|
# VDS hosts the public-facing Xray REALITY inbound on container:443,
|
||||||
|
# fronted by nginx stream on host:443 → host:15380 → container:443.
|
||||||
|
xlib.services."3x-ui" = {
|
||||||
|
certDomain = "pubray1.zeroq.su";
|
||||||
|
reality443Forwarding = true;
|
||||||
|
};
|
||||||
systemd.tmpfiles.rules = [
|
systemd.tmpfiles.rules = [
|
||||||
(xlib.helpers.mkTmpfile "d" "/mnt" "0755" "root" "root")
|
(xlib.helpers.mkTmpfile "d" "/mnt" "0755" "root" "root")
|
||||||
(xlib.helpers.mkTmpfile "d" xlib.dirs.services-mnt-folder "0755" "root" "root")
|
(xlib.helpers.mkTmpfile "d" xlib.dirs.services-mnt-folder "0755" "root" "root")
|
||||||
|
|||||||
+51
-31
@@ -4,49 +4,69 @@
|
|||||||
pkgs,
|
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
|
let
|
||||||
server = "100.64.0.0";
|
# 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 = ''
|
||||||
|
ssl_preread on;
|
||||||
|
|
||||||
|
map $ssl_preread_server_name $sni_backend {
|
||||||
|
default xray;
|
||||||
|
pubray1.zeroq.su panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream panel {
|
||||||
|
server 127.0.0.1:2049;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream xray {
|
||||||
|
server 127.0.0.1:15380;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443;
|
||||||
|
proxy_pass $sni_backend;
|
||||||
|
proxy_timeout 600s;
|
||||||
|
proxy_connect_timeout 5s;
|
||||||
|
}
|
||||||
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
users.users.nginx.extraGroups = [ "acme" ];
|
users.users.nginx.extraGroups = [ "acme" ];
|
||||||
services = {
|
services.nginx = {
|
||||||
nginx = {
|
|
||||||
enable = true;
|
enable = true;
|
||||||
recommendedGzipSettings = true;
|
recommendedGzipSettings = true;
|
||||||
recommendedOptimisation = true;
|
recommendedOptimisation = true;
|
||||||
recommendedProxySettings = true;
|
recommendedProxySettings = true;
|
||||||
recommendedTlsSettings = true;
|
recommendedTlsSettings = true;
|
||||||
virtualHosts = {
|
# ACME only — 443 is owned by the stream block, not by http { server {} }.
|
||||||
"pubray1.zeroq.su" = {
|
# Don't add forceSSL: it would generate an HTTPS server block that
|
||||||
forceSSL = true;
|
# conflicts with the stream listener.
|
||||||
enableACME = true;
|
virtualHosts."pubray1.zeroq.su".enableACME = true;
|
||||||
locations = {
|
# Lands inside the auto-generated `stream {}` block.
|
||||||
"/" = {
|
streamConfig = streamConfig;
|
||||||
proxyPass = "http://localhost:2049";
|
|
||||||
proxyWebsockets = true;
|
|
||||||
};
|
|
||||||
"/subs/" = {
|
|
||||||
proxyPass = "http://localhost:2096";
|
|
||||||
proxyWebsockets = true;
|
|
||||||
};
|
|
||||||
"/subsjs/" = {
|
|
||||||
proxyPass = "http://localhost:2096";
|
|
||||||
proxyWebsockets = true;
|
|
||||||
};
|
|
||||||
"/clash/" = {
|
|
||||||
proxyPass = "http://localhost:2096";
|
|
||||||
proxyWebsockets = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
security.acme = {
|
security.acme = {
|
||||||
acceptTerms = true;
|
acceptTerms = true;
|
||||||
defaults = {
|
defaults.email = "oqyude@gmail.com";
|
||||||
email = "oqyude@gmail.com";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
networking.firewall.allowedTCPPorts = [
|
networking.firewall.allowedTCPPorts = [
|
||||||
80
|
80
|
||||||
|
|||||||
Reference in New Issue
Block a user