diff --git a/.env.example b/.env.example index 4f5fb79c..32e437e5 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,7 @@ DOCKER_DEFAULT_PLATFORM=linux/amd64 COMPOSE_PROJECT_NAME=3x BUILD_WITH_ANTIZAPRET="0" +HOSTNAME="3x-ui" XUI_SERVER_IP="" XUI_PANEL_DOMAIN="" XUI_SUB_DOMAIN="" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 84a00cf6..e2ce07ae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -83,7 +83,7 @@ jobs: cd x-ui/bin # Download dependencies - Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v24.12.15/" + Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v24.12.18/" if [ "${{ matrix.platform }}" == "amd64" ]; then wget ${Xray_URL}Xray-linux-64.zip unzip Xray-linux-64.zip diff --git a/DockerInit.sh b/DockerInit.sh index 4493050c..38cd9d7b 100755 --- a/DockerInit.sh +++ b/DockerInit.sh @@ -27,7 +27,7 @@ case $1 in esac mkdir -p build/bin cd build/bin -wget "https://github.com/XTLS/Xray-core/releases/download/v24.12.15/Xray-linux-${ARCH}.zip" +wget "https://github.com/XTLS/Xray-core/releases/download/v24.12.18/Xray-linux-${ARCH}.zip" unzip "Xray-linux-${ARCH}.zip" rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat mv xray "xray-linux-${FNAME}" diff --git a/README.es_ES.md b/README.es_ES.md index 1efdddf0..1b158589 100644 --- a/README.es_ES.md +++ b/README.es_ES.md @@ -569,7 +569,8 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go - + + ## Un agradecimiento especial a @@ -583,4 +584,4 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go ## Estrellas a lo largo del tiempo -[](https://starchart.cc/MHSanaei/3x-ui) +[](https://starchart.cc/MHSanaei/3x-ui) \ No newline at end of file diff --git a/README.md b/README.md index 353dcef3..24b18568 100644 --- a/README.md +++ b/README.md @@ -566,7 +566,8 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go - + + ## A Special Thanks to diff --git a/README.ru_RU.md b/README.ru_RU.md index 4444ecce..464c497d 100644 --- a/README.ru_RU.md +++ b/README.ru_RU.md @@ -564,7 +564,8 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go - + + ## Особая благодарность diff --git a/README.zh_CN.md b/README.zh_CN.md index 5727a88e..bc51535f 100644 --- a/README.zh_CN.md +++ b/README.zh_CN.md @@ -557,7 +557,8 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go - + + ## 特别感谢 diff --git a/config/version b/config/version index 15834981..a6c4b4a2 100644 --- a/config/version +++ b/config/version @@ -1 +1 @@ -2.4.9 \ No newline at end of file +2.4.10 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index b70125e6..565c6820 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,31 +9,36 @@ services: reservations: memory: 256M container_name: 3x-ui - hostname: 3x-ui + hostname: ${HOSTNAME:-3x-ui} networks: traefik: labels: - "traefik.enable=true" - "traefik.http.routers.3x-ui.rule=Host(`${XUI_PANEL_DOMAIN}`)" + - "traefik.http.routers.3x-ui.middlewares=cf-x-real-ip@file" + - "traefik.http.routers.3x-ui.entrypoints=websecure" - "traefik.http.routers.3x-ui.service=3x-ui" - - "traefik.http.routers.3x-ui.entrypoints=https" - "traefik.http.services.3x-ui.loadbalancer.server.port=2053" # - "traefik.http.routers.3x-ui-sub.rule=Host(`${XUI_SUB_DOMAIN}`)" + - "traefik.http.routers.3x-ui-sub.middlewares=cf-x-real-ip@file" + - "traefik.http.routers.3x-ui-sub.entrypoints=websecure" - "traefik.http.routers.3x-ui-sub.service=3x-ui-sub" - - "traefik.http.routers.3x-ui-sub.entrypoints=https" - "traefik.http.services.3x-ui-sub.loadbalancer.server.port=2096" # - - "traefik.tcp.routers.vless-reality.rule=HostSNI(`${XUI_VLESS_REALITY_SNI}`)" + - "traefik.tcp.routers.vless-reality.rule=HostSNI(`${XUI_VLESS_REALITY_SNI}`) || HostSNI(`www.${XUI_VLESS_REALITY_SNI}`)" - "traefik.tcp.routers.vless-reality.tls.passthrough=true" + - "traefik.tcp.routers.vless-reality.entrypoints=websecure" - "traefik.tcp.routers.vless-reality.service=3x-ui-inbound-443" - "traefik.tcp.services.3x-ui-inbound-443.loadbalancer.server.port=443" + - "traefik.tcp.services.3x-ui-inbound-443.loadbalancer.proxyprotocol.version=2" # - "traefik.tcp.routers.vless-grpc.rule=HostSNI(`${XUI_VLESS_GRPC_SNI}`)" - "traefik.tcp.routers.vless-grpc.tls.passthrough=true" - - "traefik.tcp.routers.vless-grpc.entrypoints=https" + - "traefik.tcp.routers.vless-grpc.entrypoints=websecure" - "traefik.tcp.routers.vless-grpc.service=3x-ui-inbound-8888" - "traefik.tcp.services.3x-ui-inbound-8888.loadbalancer.server.port=8888" + - "traefik.tcp.services.3x-ui-inbound-8888.loadbalancer.proxyprotocol.version=2" volumes: - ./db/:/etc/x-ui/ - ./db/fail2ban.sqlite3:/var/lib/fail2ban/fail2ban.sqlite3 diff --git a/go.mod b/go.mod index 9e2cfa03..537bb9cd 100644 --- a/go.mod +++ b/go.mod @@ -14,10 +14,10 @@ require ( github.com/robfig/cron/v3 v3.0.1 github.com/shirou/gopsutil/v4 v4.24.11 github.com/valyala/fasthttp v1.58.0 - github.com/xtls/xray-core v1.8.25-0.20241215123619-7d0a80b501d4 + github.com/xtls/xray-core v1.8.25-0.20241218133935-cab2fdefd321 go.uber.org/atomic v1.11.0 golang.org/x/text v0.21.0 - google.golang.org/grpc v1.69.0 + google.golang.org/grpc v1.69.2 gorm.io/driver/sqlite v1.5.7 gorm.io/gorm v1.25.12 ) @@ -86,17 +86,17 @@ require ( go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect golang.org/x/arch v0.12.0 // indirect golang.org/x/crypto v0.31.0 // indirect - golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e // indirect + golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/net v0.32.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/time v0.8.0 // indirect golang.org/x/tools v0.28.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect - google.golang.org/protobuf v1.35.2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 // indirect + google.golang.org/protobuf v1.36.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 // indirect lukechampine.com/blake3 v1.3.0 // indirect diff --git a/go.sum b/go.sum index 5b7c2318..ce8df966 100644 --- a/go.sum +++ b/go.sum @@ -191,8 +191,8 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463 h1:g1Cj7d+my6k/HHxLAyxPwyX8i7FGRr6ulBDMkBzg2BM= github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463/go.mod h1:BjIOLmkEEtAgloAiVUcYj0Mt+YU00JARZw8AEU0IwAg= -github.com/xtls/xray-core v1.8.25-0.20241215123619-7d0a80b501d4 h1:zdd86FEjFZjAaRbWxiZQM2QPOzk/d6cig2DaE7c3MDQ= -github.com/xtls/xray-core v1.8.25-0.20241215123619-7d0a80b501d4/go.mod h1:lduNPDkXku+Avphl8g7W0yJrHhWyxdOnPo0XGYdF0Aw= +github.com/xtls/xray-core v1.8.25-0.20241218133935-cab2fdefd321 h1:vk+n1RmfhFCj5xSi4I6C3USpcUQ48H3lt/QrtARVz1M= +github.com/xtls/xray-core v1.8.25-0.20241218133935-cab2fdefd321/go.mod h1:DCaUwrBk1RIC7hWg/wGoAynE69g3ptua1sEr8i0BWxA= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= @@ -217,12 +217,12 @@ golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg= golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e h1:4qufH0hlUYs6AO6XmZC3GqfDPGSXHVXUFR6OND+iJX4= -golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo= +golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= -golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -243,12 +243,12 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= -google.golang.org/grpc v1.69.0 h1:quSiOM1GJPmPH5XtU+BCoVXcDVJJAzNcoyfC2cCjGkI= -google.golang.org/grpc v1.69.0/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= -google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= -google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 h1:Z7FRVJPSMaHQxD0uXU8WdgFh8PseLM8Q8NzhnpMrBhQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= +google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= +google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= +google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ= +google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/install.sh b/install.sh index c6c52000..150ae86c 100644 --- a/install.sh +++ b/install.sh @@ -2,6 +2,7 @@ red='\033[0;31m' green='\033[0;32m' +blue='\033[0;34m' yellow='\033[0;33m' plain='\033[0m' @@ -260,24 +261,24 @@ install_x-ui() { systemctl start x-ui echo -e "${green}x-ui ${tag_version}${plain} installation finished, it is running now..." echo -e "" - echo -e "x-ui control menu usages: " - echo -e "----------------------------------------------" - echo -e "SUBCOMMANDS:" - echo -e "x-ui - Admin Management Script" - echo -e "x-ui start - Start" - echo -e "x-ui stop - Stop" - echo -e "x-ui restart - Restart" - echo -e "x-ui status - Current Status" - echo -e "x-ui settings - Current Settings" - echo -e "x-ui enable - Enable Autostart on OS Startup" - echo -e "x-ui disable - Disable Autostart on OS Startup" - echo -e "x-ui log - Check logs" - echo -e "x-ui banlog - Check Fail2ban ban logs" - echo -e "x-ui update - Update" - echo -e "x-ui legacy - legacy version" - echo -e "x-ui install - Install" - echo -e "x-ui uninstall - Uninstall" - echo -e "----------------------------------------------" + echo -e "┌───────────────────────────────────────────────────────┐ +│ ${blue}x-ui control menu usages (subcommands):${plain} │ +│ │ +│ ${blue}x-ui${plain} - Admin Management Script │ +│ ${blue}x-ui start${plain} - Start │ +│ ${blue}x-ui stop${plain} - Stop │ +│ ${blue}x-ui restart${plain} - Restart │ +│ ${blue}x-ui status${plain} - Current Status │ +│ ${blue}x-ui settings${plain} - Current Settings │ +│ ${blue}x-ui enable${plain} - Enable Autostart on OS Startup │ +│ ${blue}x-ui disable${plain} - Disable Autostart on OS Startup │ +│ ${blue}x-ui log${plain} - Check logs │ +│ ${blue}x-ui banlog${plain} - Check Fail2ban ban logs │ +│ ${blue}x-ui update${plain} - Update │ +│ ${blue}x-ui legacy${plain} - legacy version │ +│ ${blue}x-ui install${plain} - Install │ +│ ${blue}x-ui uninstall${plain} - Uninstall │ +└───────────────────────────────────────────────────────┘" } echo -e "${green}Running...${plain}" diff --git a/media/07-bot-dark.png b/media/07-bot-dark.png new file mode 100644 index 00000000..f2912561 Binary files /dev/null and b/media/07-bot-dark.png differ diff --git a/media/07-bot-light.png b/media/07-bot-light.png new file mode 100644 index 00000000..f22a2b7f Binary files /dev/null and b/media/07-bot-light.png differ diff --git a/media/7.png b/media/7.png deleted file mode 100644 index 4f12ceaf..00000000 Binary files a/media/7.png and /dev/null differ diff --git a/web/assets/js/model/inbound.js b/web/assets/js/model/inbound.js index a36b9db2..44c8d767 100644 --- a/web/assets/js/model/inbound.js +++ b/web/assets/js/model/inbound.js @@ -34,10 +34,6 @@ const TLS_VERSION_OPTION = { }; const TLS_CIPHER_OPTION = { - RSA_AES_128_CBC: "TLS_RSA_WITH_AES_128_CBC_SHA", - RSA_AES_256_CBC: "TLS_RSA_WITH_AES_256_CBC_SHA", - RSA_AES_128_GCM: "TLS_RSA_WITH_AES_128_GCM_SHA256", - RSA_AES_256_GCM: "TLS_RSA_WITH_AES_256_GCM_SHA384", AES_128_GCM: "TLS_AES_128_GCM_SHA256", AES_256_GCM: "TLS_AES_256_GCM_SHA384", CHACHA20_POLY1305: "TLS_CHACHA20_POLY1305_SHA256", @@ -64,6 +60,7 @@ const UTLS_FINGERPRINT = { UTLS_QQ: "qq", UTLS_RANDOM: "random", UTLS_RANDOMIZED: "randomized", + UTLS_UNSAFE: "unsafe", }; const ALPN_OPTION = { @@ -496,19 +493,9 @@ class xHTTPStreamSettings extends XrayCommonClass { headers = [], scMaxBufferedPosts = 30, scMaxEachPostBytes = "1000000", - scMinPostsIntervalMs = "30", noSSEHeader = false, xPaddingBytes = "100-1000", - xmux = { - maxConcurrency: "16-32", - maxConnections: 0, - cMaxReuseTimes: "64-128", - cMaxLifetimeMs: 0, - hMaxRequestTimes: "800-900", - hKeepAlivePeriod: 0, - }, mode = MODE_OPTION.AUTO, - noGRPCHeader = false ) { super(); this.path = path; @@ -516,12 +503,9 @@ class xHTTPStreamSettings extends XrayCommonClass { this.headers = headers; this.scMaxBufferedPosts = scMaxBufferedPosts; this.scMaxEachPostBytes = scMaxEachPostBytes; - this.scMinPostsIntervalMs = scMinPostsIntervalMs; this.noSSEHeader = noSSEHeader; this.xPaddingBytes = xPaddingBytes; - this.xmux = xmux; this.mode = mode; - this.noGRPCHeader = noGRPCHeader; } addHeader(name, value) { @@ -539,12 +523,9 @@ class xHTTPStreamSettings extends XrayCommonClass { XrayCommonClass.toHeaders(json.headers), json.scMaxBufferedPosts, json.scMaxEachPostBytes, - json.scMinPostsIntervalMs, json.noSSEHeader, json.xPaddingBytes, - json.xmux, json.mode, - json.noGRPCHeader, ); } @@ -555,19 +536,9 @@ class xHTTPStreamSettings extends XrayCommonClass { headers: XrayCommonClass.toV2Headers(this.headers, false), scMaxBufferedPosts: this.scMaxBufferedPosts, scMaxEachPostBytes: this.scMaxEachPostBytes, - scMinPostsIntervalMs: this.scMinPostsIntervalMs, noSSEHeader: this.noSSEHeader, xPaddingBytes: this.xPaddingBytes, - xmux: { - maxConcurrency: this.xmux.maxConcurrency, - maxConnections: this.xmux.maxConnections, - cMaxReuseTimes: this.xmux.cMaxReuseTimes, - cMaxLifetimeMs: this.xmux.cMaxLifetimeMs, - hMaxRequestTimes: this.xmux.hMaxRequestTimes, - hKeepAlivePeriod: this.xmux.hKeepAlivePeriod, - }, mode: this.mode, - noGRPCHeader: this.noGRPCHeader, }; } } @@ -718,7 +689,10 @@ TlsStreamSettings.Cert = class extends XrayCommonClass { }; TlsStreamSettings.Settings = class extends XrayCommonClass { - constructor(allowInsecure = false, fingerprint = '') { + constructor( + allowInsecure = false, + fingerprint = UTLS_FINGERPRINT.UTLS_CHROME, + ) { super(); this.allowInsecure = allowInsecure; this.fingerprint = fingerprint; @@ -807,7 +781,7 @@ class RealityStreamSettings extends XrayCommonClass { RealityStreamSettings.Settings = class extends XrayCommonClass { constructor( publicKey = '', - fingerprint = UTLS_FINGERPRINT.UTLS_RANDOM, + fingerprint = UTLS_FINGERPRINT.UTLS_CHROME, serverName = '', spiderX = '/' ) { diff --git a/web/assets/js/model/outbound.js b/web/assets/js/model/outbound.js index 7a53383c..eb4d7f71 100644 --- a/web/assets/js/model/outbound.js +++ b/web/assets/js/model/outbound.js @@ -39,6 +39,7 @@ const UTLS_FINGERPRINT = { UTLS_QQ: "qq", UTLS_RANDOM: "random", UTLS_RANDOMIZED: "randomized", + UTLS_UNSAFE: "unsafe", }; const ALPN_OPTION = { @@ -287,11 +288,24 @@ class xHTTPStreamSettings extends CommonClass { path = '/', host = '', mode = '', + noGRPCHeader = false, + scMinPostsIntervalMs = "30", + xmux = { + maxConcurrency: "16-32", + maxConnections: 0, + cMaxReuseTimes: "64-128", + cMaxLifetimeMs: 0, + hMaxRequestTimes: "800-900", + hKeepAlivePeriod: 0, + }, ) { super(); this.path = path; this.host = host; this.mode = mode; + this.noGRPCHeader = noGRPCHeader; + this.scMinPostsIntervalMs = scMinPostsIntervalMs; + this.xmux = xmux; } static fromJson(json = {}) { @@ -299,6 +313,9 @@ class xHTTPStreamSettings extends CommonClass { json.path, json.host, json.mode, + json.noGRPCHeader, + json.scMinPostsIntervalMs, + json.xmux ); } @@ -307,6 +324,16 @@ class xHTTPStreamSettings extends CommonClass { path: this.path, host: this.host, mode: this.mode, + noGRPCHeader: this.noGRPCHeader, + scMinPostsIntervalMs: this.scMinPostsIntervalMs, + xmux: { + maxConcurrency: this.xmux.maxConcurrency, + maxConnections: this.xmux.maxConnections, + cMaxReuseTimes: this.xmux.cMaxReuseTimes, + cMaxLifetimeMs: this.xmux.cMaxLifetimeMs, + hMaxRequestTimes: this.xmux.hMaxRequestTimes, + hKeepAlivePeriod: this.xmux.hKeepAlivePeriod, + }, }; } } diff --git a/web/controller/index.go b/web/controller/index.go index c74b6fb1..9af4ed7f 100644 --- a/web/controller/index.go +++ b/web/controller/index.go @@ -9,6 +9,7 @@ import ( "x-ui/web/service" "x-ui/web/session" + "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" ) @@ -49,8 +50,8 @@ func (a *IndexController) index(c *gin.Context) { func (a *IndexController) login(c *gin.Context) { var form LoginForm - err := c.ShouldBind(&form) - if err != nil { + + if err := c.ShouldBind(&form); err != nil { pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.invalidFormData")) return } @@ -68,29 +69,31 @@ func (a *IndexController) login(c *gin.Context) { safeUser := template.HTMLEscapeString(form.Username) safePass := template.HTMLEscapeString(form.Password) safeSecret := template.HTMLEscapeString(form.LoginSecret) + if user == nil { logger.Warningf("wrong username: \"%s\", password: \"%s\", secret: \"%s\", IP: \"%s\"", safeUser, safePass, safeSecret, getRemoteIp(c)) a.tgbot.UserLoginNotify(safeUser, safePass, getRemoteIp(c), timeStr, 0) pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword")) return - } else { - logger.Infof("%s logged in successfully, Ip Address: %s\n", safeUser, getRemoteIp(c)) - a.tgbot.UserLoginNotify(safeUser, ``, getRemoteIp(c), timeStr, 1) } + logger.Infof("%s logged in successfully, Ip Address: %s\n", safeUser, getRemoteIp(c)) + a.tgbot.UserLoginNotify(safeUser, ``, getRemoteIp(c), timeStr, 1) + sessionMaxAge, err := a.settingService.GetSessionMaxAge() if err != nil { logger.Warning("Unable to get session's max age from DB") } - err = session.SetMaxAge(c, sessionMaxAge*60) - if err != nil { - logger.Warning("Unable to set session's max age") + session.SetMaxAge(c, sessionMaxAge*60) + session.SetLoginUser(c, user) + if err := sessions.Default(c).Save(); err != nil { + logger.Warning("Unable to save session: ", err) + return } - err = session.SetLoginUser(c, user) - logger.Infof("%s logged in successfully", user.Username) - jsonMsg(c, I18nWeb(c, "pages.login.toasts.successLogin"), err) + logger.Infof("%s logged in successfully", safeUser) + jsonMsg(c, I18nWeb(c, "pages.login.toasts.successLogin"), nil) } func (a *IndexController) logout(c *gin.Context) { @@ -99,6 +102,9 @@ func (a *IndexController) logout(c *gin.Context) { logger.Infof("%s logged out successfully", user.Username) } session.ClearSession(c) + if err := sessions.Default(c).Save(); err != nil { + logger.Warning("Unable to save session after clearing:", err) + } c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path")) } diff --git a/web/html/xui/form/outbound.html b/web/html/xui/form/outbound.html index fb9e6f41..0293ffa8 100644 --- a/web/html/xui/form/outbound.html +++ b/web/html/xui/form/outbound.html @@ -377,6 +377,30 @@ [[ key ]] + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/html/xui/form/stream/stream_xhttp.html b/web/html/xui/form/stream/stream_xhttp.html index 1dd3c15c..749bfcee 100644 --- a/web/html/xui/form/stream/stream_xhttp.html +++ b/web/html/xui/form/stream/stream_xhttp.html @@ -27,41 +27,17 @@ [[ key ]] - + - + - - - - - - - - - - - - - - - - - - - - - - - - {{end}} \ No newline at end of file diff --git a/web/html/xui/inbound_info_modal.html b/web/html/xui/inbound_info_modal.html index 7796cf1e..1e30aa83 100644 --- a/web/html/xui/inbound_info_modal.html +++ b/web/html/xui/inbound_info_modal.html @@ -185,6 +185,25 @@ ↑ [[ sizeFormat(infoModal.clientStats.up) ]] / [[ sizeFormat(infoModal.clientStats.down) ]] ↓ + + {{ i18n "pages.inbounds.IPLimit" }} + + [[ infoModal.clientSettings.limitIp ]] + + + + {{ i18n "pages.inbounds.IPLimitlog" }} + + [[ infoModal.clientIps ]] + + + + {{ i18n "pages.inbounds.IPLimitlogclear" }} + + + + + @@ -417,6 +436,18 @@ diff --git a/web/html/xui/xray.html b/web/html/xui/xray.html index 41cd8a39..4299f85c 100644 --- a/web/html/xui/xray.html +++ b/web/html/xui/xray.html @@ -961,6 +961,8 @@ { label: 'Google Gemini', value: 'geosite:google-gemini' }, { label: 'Perplexity', value: 'geosite:perplexity' }, { label: 'Booking', value: 'geosite:booking' }, + { label: 'JetBrains', value: 'geosite:jetbrains' }, + { label: 'Discord', value: 'geosite:discord' }, { label: 'X (Twitter)', value: 'geosite:x' }, { label: 'Category: Porn', value: 'geosite:category-porn' }, { label: 'Category: AI Chats', value: 'geosite:category-ai-chat-!cn' }, diff --git a/web/job/check_client_ip_job.go b/web/job/check_client_ip_job.go index 08899010..61f8b724 100644 --- a/web/job/check_client_ip_job.go +++ b/web/job/check_client_ip_job.go @@ -151,13 +151,13 @@ func (j *CheckClientIpJob) processLogFile() bool { } sort.Strings(ips) - inboundClientIps, err := j.getInboundClientIps(email) + clientIpsRecord, err := j.getInboundClientIps(email) if err != nil { j.addInboundClientIps(email, ips) continue } - shouldCleanLog = j.updateInboundClientIps(inboundClientIps, email, ips) || shouldCleanLog + shouldCleanLog = j.updateInboundClientIps(clientIpsRecord, email, ips) || shouldCleanLog } return shouldCleanLog @@ -309,12 +309,12 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun func (j *CheckClientIpJob) getInboundByEmail(clientEmail string) (*model.Inbound, error) { db := database.GetDB() - var inbounds *model.Inbound + inbound := &model.Inbound{} - err := db.Model(model.Inbound{}).Where("settings LIKE ?", "%"+clientEmail+"%").Find(&inbounds).Error + err := db.Model(&model.Inbound{}).Where("settings LIKE ?", "%"+clientEmail+"%").First(inbound).Error if err != nil { return nil, err } - return inbounds, nil + return inbound, nil } diff --git a/web/session/session.go b/web/session/session.go index f5055efd..13aedad8 100644 --- a/web/session/session.go +++ b/web/session/session.go @@ -10,38 +10,41 @@ import ( ) const ( - loginUser = "LOGIN_USER" - defaultPath = "/" + loginUserKey = "LOGIN_USER" + defaultPath = "/" ) func init() { gob.Register(model.User{}) } -func SetLoginUser(c *gin.Context, user *model.User) error { +func SetLoginUser(c *gin.Context, user *model.User) { + if user == nil { + return + } s := sessions.Default(c) - s.Set(loginUser, user) - return s.Save() + s.Set(loginUserKey, *user) } -func SetMaxAge(c *gin.Context, maxAge int) error { +func SetMaxAge(c *gin.Context, maxAge int) { s := sessions.Default(c) s.Options(sessions.Options{ Path: defaultPath, MaxAge: maxAge, HttpOnly: true, }) - return s.Save() } func GetLoginUser(c *gin.Context) *model.User { s := sessions.Default(c) - obj := s.Get(loginUser) + obj := s.Get(loginUserKey) if obj == nil { return nil } user, ok := obj.(model.User) if !ok { + + s.Delete(loginUserKey) return nil } return &user @@ -51,7 +54,7 @@ func IsLogin(c *gin.Context) bool { return GetLoginUser(c) != nil } -func ClearSession(c *gin.Context) error { +func ClearSession(c *gin.Context) { s := sessions.Default(c) s.Clear() s.Options(sessions.Options{ @@ -59,5 +62,4 @@ func ClearSession(c *gin.Context) error { MaxAge: -1, HttpOnly: true, }) - return s.Save() } diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml index aad4cd29..093a296a 100644 --- a/web/translation/translate.ru_RU.toml +++ b/web/translation/translate.ru_RU.toml @@ -41,7 +41,7 @@ "offline" = "Офлайн" "online" = "Онлайн" "domainName" = "Домен" -"monitor" = "Порт IP" +"monitor" = "Слушать IP" "certificate" = "Цифровой сертификат" "fail" = "Неудачно" "success" = "Успешно" diff --git a/x-ui.sh b/x-ui.sh index 3f58fb51..4062e7fa 100755 --- a/x-ui.sh +++ b/x-ui.sh @@ -2,6 +2,7 @@ red='\033[0;31m' green='\033[0;32m' +blue='\033[0;34m' yellow='\033[0;33m' plain='\033[0m' @@ -688,10 +689,12 @@ show_xray_status() { } firewall_menu() { - echo -e "${green}\t1.${plain} Install Firewall & open ports" - echo -e "${green}\t2.${plain} Allowed List" - echo -e "${green}\t3.${plain} Delete Ports from List" - echo -e "${green}\t4.${plain} Disable Firewall" + echo -e "${green}\t1.${plain} Install Firewall" + echo -e "${green}\t2.${plain} Port List" + echo -e "${green}\t3.${plain} Open Ports" + echo -e "${green}\t4.${plain} Delete Ports from List" + echo -e "${green}\t5.${plain} Disable Firewall" + echo -e "${green}\t6.${plain} Firewall Status" echo -e "${green}\t0.${plain} Back to Main Menu" read -p "Choose an option: " choice case "$choice" in @@ -699,19 +702,27 @@ firewall_menu() { show_menu ;; 1) - open_ports + install_firewall firewall_menu ;; 2) - sudo ufw status + ufw status numbered firewall_menu ;; 3) - delete_ports + open_ports firewall_menu ;; 4) - sudo ufw disable + delete_ports + firewall_menu + ;; + 5) + ufw disable + firewall_menu + ;; + 6) + ufw status verbose firewall_menu ;; *) @@ -721,7 +732,7 @@ firewall_menu() { esac } -open_ports() { +install_firewall() { if ! command -v ufw &>/dev/null; then echo "ufw firewall is not installed. Installing now..." apt-get update @@ -739,13 +750,16 @@ open_ports() { ufw allow ssh ufw allow http ufw allow https - ufw allow 2053/tcp + ufw allow 2053/tcp #webPort + ufw allow 2096/tcp #subport # Enable the firewall ufw --force enable fi +} - # Prompt the user to enter a list of ports +open_ports() { + # Prompt the user to enter the ports they want to open read -p "Enter the ports you want to open (e.g. 80,443,2053 or range 400-500): " ports # Check if the input is valid @@ -761,19 +775,28 @@ open_ports() { # Split the range into start and end ports start_port=$(echo $port | cut -d'-' -f1) end_port=$(echo $port | cut -d'-' -f2) + # Open the port range ufw allow $start_port:$end_port/tcp ufw allow $start_port:$end_port/udp else + # Open the single port ufw allow "$port" fi done - # Confirm that the ports are open - echo "The following ports are now open:" - ufw status | grep "ALLOW" | grep -Eo "[0-9]+(/[a-z]+)?" - - echo "Firewall status:" - ufw status verbose + # Confirm that the ports are opened + echo "Opened the specified ports:" + for port in "${PORT_LIST[@]}"; do + if [[ $port == *-* ]]; then + start_port=$(echo $port | cut -d'-' -f1) + end_port=$(echo $port | cut -d'-' -f2) + # Check if the port range has been successfully opened + (ufw status | grep -q "$start_port:$end_port") && echo "$start_port-$end_port" + else + # Check if the individual port has been successfully opened + (ufw status | grep -q "$port") && echo "$port" + fi + done } delete_ports() { @@ -1289,8 +1312,8 @@ build_image_tar() { } create_iplimit_jails() { - # Use default bantime if not passed => 15 minutes - local bantime="${1:-15}" + # Use default bantime if not passed => 30 minutes + local bantime="${1:-30}" # Uncomment 'allowipv6 = auto' in fail2ban.conf sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/fail2ban.conf @@ -1321,7 +1344,7 @@ EOF cat << EOF > /etc/fail2ban/action.d/3x-ipl.conf [INCLUDES] -before = iptables-common.conf +before = iptables-allports.conf [Definition] actionstart = -N f2b- @@ -1364,15 +1387,22 @@ iplimit_remove_conflicts() { done } +ip_validation() { + ipv6_regex="^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$" + ipv4_regex="^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)$" +} + iplimit_main() { echo -e "\n${green}\t1.${plain} Install Fail2ban and configure IP Limit" echo -e "${green}\t2.${plain} Change Ban Duration" echo -e "${green}\t3.${plain} Unban Everyone" echo -e "${green}\t4.${plain} Ban Logs" - echo -e "${green}\t5.${plain} Real-Time Logs" - echo -e "${green}\t6.${plain} Service Status" - echo -e "${green}\t7.${plain} Service Restart" - echo -e "${green}\t8.${plain} Uninstall Fail2ban and IP Limit" + echo -e "${green}\t5.${plain} Ban an IP Address" + echo -e "${green}\t6.${plain} Unban an IP Address" + echo -e "${green}\t7.${plain} Real-Time Logs" + echo -e "${green}\t8.${plain} Service Status" + echo -e "${green}\t9.${plain} Service Restart" + echo -e "${green}\t10.${plain} Uninstall Fail2ban and IP Limit" echo -e "${green}\t0.${plain} Back to Main Menu" read -p "Choose an option: " choice case "$choice" in @@ -1400,7 +1430,7 @@ iplimit_main() { 3) confirm "Proceed with Unbanning everyone from IP Limit jail?" "y" if [[ $? == 0 ]]; then - fail2ban-client set 3x-ipl unban --all + fail2ban-client reload --restart --unban 3x-ipl truncate -s 0 "${iplimit_banned_log_path}" echo -e "${green}All users Unbanned successfully.${plain}" iplimit_main @@ -1414,22 +1444,44 @@ iplimit_main() { iplimit_main ;; 5) - tail -f /var/log/fail2ban.log + read -rp "Enter the IP address you want to ban: " ban_ip + ip_validation + if [[ $ban_ip =~ $ipv4_regex || $ban_ip =~ $ipv6_regex ]]; then + fail2ban-client set 3x-ipl banip "$ban_ip" + echo -e "${green}IP Address ${ban_ip} has been banned successfully.${plain}" + else + echo -e "${red}Invalid IP address format! Please try again.${plain}" + fi iplimit_main ;; 6) - service fail2ban status + read -rp "Enter the IP address you want to unban: " unban_ip + ip_validation + if [[ $unban_ip =~ $ipv4_regex || $unban_ip =~ $ipv6_regex ]]; then + fail2ban-client set 3x-ipl unbanip "$unban_ip" + echo -e "${green}IP Address ${unban_ip} has been unbanned successfully.${plain}" + else + echo -e "${red}Invalid IP address format! Please try again.${plain}" + fi iplimit_main ;; 7) - systemctl restart fail2ban + tail -f /var/log/fail2ban.log iplimit_main ;; 8) + service fail2ban status + iplimit_main + ;; + 9) + systemctl restart fail2ban + iplimit_main + ;; + 10) remove_iplimit iplimit_main ;; - *) + *) echo -e "${red}Invalid option. Please select a valid number.${plain}\n" iplimit_main ;; @@ -1640,62 +1692,66 @@ SSH_port_forwarding() { } show_usage() { - echo "x-ui control menu usages: " - echo "------------------------------------------" - echo -e "SUBCOMMANDS:" - echo -e "x-ui - Admin Management Script" - echo -e "x-ui start - Start" - echo -e "x-ui stop - Stop" - echo -e "x-ui restart - Restart" - echo -e "x-ui status - Current Status" - echo -e "x-ui settings - Current Settings" - echo -e "x-ui enable - Enable Autostart on OS Startup" - echo -e "x-ui disable - Disable Autostart on OS Startup" - echo -e "x-ui log - Check logs" - echo -e "x-ui banlog - Check Fail2ban ban logs" - echo -e "x-ui update - Update" - echo -e "x-ui custom - custom version" - echo -e "x-ui install - Install" - echo -e "x-ui uninstall - Uninstall" - echo "------------------------------------------" + echo -e "┌───────────────────────────────────────────────────────┐ +│ ${blue}x-ui control menu usages (subcommands):${plain} │ +│ │ +│ ${blue}x-ui${plain} - Admin Management Script │ +│ ${blue}x-ui start${plain} - Start │ +│ ${blue}x-ui stop${plain} - Stop │ +│ ${blue}x-ui restart${plain} - Restart │ +│ ${blue}x-ui status${plain} - Current Status │ +│ ${blue}x-ui settings${plain} - Current Settings │ +│ ${blue}x-ui enable${plain} - Enable Autostart on OS Startup │ +│ ${blue}x-ui disable${plain} - Disable Autostart on OS Startup │ +│ ${blue}x-ui log${plain} - Check logs │ +│ ${blue}x-ui banlog${plain} - Check Fail2ban ban logs │ +│ ${blue}x-ui update${plain} - Update │ +│ ${blue}x-ui legacy${plain} - legacy version │ +│ ${blue}x-ui install${plain} - Install │ +│ ${blue}x-ui uninstall${plain} - Uninstall │ +└───────────────────────────────────────────────────────┘" } show_menu() { echo -e " - ${green}3X-UI Panel Management Script${plain} - ${green}0.${plain} Exit Script -———————————————— - ${green}1.${plain} Install - ${green}2.${plain} Update - ${green}3.${plain} Update Menu - ${green}4.${plain} Legacy Version - ${green}5.${plain} Uninstall -———————————————— - ${green}6.${plain} Reset Username & Password & Secret Token - ${green}7.${plain} Reset Web Base Path - ${green}8.${plain} Reset Settings - ${green}9.${plain} Change Port - ${green}10.${plain} View Current Settings -———————————————— - ${green}11.${plain} Start - ${green}12.${plain} Stop - ${green}13.${plain} Restart - ${green}14.${plain} Check Status - ${green}15.${plain} Logs Management -———————————————— - ${green}16.${plain} Enable Autostart - ${green}17.${plain} Disable Autostart -———————————————— - ${green}18.${plain} SSL Certificate Management - ${green}19.${plain} Cloudflare SSL Certificate - ${green}20.${plain} IP Limit Management - ${green}21.${plain} Firewall Management - ${green}22.${plain} SSH Port Forwarding Management -———————————————— - ${green}23.${plain} Enable BBR - ${green}24.${plain} Update Geo Files - ${green}25.${plain} Speedtest by Ookla - ${green}99.${plain} Build Docker Image (tar archive) +╔────────────────────────────────────────────────╗ +│ ${green}3X-UI Panel Management Script${plain} │ +│ ${green}0.${plain} Exit Script │ +│────────────────────────────────────────────────│ +│ ${green}1.${plain} Install │ +│ ${green}2.${plain} Update │ +│ ${green}3.${plain} Update Menu │ +│ ${green}4.${plain} Legacy Version │ +│ ${green}5.${plain} Uninstall │ +│────────────────────────────────────────────────│ +│ ${green}6.${plain} Reset Username & Password & Secret Token │ +│ ${green}7.${plain} Reset Web Base Path │ +│ ${green}8.${plain} Reset Settings │ +│ ${green}9.${plain} Change Port │ +│ ${green}10.${plain} View Current Settings │ +│────────────────────────────────────────────────│ +│ ${green}11.${plain} Start │ +│ ${green}12.${plain} Stop │ +│ ${green}13.${plain} Restart │ +│ ${green}14.${plain} Check Status │ +│ ${green}15.${plain} Logs Management │ +│────────────────────────────────────────────────│ +│ ${green}16.${plain} Enable Autostart │ +│ ${green}17.${plain} Disable Autostart │ +│────────────────────────────────────────────────│ +│ ${green}18.${plain} SSL Certificate Management │ +│ ${green}19.${plain} Cloudflare SSL Certificate │ +│ ${green}20.${plain} IP Limit Management │ +│ ${green}21.${plain} Firewall Management │ +│ ${green}22.${plain} SSH Port Forwarding Management │ +│────────────────────────────────────────────────│ +│ ${green}23.${plain} Enable BBR │ +│ ${green}24.${plain} Update Geo Files │ +│ ${green}25.${plain} Speedtest by Ookla │ +│────────────────────────────────────────────────│ +│ ${green}99.${plain} Build Docker Image (tar archive) +│ +╚────────────────────────────────────────────────╝ " show_status echo && read -p "Please enter your selection [0-25]: " num