Compare commits

...

13 Commits

Author SHA1 Message Date
Alireza Ahmadi
5d14f381c2 v1.10.2 2026-02-27 02:11:50 +01:00
Alireza Ahmadi
088dd2e881 fix session maxAge #1625 2026-02-27 01:42:33 +01:00
Alireza Ahmadi
5909955f5d remove test from #1622 2026-02-27 00:40:47 +01:00
Davood Qorbani
a3df597b5d return TCPing test + External proxy should no longer show : null (#1621)
When Subscription Json is enabled in Alireza x-ui +v1.10.0
- TCPing test in apps such as v2rayNG should return again
- External proxy should no longer show ": null"
also:
- Preserves VMess compatibility properly
- Keeps level consistent
- Avoids empty flow
- Matches standard Xray schema precisely
2026-02-27 00:39:20 +01:00
Alireza Ahmadi
49e43a2079 Merge pull request #1622 from shayan775/fix-AddClientTraffic
The too many SQL variables path is now batched.
2026-02-27 00:37:27 +01:00
Alireza Ahmadi
95afd3006a improve expiration delay start 2026-02-27 00:30:50 +01:00
shayan775
bcea56283f The too many SQL variables path is now batched.
Changed code:

Added safe batching limits in inbound.go:
safeSQLVariablesPerQuery = 900
safeSaveBatchSize = 50
Fixed addClientTraffic in inbound.go:
email IN (...) lookup is now chunked (line 815).
tx.Save(dbClientTraffics) is now chunked (line 859).
Added nil guard for p.SetOnlineClients(...) (line 855).
Hardened adjustTraffics in inbound.go:
deduplicates inbound IDs before querying (line 877).
chunked inbound IN query (line 890).
chunked inbound Save (line 932).
Added regression test:

inbound_add_client_traffic_test.go
Verifies 4,000 client traffic updates succeed and totals are correct.
Validation run:

go test ./web/service -run TestAddClientTrafficHandlesLargeBatch -count=1 passed
go test ./web/service -count=1 passed
2026-02-24 19:19:04 +03:30
Alireza Ahmadi
e70fb3daf5 Xray-Core v26.2.6 2026-02-20 02:11:24 +01:00
Alireza Ahmadi
322a74429d fix windows signal 2026-02-20 02:09:06 +01:00
Alireza Ahmadi
5b6daf9e70 Go 1.25.7 2026-02-20 01:16:03 +01:00
Alireza Ahmadi
e5184b81be sub json fix fragment noises effect #1617 2026-02-20 01:05:37 +01:00
Alireza Ahmadi
35798c2c74 fix prompt #1618 2026-02-20 00:14:07 +01:00
Alireza Ahmadi
6781b0f7ae add xray-core restart option in cli 2026-02-20 00:08:36 +01:00
17 changed files with 306 additions and 213 deletions

View File

@@ -1 +1 @@
1.10.1
1.10.2

14
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/alireza0/x-ui
go 1.25.6
go 1.25.7
require (
github.com/gin-contrib/gzip v1.2.5
@@ -13,10 +13,10 @@ require (
github.com/pelletier/go-toml/v2 v2.2.4
github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v4 v4.26.1
github.com/xtls/xray-core v1.260202.0
github.com/xtls/xray-core v1.260206.0
go.uber.org/atomic v1.11.0
golang.org/x/text v0.33.0
google.golang.org/grpc v1.78.0
golang.org/x/text v0.34.0
google.golang.org/grpc v1.79.1
gorm.io/driver/sqlite v1.6.0
gorm.io/gorm v1.31.1
)
@@ -49,9 +49,9 @@ require (
github.com/klauspost/compress v1.18.3 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.33 // indirect
github.com/mattn/go-sqlite3 v1.14.34 // indirect
github.com/miekg/dns v1.1.72 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
@@ -83,7 +83,7 @@ require (
golang.org/x/tools v0.41.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 // indirect
lukechampine.com/blake3 v1.4.1 // indirect

56
go.sum
View File

@@ -10,6 +10,8 @@ github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uS
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
@@ -19,8 +21,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
@@ -52,8 +52,6 @@ github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGi
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
@@ -95,12 +93,12 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6+8hHTyWBeQ/P4Nb7dd4/0ohEcWQuM=
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0=
github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk=
github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -136,8 +134,6 @@ github.com/sagernet/sing v0.7.17 h1:Jg4RUYIaQWTi7iY5ROHi3/Zsgxn4SPoRTwbdt35mt50=
github.com/sagernet/sing v0.7.17/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-shadowsocks v0.2.9 h1:Paep5zCszRKsEn8587O0MnhFWKJwDW1Y4zOYYlIxMkM=
github.com/sagernet/sing-shadowsocks v0.2.9/go.mod h1:TE/Z6401Pi8tgr0nBZcM/xawAI6u3F6TTbz4nH/qw+8=
github.com/shirou/gopsutil/v4 v4.25.12 h1:e7PvW/0RmJ8p8vPGJH4jvNkOyLmbkXgXW4m6ZPic6CY=
github.com/shirou/gopsutil/v4 v4.25.12/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU=
github.com/shirou/gopsutil/v4 v4.26.1 h1:TOkEyriIXk2HX9d4isZJtbjXbEjf5qyKPAzbzY0JWSo=
github.com/shirou/gopsutil/v4 v4.26.1/go.mod h1:medLI9/UNAb0dOI9Q3/7yWSqKkj00u+1tgY8nvv41pc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -165,26 +161,24 @@ 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-20251116175510-cd53f7d50237 h1:UXjrmniKlY+ZbIqpN91lejB3pszQQQRVu1vqH/p/aGM=
github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237/go.mod h1:vbHCV/3VWUvy1oKvTxxWJRPEWSeR1sYgQHIh6u/JiZQ=
github.com/xtls/xray-core v1.260131.0 h1:gPBykLhUvRZ8sfubNerkwWqV3c15UtmSYQG2cgKqrV4=
github.com/xtls/xray-core v1.260131.0/go.mod h1:cxzYFZrxu1B1NtPjHsqv4UzgDvRA71mV4rXYH4KtO7Q=
github.com/xtls/xray-core v1.260202.0 h1:dYduYxGlkn/krSQJbmksbTtCdRe8OFb3YwpuXXEJG5c=
github.com/xtls/xray-core v1.260202.0/go.mod h1:cxzYFZrxu1B1NtPjHsqv4UzgDvRA71mV4rXYH4KtO7Q=
github.com/xtls/xray-core v1.260206.0 h1:gY8IV6u76CW93txL9QmacgZ0Udxr2Q3e9qUxXAhdHqI=
github.com/xtls/xray-core v1.260206.0/go.mod h1:GyFIgVGRJkt3eyV/NMcdxOKXcJPqGGpyupHzy16uJhU=
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=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
@@ -213,8 +207,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
@@ -225,10 +219,10 @@ golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+Z
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -241,8 +235,6 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY=
gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 h1:Lk6hARj5UPY47dBep70OD/TIMwikJ5fGUGX0Rm3Xigk=

16
main.go
View File

@@ -16,13 +16,13 @@ import (
"github.com/alireza0/x-ui/database"
"github.com/alireza0/x-ui/logger"
"github.com/alireza0/x-ui/sub"
"github.com/alireza0/x-ui/util/sys"
"github.com/alireza0/x-ui/web"
"github.com/alireza0/x-ui/web/global"
"github.com/alireza0/x-ui/web/service"
"github.com/op/go-logging"
"github.com/shirou/gopsutil/v4/net"
xrayCore "github.com/xtls/xray-core/core"
)
func runWebServer() {
@@ -68,7 +68,7 @@ func runWebServer() {
sigCh := make(chan os.Signal, 1)
// Trap shutdown signals
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM)
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM, sys.SIGUSR1)
for {
sig := <-sigCh
@@ -99,6 +99,12 @@ func runWebServer() {
log.Println(err)
return
}
case sys.SIGUSR1:
logger.Info("Received USR1 signal, restarting xray-core...")
err := server.RestartXray()
if err != nil {
logger.Error("Failed to restart xray-core:", err)
}
default:
server.Stop()
subServer.Stop()
@@ -473,9 +479,3 @@ func main() {
settingCmd.Usage()
}
}
func startXray() {
conf := xrayCore.Config{}
core, _ := xrayCore.New(&conf)
core.Start()
}

View File

@@ -20,8 +20,7 @@ var defaultJson string
type SubJsonService struct {
configJson map[string]interface{}
defaultOutbounds []json_util.RawMessage
fragment string
noises string
fragmentOrNoises bool
mux string
inboundService service.InboundService
@@ -39,6 +38,31 @@ func NewSubJsonService(fragment string, noises string, mux string, rules string,
}
}
fragmentOrNoises := false
if fragment != "" || noises != "" {
fragmentOrNoises = true
defaultOutboundsSettings := map[string]interface{}{
"domainStrategy": "UseIP",
"redirect": "",
}
if fragment != "" {
defaultOutboundsSettings["fragment"] = json_util.RawMessage(fragment)
}
if noises != "" {
defaultOutboundsSettings["noises"] = json_util.RawMessage(noises)
}
defaultDirectOutbound := map[string]interface{}{
"protocol": "freedom",
"settings": defaultOutboundsSettings,
"tag": "direct_out",
}
jsonBytes, _ := json.MarshalIndent(defaultDirectOutbound, "", " ")
defaultOutbounds = append(defaultOutbounds, jsonBytes)
}
if rules != "" {
var newRules []interface{}
routing, _ := configJson["routing"].(map[string]interface{})
@@ -49,19 +73,10 @@ func NewSubJsonService(fragment string, noises string, mux string, rules string,
configJson["routing"] = routing
}
if fragment != "" {
defaultOutbounds = append(defaultOutbounds, json_util.RawMessage(fragment))
}
if noises != "" {
defaultOutbounds = append(defaultOutbounds, json_util.RawMessage(noises))
}
return &SubJsonService{
configJson: configJson,
defaultOutbounds: defaultOutbounds,
fragment: fragment,
noises: noises,
fragmentOrNoises: fragmentOrNoises,
mux: mux,
SubService: subService,
}
@@ -222,8 +237,8 @@ func (s *SubJsonService) streamData(stream string) map[string]interface{} {
}
delete(streamSettings, "sockopt")
if s.fragment != "" {
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "penetrate": true}`)
if s.fragmentOrNoises {
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "direct_out", "tcpKeepAliveIdle": 100}`)
}
// remove proxy protocol
@@ -294,19 +309,39 @@ func (s *SubJsonService) genVnext(inbound *model.Inbound, streamSettings json_ut
outbound.Protocol = string(inbound.Protocol)
outbound.Tag = "proxy"
if s.mux != "" {
outbound.Mux = json_util.RawMessage(s.mux)
}
outbound.StreamSettings = streamSettings
settings := make(map[string]any)
settings["address"] = inbound.Listen
settings["port"] = inbound.Port
settings["id"] = client.ID
if inbound.Protocol == model.VLESS {
settings["flow"] = client.Flow
settings["encryption"] = encryption
// Build standard Xray/V2Ray schema
user := map[string]any{
"id": client.ID,
"level": 8,
}
if inbound.Protocol == model.VLESS {
user["encryption"] = encryption
if client.Flow != "" {
user["flow"] = client.Flow
}
} else {
// VMess
user["alterId"] = 0
user["security"] = "auto"
}
vnext := map[string]any{
"address": inbound.Listen,
"port": inbound.Port,
"users": []any{user},
}
outbound.Settings = map[string]any{
"vnext": []any{vnext},
}
outbound.Settings = settings
result, _ := json.MarshalIndent(outbound, "", " ")
return result

View File

@@ -4,9 +4,13 @@
package sys
import (
"syscall"
"github.com/shirou/gopsutil/v4/net"
)
var SIGUSR1 = syscall.SIGUSR1
func GetTCPCount() (int, error) {
stats, err := net.Connections("tcp")
if err != nil {

View File

@@ -8,8 +8,11 @@ import (
"fmt"
"io"
"os"
"syscall"
)
var SIGUSR1 = syscall.SIGUSR1
func getLinesNum(filename string) (int, error) {
file, err := os.Open(filename)
if err != nil {

View File

@@ -4,9 +4,13 @@
package sys
import (
"syscall"
"github.com/shirou/gopsutil/v4/net"
)
var SIGUSR1 = syscall.Signal(0)
func GetTCPCount() (int, error) {
stats, err := net.Connections("tcp")
if err != nil {

View File

@@ -34,13 +34,13 @@ function getLang() {
lang = window.navigator.language || window.navigator.userLanguage;
if (isSupportLang(lang)) {
setCookie('lang', lang, 150);
setCookie('lang', lang);
} else {
setCookie('lang', 'en-US', 150);
setCookie('lang', 'en-US');
window.location.reload();
}
} else {
setCookie('lang', 'en-US', 150);
setCookie('lang', 'en-US');
window.location.reload();
}
}
@@ -53,7 +53,7 @@ function setLang(lang) {
lang = 'en-US';
}
setCookie('lang', lang, 150);
setCookie('lang', lang);
window.location.reload();
}

View File

@@ -95,10 +95,13 @@ function getCookie(cname) {
}
function setCookie(cname, cvalue, exdays) {
const d = new Date();
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
let expires = "expires=" + d.toUTCString();
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
let expires = "";
if (exdays > 0) {
const d = new Date();
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
expires = "expires=" + d.toUTCString();
}
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=" + axios.defaults.baseURL;
}
function usageColor(data, threshold, total) {

View File

@@ -75,20 +75,12 @@ func (a *IndexController) login(c *gin.Context) {
a.tgbot.UserLoginNotify(safeUser, getRemoteIp(c), timeStr, 1)
}
sessionMaxAge, err := a.settingService.GetSessionMaxAge()
if err != nil {
logger.Info("Unable to get session's max age from DB")
}
if sessionMaxAge > 0 {
err = session.SetMaxAge(c, sessionMaxAge*60)
if err != nil {
logger.Info("Unable to set session's max age")
}
}
err = session.SetLoginUser(c, user)
logger.Infof("%s logged in successfully", user.Username)
if err == nil {
logger.Infof("%s logged in successfully", user.Username)
} else {
logger.Error("Unable to set login user")
}
jsonMsg(c, I18nWeb(c, "pages.login.toasts.successLogin"), err)
}

View File

@@ -477,34 +477,14 @@
remarkSeparators: [' ', '-', '_', '@', ':', '~', '|', ',', '.', '/'],
remarkSample: '',
defaultFragment: {
tag: "fragment",
protocol: "freedom",
settings: {
domainStrategy: "AsIs",
fragment: {
packets: "tlshello",
length: "100-200",
interval: "10-20",
maxSplit: "300-400"
}
},
streamSettings: {
sockopt: {
tcpKeepAliveIdle: 100,
penetrate: true
}
}
},
defaultNoises: {
tag: "noises",
protocol: "freedom",
settings: {
domainStrategy: "AsIs",
noises: [
{ type: "rand", packet: "10-20", delay: "10-16" },
],
},
packets: "tlshello",
length: "100-200",
interval: "10-20",
maxSplit: "300-400"
},
defaultNoises: [
{ type: "rand", packet: "10-20", delay: "10-16" }
],
defaultMux: {
enabled: true,
concurrency: 8,
@@ -661,41 +641,41 @@
}
},
fragmentPackets: {
get: function () { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.packets : ""; },
get: function () { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).packets : ""; },
set: function (v) {
if (v != "") {
newFragment = JSON.parse(this.allSetting.subJsonFragment);
newFragment.settings.fragment.packets = v;
newFragment.packets = v;
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
}
}
},
fragmentLength: {
get: function () { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.length : ""; },
get: function () { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).length : ""; },
set: function (v) {
if (v != "") {
newFragment = JSON.parse(this.allSetting.subJsonFragment);
newFragment.settings.fragment.length = v;
newFragment.length = v;
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
}
}
},
fragmentInterval: {
get: function () { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.interval : ""; },
get: function () { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).interval : ""; },
set: function (v) {
if (v != "") {
newFragment = JSON.parse(this.allSetting.subJsonFragment);
newFragment.settings.fragment.interval = v;
newFragment.interval = v;
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
}
}
},
fragmentMaxSplit: {
get: function () { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.maxSplit : ""; },
get: function () { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).maxSplit : ""; },
set: function (v) {
if (v != "") {
newFragment = JSON.parse(this.allSetting.subJsonFragment);
newFragment.settings.fragment.maxSplit = v;
newFragment.maxSplit = v;
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
}
}
@@ -714,13 +694,11 @@
},
noisesArray: {
get() {
return this.noises ? JSON.parse(this.allSetting.subJsonNoises).settings.noises : [];
return this.noises ? JSON.parse(this.allSetting.subJsonNoises) : [];
},
set(value) {
if (this.noises) {
const newNoises = JSON.parse(this.allSetting.subJsonNoises);
newNoises.settings.noises = value;
this.allSetting.subJsonNoises = JSON.stringify(newNoises);
this.allSetting.subJsonNoises = JSON.stringify(value);
}
}
},

View File

@@ -19,6 +19,10 @@ type InboundService struct {
xrayApi xray.XrayAPI
}
const (
safeBatchSize = 500
)
func (s *InboundService) GetInbounds(userId int) ([]*model.Inbound, error) {
db := database.GetDB()
var inbounds []*model.Inbound
@@ -789,6 +793,11 @@ func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic
return nil
}
type newExpiryTime struct {
Email string
NewExpiryTime int64
}
func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTraffic) (err error) {
if len(traffics) == 0 {
// Empty onlineUsers
@@ -805,9 +814,18 @@ func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTr
emails = append(emails, traffic.Email)
}
dbClientTraffics := make([]*xray.ClientTraffic, 0, len(traffics))
err = tx.Model(xray.ClientTraffic{}).Where("email IN (?)", emails).Find(&dbClientTraffics).Error
if err != nil {
return err
for i := 0; i < len(emails); i += safeBatchSize {
end := i + safeBatchSize
if end > len(emails) {
end = len(emails)
}
batchClientTraffics := make([]*xray.ClientTraffic, 0, end-i)
err = tx.Model(xray.ClientTraffic{}).Where("email IN ?", emails[i:end]).Find(&batchClientTraffics).Error
if err != nil {
return err
}
dbClientTraffics = append(dbClientTraffics, batchClientTraffics...)
}
// Avoid empty slice error
@@ -815,7 +833,20 @@ func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTr
return nil
}
dbClientTraffics, err = s.adjustTraffics(tx, dbClientTraffics)
inboundExpiryTimeMap := make(map[int][]newExpiryTime, 0)
for index, t := range dbClientTraffics {
if t.ExpiryTime < 0 {
newClientExpiryTime := (time.Now().Unix() * 1000) - int64(t.ExpiryTime)
newExpiryTime := newExpiryTime{
Email: t.Email,
NewExpiryTime: newClientExpiryTime,
}
inboundExpiryTimeMap[t.InboundId] = append(inboundExpiryTimeMap[t.InboundId], newExpiryTime)
dbClientTraffics[index].ExpiryTime = newClientExpiryTime
}
}
err = s.adjustTraffics(tx, inboundExpiryTimeMap)
if err != nil {
return err
}
@@ -836,66 +867,90 @@ func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTr
}
// Set onlineUsers
p.SetOnlineClients(onlineClients)
if p != nil {
p.SetOnlineClients(onlineClients)
}
err = tx.Save(dbClientTraffics).Error
if err != nil {
logger.Warning("AddClientTraffic update data ", err)
for i := 0; i < len(dbClientTraffics); i += safeBatchSize {
end := i + safeBatchSize
if end > len(dbClientTraffics) {
end = len(dbClientTraffics)
}
err = tx.Save(dbClientTraffics[i:end]).Error
if err != nil {
logger.Warning("AddClientTraffic update data ", err)
return nil
}
}
return nil
}
func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.ClientTraffic) ([]*xray.ClientTraffic, error) {
inboundIds := make([]int, 0, len(dbClientTraffics))
for _, dbClientTraffic := range dbClientTraffics {
if dbClientTraffic.ExpiryTime < 0 {
inboundIds = append(inboundIds, dbClientTraffic.InboundId)
func (s *InboundService) adjustTraffics(tx *gorm.DB, inboundExpiryTimeMap map[int][]newExpiryTime) error {
if len(inboundExpiryTimeMap) == 0 {
return nil
}
inboundIds := make([]int, 0)
for inId := range inboundExpiryTimeMap {
inboundIds = append(inboundIds, inId)
}
inbounds := make([]*model.Inbound, 0, len(inboundIds))
for i := 0; i < len(inboundIds); i += safeBatchSize {
end := i + safeBatchSize
if end > len(inboundIds) {
end = len(inboundIds)
}
batchInbounds := make([]*model.Inbound, 0, end-i)
err := tx.Model(model.Inbound{}).Where("id IN ?", inboundIds[i:end]).Find(&batchInbounds).Error
if err != nil {
return err
}
inbounds = append(inbounds, batchInbounds...)
}
for inbound_index := range inbounds {
settings := map[string]interface{}{}
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
clients, ok := settings["clients"].([]interface{})
inbEmails := inboundExpiryTimeMap[inbounds[inbound_index].Id]
if ok {
var newClients []interface{}
for client_index := range clients {
c := clients[client_index].(map[string]interface{})
for index := range inbEmails {
if c["email"] == inbEmails[index].Email {
c["expiryTime"] = inbEmails[index].NewExpiryTime
break
}
}
newClients = append(newClients, interface{}(c))
}
settings["clients"] = newClients
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
if err != nil {
return err
}
inbounds[inbound_index].Settings = string(modifiedSettings)
}
}
if len(inboundIds) > 0 {
var inbounds []*model.Inbound
err := tx.Model(model.Inbound{}).Where("id IN (?)", inboundIds).Find(&inbounds).Error
if err != nil {
return nil, err
for i := 0; i < len(inbounds); i += safeBatchSize {
end := i + safeBatchSize
if end > len(inbounds) {
end = len(inbounds)
}
for inbound_index := range inbounds {
settings := map[string]interface{}{}
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
clients, ok := settings["clients"].([]interface{})
if ok {
var newClients []interface{}
for client_index := range clients {
c := clients[client_index].(map[string]interface{})
for traffic_index := range dbClientTraffics {
if dbClientTraffics[traffic_index].ExpiryTime < 0 && c["email"] == dbClientTraffics[traffic_index].Email {
oldExpiryTime := c["expiryTime"].(float64)
newExpiryTime := (time.Now().Unix() * 1000) - int64(oldExpiryTime)
c["expiryTime"] = newExpiryTime
dbClientTraffics[traffic_index].ExpiryTime = newExpiryTime
break
}
}
newClients = append(newClients, interface{}(c))
}
settings["clients"] = newClients
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
if err != nil {
return nil, err
}
inbounds[inbound_index].Settings = string(modifiedSettings)
}
}
err = tx.Save(inbounds).Error
err := tx.Save(inbounds[i:end]).Error
if err != nil {
logger.Warning("AddClientTraffic update inbounds ", err)
logger.Error(inbounds)
break
}
}
return dbClientTraffics, nil
return nil
}
func (s *InboundService) autoRenewClients(tx *gorm.DB) (bool, int64, error) {

View File

@@ -19,23 +19,10 @@ func init() {
func SetLoginUser(c *gin.Context, user *model.User) error {
s := sessions.Default(c)
s.Options(sessions.Options{
Path: "/",
HttpOnly: true,
})
s.Set(loginUser, user)
return s.Save()
}
func SetMaxAge(c *gin.Context, maxAge int) error {
s := sessions.Default(c)
s.Options(sessions.Options{
Path: "/",
MaxAge: maxAge,
})
return s.Save()
}
func GetLoginUser(c *gin.Context) *model.User {
s := sessions.Default(c)
obj := s.Get(loginUser)
@@ -53,8 +40,12 @@ func IsLogin(c *gin.Context) bool {
func ClearSession(c *gin.Context) {
s := sessions.Default(c)
s.Clear()
basePath := c.GetString("base_path")
if basePath == "" {
basePath = "/"
}
s.Options(sessions.Options{
Path: "/",
Path: basePath,
MaxAge: -1,
})
s.Save()

View File

@@ -176,10 +176,23 @@ func (s *Server) initRouter() (*gin.Engine, error) {
if err != nil {
return nil, err
}
sessionMaxAge, err := s.settingService.GetSessionMaxAge()
if err != nil {
return nil, err
}
engine.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{basePath + "xui/API/"})))
assetsBasePath := basePath + "assets/"
store := cookie.NewStore(secret)
sessionOptions := sessions.Options{
Path: basePath,
HttpOnly: true,
}
if sessionMaxAge > 0 {
sessionOptions.MaxAge = sessionMaxAge * 60
}
store.Options(sessionOptions)
engine.Use(sessions.Sessions("x-ui", store))
engine.Use(func(c *gin.Context) {
c.Set("base_path", basePath)
@@ -392,3 +405,7 @@ func (s *Server) GetCtx() context.Context {
func (s *Server) GetCron() *cron.Cron {
return s.cron
}
func (s *Server) RestartXray() error {
return s.xrayService.RestartXray(true)
}

View File

@@ -8,6 +8,7 @@ Environment="XRAY_VMESS_AEAD_FORCED=false"
Type=simple
WorkingDirectory=/usr/local/x-ui/
ExecStart=/usr/local/x-ui/x-ui
ExecReload=kill -USR1 $MAINPID
Restart=on-failure
[Install]

60
x-ui.sh
View File

@@ -141,7 +141,7 @@ uninstall() {
}
reset_user() {
confirm "Reset your username and password to admin?" "n"
confirm "Are you sure to reset the username and password of the panel?" "n"
if [[ $? != 0 ]]; then
if [[ $# == 0 ]]; then
show_menu
@@ -286,6 +286,16 @@ restart() {
fi
}
restart_xray() {
systemctl reload x-ui
LOGI "xray-core Restart signal sent successfully, Please check the log information to confirm whether xray restarted successfully"
sleep 2
show_xray_status
if [[ $# == 0 ]]; then
before_show_menu
fi
}
status() {
systemctl status x-ui -l
if [[ $# == 0 ]]; then
@@ -1052,6 +1062,7 @@ show_usage() {
echo "x-ui start - Start"
echo "x-ui stop - Stop"
echo "x-ui restart - Restart"
echo "x-ui restart-xray - Restart xray-core"
echo "x-ui status - Current Status"
echo "x-ui settings - Current Settings"
echo "x-ui enable - Enable Autostart on OS Startup"
@@ -1084,19 +1095,20 @@ show_menu() {
${green}10.${plain} Start
${green}11.${plain} Stop
${green}12.${plain} Restart
${green}13.${plain} Check State
${green}14.${plain} Check Logs
${green}13.${plain} Restart Xray
${green}14.${plain} Check State
${green}15.${plain} Check Logs
————————————————
${green}15.${plain} Enable Autostart
${green}16.${plain} Disable Autostart
${green}16.${plain} Enable Autostart
${green}17.${plain} Disable Autostart
————————————————
${green}17.${plain} SSL Certificate Management
${green}18.${plain} Cloudflare SSL Certificate
${green}19.${plain} Firewall Management
${green}18.${plain} SSL Certificate Management
${green}19.${plain} Cloudflare SSL Certificate
${green}20.${plain} Firewall Management
————————————————
${green}20.${plain} Enable or Disable BBR
${green}21.${plain} Update Geo Files
${green}22.${plain} Speedtest by Ookla
${green}21.${plain} Enable or Disable BBR
${green}22.${plain} Update Geo Files
${green}23.${plain} Speedtest by Ookla
"
show_status
echo && read -p "Please enter your selection [0-22]: " num
@@ -1142,37 +1154,40 @@ show_menu() {
check_install && restart
;;
13)
check_install && status
check_install && restart_xray
;;
14)
check_install && show_log
check_install && status
;;
15)
check_install && enable
check_install && show_log
;;
16)
check_install && disable
check_install && enable
;;
17)
ssl_cert_issue_main
check_install && disable
;;
18)
ssl_cert_issue_CF
ssl_cert_issue_main
;;
19)
firewall_menu
ssl_cert_issue_CF
;;
20)
bbr_menu
firewall_menu
;;
21)
update_geo
bbr_menu
;;
22)
update_geo
;;
23)
run_speedtest
;;
*)
LOGE "Please enter the correct number [0-22]"
LOGE "Please enter the correct number [0-23]"
;;
esac
}
@@ -1188,6 +1203,9 @@ if [[ $# > 0 ]]; then
"restart")
check_install 0 && restart 0
;;
"restart-xray")
check_install 0 && restart_xray 0
;;
"status")
check_install 0 && status 0
;;