Compare commits

...

4 commits

Author SHA1 Message Date
mhsanaei
82ddd10627
Fixed: update Xray Core on Windows
Some checks are pending
Release 3X-UI / build (386) (push) Waiting to run
Release 3X-UI / build (amd64) (push) Waiting to run
Release 3X-UI / build (arm64) (push) Waiting to run
Release 3X-UI / build (armv5) (push) Waiting to run
Release 3X-UI / build (armv6) (push) Waiting to run
Release 3X-UI / build (armv7) (push) Waiting to run
Release 3X-UI / build (s390x) (push) Waiting to run
Release 3X-UI / Build for Windows (push) Waiting to run
2025-09-10 21:12:37 +02:00
mhsanaei
2401c99817
rules: source to sourceIP 2025-09-10 18:30:40 +02:00
mhsanaei
2f36a4047c
API: delClientByEmail 2025-09-10 16:36:12 +02:00
mhsanaei
dc3b0d218a
Xray Core v25.9.11 2025-09-10 14:39:07 +02:00
10 changed files with 164 additions and 30 deletions

View file

@ -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.10/" 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.9.10/" $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"

View file

@ -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.10/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}"

View file

@ -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"
} }

2
go.mod
View file

@ -17,7 +17,7 @@ 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.250910.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.42.0 golang.org/x/crypto v0.42.0
golang.org/x/text v0.29.0 golang.org/x/text v0.29.0

4
go.sum
View file

@ -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.250910.0 h1:9KzqL9Ulosp/JVXOMizTZxyQvqv4wkxKDcU5QZcio3s= github.com/xtls/xray-core v1.250911.0 h1:KMN8zVurAjHFixiUoFV/jwmzYohf27dQRntjV+8LQno=
github.com/xtls/xray-core v1.250910.0/go.mod h1:LkqA/BFVtPS2e5fRzg/bkYas9nQu4Uztlx+/fjlLM9k= 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=

View file

@ -46,6 +46,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) {
@ -374,3 +375,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()
}
}

View file

@ -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;

View file

@ -8,6 +8,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"regexp" "regexp"
"runtime"
"sort" "sort"
"time" "time"
@ -40,11 +41,17 @@ func (j *CheckClientIpJob) Run() {
isAccessLogAvailable := j.checkAccessLogAvailable(iplimitActive) isAccessLogAvailable := j.checkAccessLogAvailable(iplimitActive)
if iplimitActive { if iplimitActive {
if f2bInstalled && isAccessLogAvailable { if runtime.GOOS == "windows" {
shouldClearAccessLog = j.processLogFile() if isAccessLogAvailable {
shouldClearAccessLog = j.processLogFile()
}
} else { } else {
if !f2bInstalled { if f2bInstalled && isAccessLogAvailable {
logger.Warning("[LimitIP] Fail2Ban is not installed, Please install Fail2Ban from the x-ui bash menu.") shouldClearAccessLog = j.processLogFile()
} else {
if !f2bInstalled {
logger.Warning("[LimitIP] Fail2Ban is not installed, Please install Fail2Ban from the x-ui bash menu.")
}
} }
} }
} }

View file

@ -2247,3 +2247,95 @@ func (s *InboundService) FilterAndSortClientEmails(emails []string) ([]string, [
return validEmails, extraEmails, nil return validEmails, extraEmails, nil
} }
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
}

View file

@ -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 > 9) || (major == 25 && minor == 9 && patch >= 10) { 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
} }
err = copyZipFile("xray", xray.GetBinaryPath()) // 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())
}
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
} }