mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-10-13 19:49:12 +00:00
Merge branch 'main' into feature/multi-server-support
This commit is contained in:
commit
5e953bae45
16 changed files with 221 additions and 74 deletions
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
|
@ -85,7 +85,7 @@ jobs:
|
||||||
cd x-ui/bin
|
cd x-ui/bin
|
||||||
|
|
||||||
# Download dependencies
|
# Download dependencies
|
||||||
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.9.5/"
|
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.9.11/"
|
||||||
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
||||||
wget -q ${Xray_URL}Xray-linux-64.zip
|
wget -q ${Xray_URL}Xray-linux-64.zip
|
||||||
unzip Xray-linux-64.zip
|
unzip Xray-linux-64.zip
|
||||||
|
@ -183,7 +183,7 @@ jobs:
|
||||||
cd x-ui\bin
|
cd x-ui\bin
|
||||||
|
|
||||||
# Download Xray for Windows
|
# Download Xray for Windows
|
||||||
$Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v25.6.8/"
|
$Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v25.9.11/"
|
||||||
Invoke-WebRequest -Uri "${Xray_URL}Xray-windows-64.zip" -OutFile "Xray-windows-64.zip"
|
Invoke-WebRequest -Uri "${Xray_URL}Xray-windows-64.zip" -OutFile "Xray-windows-64.zip"
|
||||||
Expand-Archive -Path "Xray-windows-64.zip" -DestinationPath .
|
Expand-Archive -Path "Xray-windows-64.zip" -DestinationPath .
|
||||||
Remove-Item "Xray-windows-64.zip"
|
Remove-Item "Xray-windows-64.zip"
|
||||||
|
|
|
@ -27,7 +27,7 @@ case $1 in
|
||||||
esac
|
esac
|
||||||
mkdir -p build/bin
|
mkdir -p build/bin
|
||||||
cd build/bin
|
cd build/bin
|
||||||
wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.9.5/Xray-linux-${ARCH}.zip"
|
wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.9.11/Xray-linux-${ARCH}.zip"
|
||||||
unzip "Xray-linux-${ARCH}.zip"
|
unzip "Xray-linux-${ARCH}.zip"
|
||||||
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
|
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
|
||||||
mv xray "xray-linux-${FNAME}"
|
mv xray "xray-linux-${FNAME}"
|
||||||
|
|
|
@ -95,7 +95,7 @@ func GetLogFolder() string {
|
||||||
return logFolderPath
|
return logFolderPath
|
||||||
}
|
}
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
return getBaseDir()
|
return filepath.Join(".", "log")
|
||||||
}
|
}
|
||||||
return "/var/log"
|
return "/var/log"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
2.6.8
|
2.7.0
|
20
go.mod
20
go.mod
|
@ -1,6 +1,6 @@
|
||||||
module x-ui
|
module x-ui
|
||||||
|
|
||||||
go 1.25.0
|
go 1.25.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-contrib/gzip v1.2.3
|
github.com/gin-contrib/gzip v1.2.3
|
||||||
|
@ -17,13 +17,13 @@ require (
|
||||||
github.com/shirou/gopsutil/v4 v4.25.8
|
github.com/shirou/gopsutil/v4 v4.25.8
|
||||||
github.com/valyala/fasthttp v1.65.0
|
github.com/valyala/fasthttp v1.65.0
|
||||||
github.com/xlzd/gotp v0.1.0
|
github.com/xlzd/gotp v0.1.0
|
||||||
github.com/xtls/xray-core v1.250905.0
|
github.com/xtls/xray-core v1.250911.0
|
||||||
go.uber.org/atomic v1.11.0
|
go.uber.org/atomic v1.11.0
|
||||||
golang.org/x/crypto v0.41.0
|
golang.org/x/crypto v0.42.0
|
||||||
golang.org/x/text v0.28.0
|
golang.org/x/text v0.29.0
|
||||||
google.golang.org/grpc v1.75.0
|
google.golang.org/grpc v1.75.1
|
||||||
gorm.io/driver/sqlite v1.6.0
|
gorm.io/driver/sqlite v1.6.0
|
||||||
gorm.io/gorm v1.30.2
|
gorm.io/gorm v1.30.5
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
@ -86,16 +86,16 @@ require (
|
||||||
go.uber.org/mock v0.6.0 // indirect
|
go.uber.org/mock v0.6.0 // indirect
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||||
golang.org/x/arch v0.21.0 // indirect
|
golang.org/x/arch v0.21.0 // indirect
|
||||||
golang.org/x/mod v0.27.0 // indirect
|
golang.org/x/mod v0.28.0 // indirect
|
||||||
golang.org/x/net v0.43.0 // indirect
|
golang.org/x/net v0.44.0 // indirect
|
||||||
golang.org/x/sync v0.17.0 // indirect
|
golang.org/x/sync v0.17.0 // indirect
|
||||||
golang.org/x/sys v0.36.0 // indirect
|
golang.org/x/sys v0.36.0 // indirect
|
||||||
golang.org/x/time v0.13.0 // indirect
|
golang.org/x/time v0.13.0 // indirect
|
||||||
golang.org/x/tools v0.36.0 // indirect
|
golang.org/x/tools v0.36.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 // indirect
|
||||||
google.golang.org/protobuf v1.36.8 // indirect
|
google.golang.org/protobuf v1.36.9 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c // indirect
|
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c // indirect
|
||||||
lukechampine.com/blake3 v1.4.1 // indirect
|
lukechampine.com/blake3 v1.4.1 // indirect
|
||||||
|
|
36
go.sum
36
go.sum
|
@ -176,8 +176,8 @@ github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po=
|
||||||
github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
|
github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
|
||||||
github.com/xtls/reality v0.0.0-20250904214705-431b6ff8c67c h1:LHLhQY3mKXSpTcQAkjFR4/6ar3rXjQryNeM7khK3AHU=
|
github.com/xtls/reality v0.0.0-20250904214705-431b6ff8c67c h1:LHLhQY3mKXSpTcQAkjFR4/6ar3rXjQryNeM7khK3AHU=
|
||||||
github.com/xtls/reality v0.0.0-20250904214705-431b6ff8c67c/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0=
|
github.com/xtls/reality v0.0.0-20250904214705-431b6ff8c67c/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0=
|
||||||
github.com/xtls/xray-core v1.250905.0 h1:VNL3l/6fcwyeYXJTRbf+TYqPfJYkk0Wmmz7qoQNkxY8=
|
github.com/xtls/xray-core v1.250911.0 h1:KMN8zVurAjHFixiUoFV/jwmzYohf27dQRntjV+8LQno=
|
||||||
github.com/xtls/xray-core v1.250905.0/go.mod h1:WB/73DmN9Vs7lxtx4Xc/D0Ub1VUu06hAh1mMh8JN2uM=
|
github.com/xtls/xray-core v1.250911.0/go.mod h1:LkqA/BFVtPS2e5fRzg/bkYas9nQu4Uztlx+/fjlLM9k=
|
||||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
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 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
|
@ -202,12 +202,12 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/arch v0.21.0 h1:iTC9o7+wP6cPWpDWkivCvQFGAHDQ59SrSxsLPcnkArw=
|
golang.org/x/arch v0.21.0 h1:iTC9o7+wP6cPWpDWkivCvQFGAHDQ59SrSxsLPcnkArw=
|
||||||
golang.org/x/arch v0.21.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
golang.org/x/arch v0.21.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
@ -218,8 +218,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.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||||
golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
|
golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
|
||||||
golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||||
|
@ -230,12 +230,12 @@ 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=
|
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 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 h1:/OQuEa4YWtDt7uQWHd3q3sUMb+QOLQUg1xa8CEsRv5w=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
|
||||||
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
|
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
|
||||||
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
@ -247,8 +247,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
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 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||||
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||||
gorm.io/gorm v1.30.2 h1:f7bevlVoVe4Byu3pmbWPVHnPsLoWaMjEb7/clyr9Ivs=
|
gorm.io/gorm v1.30.5 h1:dvEfYwxL+i+xgCNSGGBT1lDjCzfELK8fHZxL3Ee9X0s=
|
||||||
gorm.io/gorm v1.30.2/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
gorm.io/gorm v1.30.5/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||||
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c h1:m/r7OM+Y2Ty1sgBQ7Qb27VgIMBW8ZZhT4gLnUyDIhzI=
|
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c h1:m/r7OM+Y2Ty1sgBQ7Qb27VgIMBW8ZZhT4gLnUyDIhzI=
|
||||||
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=
|
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=
|
||||||
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
|
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
|
||||||
|
|
|
@ -6,7 +6,7 @@ const Protocols = {
|
||||||
VLESS: "vless",
|
VLESS: "vless",
|
||||||
Trojan: "trojan",
|
Trojan: "trojan",
|
||||||
Shadowsocks: "shadowsocks",
|
Shadowsocks: "shadowsocks",
|
||||||
Mixed: "mixed",
|
Socks: "socks",
|
||||||
HTTP: "http",
|
HTTP: "http",
|
||||||
Wireguard: "wireguard"
|
Wireguard: "wireguard"
|
||||||
};
|
};
|
||||||
|
@ -643,7 +643,7 @@ class Outbound extends CommonClass {
|
||||||
Protocols.Trojan,
|
Protocols.Trojan,
|
||||||
Protocols.Shadowsocks,
|
Protocols.Shadowsocks,
|
||||||
Protocols.HTTP,
|
Protocols.HTTP,
|
||||||
Protocols.Mixed
|
Protocols.Socks
|
||||||
].includes(this.protocol);
|
].includes(this.protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -652,7 +652,7 @@ class Outbound extends CommonClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
hasServers() {
|
hasServers() {
|
||||||
return [Protocols.Trojan, Protocols.Shadowsocks, Protocols.Mixed, Protocols.HTTP].includes(this.protocol);
|
return [Protocols.Trojan, Protocols.Shadowsocks, Protocols.Socks, Protocols.HTTP].includes(this.protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasAddressPort() {
|
hasAddressPort() {
|
||||||
|
@ -662,13 +662,13 @@ class Outbound extends CommonClass {
|
||||||
Protocols.VLESS,
|
Protocols.VLESS,
|
||||||
Protocols.Trojan,
|
Protocols.Trojan,
|
||||||
Protocols.Shadowsocks,
|
Protocols.Shadowsocks,
|
||||||
Protocols.Mixed,
|
Protocols.Socks,
|
||||||
Protocols.HTTP
|
Protocols.HTTP
|
||||||
].includes(this.protocol);
|
].includes(this.protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasUsername() {
|
hasUsername() {
|
||||||
return [Protocols.Mixed, Protocols.HTTP].includes(this.protocol);
|
return [Protocols.Socks, Protocols.HTTP].includes(this.protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
|
@ -847,7 +847,7 @@ Outbound.Settings = class extends CommonClass {
|
||||||
case Protocols.VLESS: return new Outbound.VLESSSettings();
|
case Protocols.VLESS: return new Outbound.VLESSSettings();
|
||||||
case Protocols.Trojan: return new Outbound.TrojanSettings();
|
case Protocols.Trojan: return new Outbound.TrojanSettings();
|
||||||
case Protocols.Shadowsocks: return new Outbound.ShadowsocksSettings();
|
case Protocols.Shadowsocks: return new Outbound.ShadowsocksSettings();
|
||||||
case Protocols.Mixed: return new Outbound.MixedSettings();
|
case Protocols.Socks: return new Outbound.SocksSettings();
|
||||||
case Protocols.HTTP: return new Outbound.HttpSettings();
|
case Protocols.HTTP: return new Outbound.HttpSettings();
|
||||||
case Protocols.Wireguard: return new Outbound.WireguardSettings();
|
case Protocols.Wireguard: return new Outbound.WireguardSettings();
|
||||||
default: return null;
|
default: return null;
|
||||||
|
@ -863,7 +863,7 @@ Outbound.Settings = class extends CommonClass {
|
||||||
case Protocols.VLESS: return Outbound.VLESSSettings.fromJson(json);
|
case Protocols.VLESS: return Outbound.VLESSSettings.fromJson(json);
|
||||||
case Protocols.Trojan: return Outbound.TrojanSettings.fromJson(json);
|
case Protocols.Trojan: return Outbound.TrojanSettings.fromJson(json);
|
||||||
case Protocols.Shadowsocks: return Outbound.ShadowsocksSettings.fromJson(json);
|
case Protocols.Shadowsocks: return Outbound.ShadowsocksSettings.fromJson(json);
|
||||||
case Protocols.Mixed: return Outbound.MixedSettings.fromJson(json);
|
case Protocols.Socks: return Outbound.SocksSettings.fromJson(json);
|
||||||
case Protocols.HTTP: return Outbound.HttpSettings.fromJson(json);
|
case Protocols.HTTP: return Outbound.HttpSettings.fromJson(json);
|
||||||
case Protocols.Wireguard: return Outbound.WireguardSettings.fromJson(json);
|
case Protocols.Wireguard: return Outbound.WireguardSettings.fromJson(json);
|
||||||
default: return null;
|
default: return null;
|
||||||
|
@ -1141,7 +1141,7 @@ Outbound.ShadowsocksSettings = class extends CommonClass {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Outbound.MixedSettings = class extends CommonClass {
|
Outbound.SocksSettings = class extends CommonClass {
|
||||||
constructor(address, port, user, pass) {
|
constructor(address, port, user, pass) {
|
||||||
super();
|
super();
|
||||||
this.address = address;
|
this.address = address;
|
||||||
|
@ -1153,7 +1153,7 @@ Outbound.MixedSettings = class extends CommonClass {
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
let servers = json.servers;
|
let servers = json.servers;
|
||||||
if (ObjectUtil.isArrEmpty(servers)) servers = [{ users: [{}] }];
|
if (ObjectUtil.isArrEmpty(servers)) servers = [{ users: [{}] }];
|
||||||
return new Outbound.MixedSettings(
|
return new Outbound.SocksSettings(
|
||||||
servers[0].address,
|
servers[0].address,
|
||||||
servers[0].port,
|
servers[0].port,
|
||||||
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
||||||
|
|
|
@ -326,6 +326,14 @@ class ObjectUtil {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (const key in b) {
|
||||||
|
if (!b.hasOwnProperty(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!a.hasOwnProperty(key)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,7 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
||||||
g.POST("/onlines", a.onlines)
|
g.POST("/onlines", a.onlines)
|
||||||
g.POST("/lastOnline", a.lastOnline)
|
g.POST("/lastOnline", a.lastOnline)
|
||||||
g.POST("/updateClientTraffic/:email", a.updateClientTraffic)
|
g.POST("/updateClientTraffic/:email", a.updateClientTraffic)
|
||||||
|
g.POST("/:id/delClientByEmail/:email", a.delInboundClientByEmail)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) getInbounds(c *gin.Context) {
|
func (a *InboundController) getInbounds(c *gin.Context) {
|
||||||
|
@ -375,3 +376,23 @@ func (a *InboundController) updateClientTraffic(c *gin.Context) {
|
||||||
|
|
||||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundClientUpdateSuccess"), nil)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundClientUpdateSuccess"), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *InboundController) delInboundClientByEmail(c *gin.Context) {
|
||||||
|
inboundId, err := strconv.Atoi(c.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "Invalid inbound ID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
email := c.Param("email")
|
||||||
|
needRestart, err := a.inboundService.DelInboundClientByEmail(inboundId, email)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "Failed to delete client by email", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonMsg(c, "Client deleted successfully", nil)
|
||||||
|
if needRestart {
|
||||||
|
a.xrayService.SetToNeedRestart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -241,9 +241,9 @@
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Servers (trojan/shadowsocks/mixed/http) settings -->
|
<!-- Servers (trojan/shadowsocks/socks/http) settings -->
|
||||||
<template v-if="outbound.hasServers()">
|
<template v-if="outbound.hasServers()">
|
||||||
<!-- http / mixed -->
|
<!-- http / socks -->
|
||||||
<template v-if="outbound.hasUsername()">
|
<template v-if="outbound.hasUsername()">
|
||||||
<a-form-item label='{{ i18n "username" }}'>
|
<a-form-item label='{{ i18n "username" }}'>
|
||||||
<a-input v-model.trim="outbound.settings.user"></a-input>
|
<a-input v-model.trim="outbound.settings.user"></a-input>
|
||||||
|
|
|
@ -983,11 +983,13 @@
|
||||||
const list = this.clientCount[inbound.id][this.filterBy];
|
const list = this.clientCount[inbound.id][this.filterBy];
|
||||||
if (list.length > 0) {
|
if (list.length > 0) {
|
||||||
const filteredSettings = { "clients": [] };
|
const filteredSettings = { "clients": [] };
|
||||||
|
if (inboundSettings.clients) {
|
||||||
inboundSettings.clients.forEach(client => {
|
inboundSettings.clients.forEach(client => {
|
||||||
if (list.includes(client.email)) {
|
if (list.includes(client.email)) {
|
||||||
filteredSettings.clients.push(client);
|
filteredSettings.clients.push(client);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
newInbound.settings = Inbound.Settings.fromJson(inbound.protocol, filteredSettings);
|
newInbound.settings = Inbound.Settings.fromJson(inbound.protocol, filteredSettings);
|
||||||
this.searchedInbounds.push(newInbound);
|
this.searchedInbounds.push(newInbound);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
</template> Source IPs <a-icon type="question-circle"></a-icon>
|
</template> Source IPs <a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-input v-model.trim="ruleModal.rule.source"></a-input>
|
<a-input v-model.trim="ruleModal.rule.sourceIP"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
|
@ -123,7 +123,7 @@
|
||||||
port: "",
|
port: "",
|
||||||
sourcePort: "",
|
sourcePort: "",
|
||||||
network: "",
|
network: "",
|
||||||
source: "",
|
sourceIP: "",
|
||||||
user: "",
|
user: "",
|
||||||
inboundTag: [],
|
inboundTag: [],
|
||||||
protocol: [],
|
protocol: [],
|
||||||
|
@ -156,7 +156,7 @@
|
||||||
this.rule.port = rule.port;
|
this.rule.port = rule.port;
|
||||||
this.rule.sourcePort = rule.sourcePort;
|
this.rule.sourcePort = rule.sourcePort;
|
||||||
this.rule.network = rule.network;
|
this.rule.network = rule.network;
|
||||||
this.rule.source = rule.source ? rule.source.join(',') : [];
|
this.rule.sourceIP = rule.sourceIP ? rule.sourceIP.join(',') : [];
|
||||||
this.rule.user = rule.user ? rule.user.join(',') : [];
|
this.rule.user = rule.user ? rule.user.join(',') : [];
|
||||||
this.rule.inboundTag = rule.inboundTag;
|
this.rule.inboundTag = rule.inboundTag;
|
||||||
this.rule.protocol = rule.protocol;
|
this.rule.protocol = rule.protocol;
|
||||||
|
@ -170,7 +170,7 @@
|
||||||
port: "",
|
port: "",
|
||||||
sourcePort: "",
|
sourcePort: "",
|
||||||
network: "",
|
network: "",
|
||||||
source: "",
|
sourceIP: "",
|
||||||
user: "",
|
user: "",
|
||||||
inboundTag: [],
|
inboundTag: [],
|
||||||
protocol: [],
|
protocol: [],
|
||||||
|
@ -211,7 +211,7 @@
|
||||||
rule.port = value.port;
|
rule.port = value.port;
|
||||||
rule.sourcePort = value.sourcePort;
|
rule.sourcePort = value.sourcePort;
|
||||||
rule.network = value.network;
|
rule.network = value.network;
|
||||||
rule.source = value.source.length > 0 ? value.source.split(',') : [];
|
rule.sourceIP = value.sourceIP.length > 0 ? value.sourceIP.split(',') : [];
|
||||||
rule.user = value.user.length > 0 ? value.user.split(',') : [];
|
rule.user = value.user.length > 0 ? value.user.split(',') : [];
|
||||||
rule.inboundTag = value.inboundTag;
|
rule.inboundTag = value.inboundTag;
|
||||||
rule.protocol = value.protocol;
|
rule.protocol = value.protocol;
|
||||||
|
|
|
@ -351,7 +351,7 @@
|
||||||
{ label: '🇨🇳 China', value: 'geosite:cn' },
|
{ label: '🇨🇳 China', value: 'geosite:cn' },
|
||||||
{ label: '🇨🇳 .cn', value: 'regexp:.*\\.cn$' },
|
{ label: '🇨🇳 .cn', value: 'regexp:.*\\.cn$' },
|
||||||
{ label: '🇷🇺 Russia', value: 'ext:geosite_RU.dat:ru-available-only-inside' },
|
{ label: '🇷🇺 Russia', value: 'ext:geosite_RU.dat:ru-available-only-inside' },
|
||||||
{ label: '🇷🇺 .ru', value: 'regexp:.*\\.ru' },
|
{ label: '🇷🇺 .ru', value: 'regexp:.*\\.ru$' },
|
||||||
{ label: '🇷🇺 .su', value: 'regexp:.*\\.su$' },
|
{ label: '🇷🇺 .su', value: 'regexp:.*\\.su$' },
|
||||||
{ label: '🇷🇺 .рф', value: 'regexp:.*\\.xn--p1ai$' },
|
{ label: '🇷🇺 .рф', value: 'regexp:.*\\.xn--p1ai$' },
|
||||||
{ label: '🇻🇳 .vn', value: 'regexp:.*\\.vn$' },
|
{ label: '🇻🇳 .vn', value: 'regexp:.*\\.vn$' },
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -39,8 +40,14 @@ func (j *CheckClientIpJob) Run() {
|
||||||
f2bInstalled := j.checkFail2BanInstalled()
|
f2bInstalled := j.checkFail2BanInstalled()
|
||||||
isAccessLogAvailable := j.checkAccessLogAvailable(iplimitActive)
|
isAccessLogAvailable := j.checkAccessLogAvailable(iplimitActive)
|
||||||
|
|
||||||
|
if isAccessLogAvailable {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
if iplimitActive {
|
if iplimitActive {
|
||||||
if f2bInstalled && isAccessLogAvailable {
|
shouldClearAccessLog = j.processLogFile()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if iplimitActive {
|
||||||
|
if f2bInstalled {
|
||||||
shouldClearAccessLog = j.processLogFile()
|
shouldClearAccessLog = j.processLogFile()
|
||||||
} else {
|
} else {
|
||||||
if !f2bInstalled {
|
if !f2bInstalled {
|
||||||
|
@ -48,6 +55,8 @@ func (j *CheckClientIpJob) Run() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if shouldClearAccessLog || (isAccessLogAvailable && time.Now().Unix()-j.lastClear > 3600) {
|
if shouldClearAccessLog || (isAccessLogAvailable && time.Now().Unix()-j.lastClear > 3600) {
|
||||||
j.clearAccessLog()
|
j.clearAccessLog()
|
||||||
|
|
|
@ -2303,4 +2303,97 @@ func (s *InboundService) syncWithSlaves(method string, path string, body io.Read
|
||||||
logger.Warningf("Failed to sync with server %s. Status: %s, Body: %s", server.Name, resp.Status, string(bodyBytes))
|
logger.Warningf("Failed to sync with server %s. Status: %s, Body: %s", server.Name, resp.Status, string(bodyBytes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) DelInboundClientByEmail(inboundId int, email string) (bool, error) {
|
||||||
|
oldInbound, err := s.GetInbound(inboundId)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Load Old Data Error")
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var settings map[string]any
|
||||||
|
if err := json.Unmarshal([]byte(oldInbound.Settings), &settings); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
interfaceClients, ok := settings["clients"].([]any)
|
||||||
|
if !ok {
|
||||||
|
return false, common.NewError("invalid clients format in inbound settings")
|
||||||
|
}
|
||||||
|
|
||||||
|
var newClients []any
|
||||||
|
needApiDel := false
|
||||||
|
found := false
|
||||||
|
|
||||||
|
for _, client := range interfaceClients {
|
||||||
|
c, ok := client.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if cEmail, ok := c["email"].(string); ok && cEmail == email {
|
||||||
|
// matched client, drop it
|
||||||
|
found = true
|
||||||
|
needApiDel, _ = c["enable"].(bool)
|
||||||
|
} else {
|
||||||
|
newClients = append(newClients, client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return false, common.NewError(fmt.Sprintf("client with email %s not found", email))
|
||||||
|
}
|
||||||
|
if len(newClients) == 0 {
|
||||||
|
return false, common.NewError("no client remained in Inbound")
|
||||||
|
}
|
||||||
|
|
||||||
|
settings["clients"] = newClients
|
||||||
|
newSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
oldInbound.Settings = string(newSettings)
|
||||||
|
|
||||||
|
db := database.GetDB()
|
||||||
|
|
||||||
|
// remove IP bindings
|
||||||
|
if err := s.DelClientIPs(db, email); err != nil {
|
||||||
|
logger.Error("Error in delete client IPs")
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
needRestart := false
|
||||||
|
|
||||||
|
// remove stats too
|
||||||
|
if len(email) > 0 {
|
||||||
|
traffic, err := s.GetClientTrafficByEmail(email)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if traffic != nil {
|
||||||
|
if err := s.DelClientStat(db, email); err != nil {
|
||||||
|
logger.Error("Delete stats Data Error")
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if needApiDel {
|
||||||
|
s.xrayApi.Init(p.GetAPIPort())
|
||||||
|
if err1 := s.xrayApi.RemoveUser(oldInbound.Tag, email); err1 == nil {
|
||||||
|
logger.Debug("Client deleted by api:", email)
|
||||||
|
needRestart = false
|
||||||
|
} else {
|
||||||
|
if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", email)) {
|
||||||
|
logger.Debug("User is already deleted. Nothing to do more...")
|
||||||
|
} else {
|
||||||
|
logger.Debug("Error in deleting client by api:", err1)
|
||||||
|
needRestart = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.xrayApi.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return needRestart, db.Save(oldInbound).Error
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -344,7 +345,7 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if major > 25 || (major == 25 && minor > 8) || (major == 25 && minor == 8 && patch >= 3) {
|
if major > 25 || (major == 25 && minor > 9) || (major == 25 && minor == 9 && patch >= 11) {
|
||||||
versions = append(versions, release.TagName)
|
versions = append(versions, release.TagName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -376,6 +377,8 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
|
||||||
switch osName {
|
switch osName {
|
||||||
case "darwin":
|
case "darwin":
|
||||||
osName = "macos"
|
osName = "macos"
|
||||||
|
case "windows":
|
||||||
|
osName = "windows"
|
||||||
}
|
}
|
||||||
|
|
||||||
switch arch {
|
switch arch {
|
||||||
|
@ -419,19 +422,23 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) UpdateXray(version string) error {
|
func (s *ServerService) UpdateXray(version string) error {
|
||||||
|
// 1. Stop xray before doing anything
|
||||||
|
if err := s.StopXrayService(); err != nil {
|
||||||
|
logger.Warning("failed to stop xray before update:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Download the zip
|
||||||
zipFileName, err := s.downloadXRay(version)
|
zipFileName, err := s.downloadXRay(version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer os.Remove(zipFileName)
|
||||||
|
|
||||||
zipFile, err := os.Open(zipFileName)
|
zipFile, err := os.Open(zipFileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer func() {
|
defer zipFile.Close()
|
||||||
zipFile.Close()
|
|
||||||
os.Remove(zipFileName)
|
|
||||||
}()
|
|
||||||
|
|
||||||
stat, err := zipFile.Stat()
|
stat, err := zipFile.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -442,19 +449,14 @@ func (s *ServerService) UpdateXray(version string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.xrayService.StopXray()
|
// 3. Helper to extract files
|
||||||
defer func() {
|
|
||||||
err := s.xrayService.RestartXray(true)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("start xray failed:", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
copyZipFile := func(zipName string, fileName string) error {
|
copyZipFile := func(zipName string, fileName string) error {
|
||||||
zipFile, err := reader.Open(zipName)
|
zipFile, err := reader.Open(zipName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer zipFile.Close()
|
||||||
|
os.MkdirAll(filepath.Dir(fileName), 0755)
|
||||||
os.Remove(fileName)
|
os.Remove(fileName)
|
||||||
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_TRUNC, fs.ModePerm)
|
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_TRUNC, fs.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -465,11 +467,23 @@ func (s *ServerService) UpdateXray(version string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. Extract correct binary
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
targetBinary := filepath.Join("bin", "xray-windows-amd64.exe")
|
||||||
|
err = copyZipFile("xray.exe", targetBinary)
|
||||||
|
} else {
|
||||||
err = copyZipFile("xray", xray.GetBinaryPath())
|
err = copyZipFile("xray", xray.GetBinaryPath())
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5. Restart xray
|
||||||
|
if err := s.xrayService.RestartXray(true); err != nil {
|
||||||
|
logger.Error("start xray failed:", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue