This commit is contained in:
Athrav 2026-04-01 15:17:42 +03:30 committed by GitHub
commit 963d9c5e3b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 65 additions and 86 deletions

View file

@ -14,6 +14,7 @@ import (
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/mhsanaei/3x-ui/v2/logger" "github.com/mhsanaei/3x-ui/v2/logger"
"github.com/mhsanaei/3x-ui/v2/util/common" "github.com/mhsanaei/3x-ui/v2/util/common"
@ -362,16 +363,18 @@ func (s *Server) Start() (err error) {
// Stop gracefully shuts down the subscription server and closes the listener. // Stop gracefully shuts down the subscription server and closes the listener.
func (s *Server) Stop() error { func (s *Server) Stop() error {
s.cancel()
var err1 error var err1 error
var err2 error var err2 error
if s.httpServer != nil { if s.httpServer != nil {
err1 = s.httpServer.Shutdown(s.ctx) // Use a fresh timeout context for graceful shutdown
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second)
defer shutdownCancel()
err1 = s.httpServer.Shutdown(shutdownCtx)
} }
if s.listener != nil { if s.listener != nil {
err2 = s.listener.Close() err2 = s.listener.Close()
} }
s.cancel()
return common.Combine(err1, err2) return common.Combine(err1, err2)
} }

View file

@ -107,7 +107,7 @@ func (a *InboundController) addInbound(c *gin.Context) {
inbound := &model.Inbound{} inbound := &model.Inbound{}
err := c.ShouldBind(inbound) err := c.ShouldBind(inbound)
if err != nil { if err != nil {
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundCreateSuccess"), err) jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
return return
} }
user := session.GetLoginUser(c) user := session.GetLoginUser(c)
@ -136,7 +136,7 @@ func (a *InboundController) addInbound(c *gin.Context) {
func (a *InboundController) delInbound(c *gin.Context) { func (a *InboundController) delInbound(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id")) id, err := strconv.Atoi(c.Param("id"))
if err != nil { if err != nil {
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundDeleteSuccess"), err) jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
return return
} }
needRestart, err := a.inboundService.DelInbound(id) needRestart, err := a.inboundService.DelInbound(id)
@ -158,7 +158,7 @@ func (a *InboundController) delInbound(c *gin.Context) {
func (a *InboundController) updateInbound(c *gin.Context) { func (a *InboundController) updateInbound(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id")) id, err := strconv.Atoi(c.Param("id"))
if err != nil { if err != nil {
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), err) jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
return return
} }
inbound := &model.Inbound{ inbound := &model.Inbound{
@ -166,7 +166,7 @@ func (a *InboundController) updateInbound(c *gin.Context) {
} }
err = c.ShouldBind(inbound) err = c.ShouldBind(inbound)
if err != nil { if err != nil {
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), err) jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
return return
} }
inbound, needRestart, err := a.inboundService.UpdateInbound(inbound) inbound, needRestart, err := a.inboundService.UpdateInbound(inbound)
@ -234,7 +234,7 @@ func (a *InboundController) clearClientIps(c *gin.Context) {
err := a.inboundService.ClearClientIps(email) err := a.inboundService.ClearClientIps(email)
if err != nil { if err != nil {
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.updateSuccess"), err) jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
return return
} }
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.logCleanSuccess"), nil) jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.logCleanSuccess"), nil)
@ -245,7 +245,7 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
data := &model.Inbound{} data := &model.Inbound{}
err := c.ShouldBind(data) err := c.ShouldBind(data)
if err != nil { if err != nil {
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), err) jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
return return
} }
@ -264,7 +264,7 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
func (a *InboundController) delInboundClient(c *gin.Context) { func (a *InboundController) delInboundClient(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id")) id, err := strconv.Atoi(c.Param("id"))
if err != nil { if err != nil {
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), err) jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
return return
} }
clientId := c.Param("clientId") clientId := c.Param("clientId")
@ -287,7 +287,7 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
inbound := &model.Inbound{} inbound := &model.Inbound{}
err := c.ShouldBind(inbound) err := c.ShouldBind(inbound)
if err != nil { if err != nil {
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), err) jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
return return
} }
@ -306,7 +306,7 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
func (a *InboundController) resetClientTraffic(c *gin.Context) { func (a *InboundController) resetClientTraffic(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id")) id, err := strconv.Atoi(c.Param("id"))
if err != nil { if err != nil {
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), err) jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
return return
} }
email := c.Param("email") email := c.Param("email")
@ -328,9 +328,8 @@ func (a *InboundController) resetAllTraffics(c *gin.Context) {
if err != nil { if err != nil {
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err) jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
return return
} else {
a.xrayService.SetToNeedRestart()
} }
a.xrayService.SetToNeedRestart()
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.resetAllTrafficSuccess"), nil) jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.resetAllTrafficSuccess"), nil)
} }
@ -338,7 +337,7 @@ func (a *InboundController) resetAllTraffics(c *gin.Context) {
func (a *InboundController) resetAllClientTraffics(c *gin.Context) { func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id")) id, err := strconv.Atoi(c.Param("id"))
if err != nil { if err != nil {
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), err) jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
return return
} }
@ -346,9 +345,8 @@ func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
if err != nil { if err != nil {
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err) jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
return return
} else {
a.xrayService.SetToNeedRestart()
} }
a.xrayService.SetToNeedRestart()
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.resetAllClientTrafficSuccess"), nil) jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.resetAllClientTrafficSuccess"), nil)
} }
@ -386,7 +384,7 @@ func (a *InboundController) importInbound(c *gin.Context) {
func (a *InboundController) delDepletedClients(c *gin.Context) { func (a *InboundController) delDepletedClients(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id")) id, err := strconv.Atoi(c.Param("id"))
if err != nil { if err != nil {
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), err) jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
return return
} }
err = a.inboundService.DelDepletedClients(id) err = a.inboundService.DelDepletedClients(id)
@ -421,7 +419,7 @@ func (a *InboundController) updateClientTraffic(c *gin.Context) {
var request TrafficUpdateRequest var request TrafficUpdateRequest
err := c.ShouldBindJSON(&request) err := c.ShouldBindJSON(&request)
if err != nil { if err != nil {
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), err) jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
return return
} }

View file

@ -33,35 +33,34 @@ var upgrader = ws.Upgrader{
ReadBufferSize: 4096, // Increased from 1024 for better performance ReadBufferSize: 4096, // Increased from 1024 for better performance
WriteBufferSize: 4096, // Increased from 1024 for better performance WriteBufferSize: 4096, // Increased from 1024 for better performance
CheckOrigin: func(r *http.Request) bool { CheckOrigin: func(r *http.Request) bool {
// Check origin for security
origin := r.Header.Get("Origin") origin := r.Header.Get("Origin")
if origin == "" { if origin == "" {
// Allow connections without Origin header (same-origin requests) // Allow connections without Origin header (same-origin requests)
return true return true
} }
// Get the host from the request
host := r.Host // Extract host from origin
// Extract scheme and host from origin originHost := origin
originURL := origin if strings.HasPrefix(originHost, "http://") || strings.HasPrefix(originHost, "https://") {
// Simple check: origin should match the request host originHost = strings.TrimPrefix(strings.TrimPrefix(originHost, "http://"), "https://")
// This prevents cross-origin WebSocket hijacking
if strings.HasPrefix(originURL, "http://") || strings.HasPrefix(originURL, "https://") {
// Extract host from origin
originHost := strings.TrimPrefix(strings.TrimPrefix(originURL, "http://"), "https://")
if idx := strings.Index(originHost, "/"); idx != -1 { if idx := strings.Index(originHost, "/"); idx != -1 {
originHost = originHost[:idx] originHost = originHost[:idx]
} }
if idx := strings.Index(originHost, ":"); idx != -1 {
originHost = originHost[:idx]
}
// Compare hosts (without port)
requestHost := host
if idx := strings.Index(requestHost, ":"); idx != -1 {
requestHost = requestHost[:idx]
}
return originHost == requestHost || originHost == "" || requestHost == ""
} }
return false
// Normalize host for comparison (strip ports and IPv6 brackets)
normalizeHost := func(h string) string {
h = strings.TrimPrefix(h, "[")
if idx := strings.LastIndex(h, "]:"); idx != -1 {
h = h[:idx+1]
}
if idx := strings.LastIndex(h, ":"); idx != -1 && !strings.Contains(h[:idx], "]") {
h = h[:idx]
}
return h
}
return normalizeHost(originHost) == normalizeHost(r.Host)
}, },
} }

View file

@ -30,7 +30,7 @@ func (j *CheckCpuJob) Run() {
// get latest status of server // get latest status of server
percent, err := cpu.Percent(1*time.Minute, false) percent, err := cpu.Percent(1*time.Minute, false)
if err == nil && percent[0] > float64(threshold) { if err == nil && len(percent) > 0 && percent[0] > float64(threshold) {
msg := j.tgbotService.I18nBot("tgbot.messages.cpuThreshold", msg := j.tgbotService.I18nBot("tgbot.messages.cpuThreshold",
"Percent=="+strconv.FormatFloat(percent[0], 'f', 2, 64), "Percent=="+strconv.FormatFloat(percent[0], 'f', 2, 64),
"Threshold=="+strconv.Itoa(threshold)) "Threshold=="+strconv.Itoa(threshold))

View file

@ -190,23 +190,7 @@ func (s *InboundService) checkEmailExistForInbound(inbound *model.Inbound) (stri
if err != nil { if err != nil {
return "", err return "", err
} }
allEmails, err := s.getAllEmails() return s.checkEmailsExistForClients(clients)
if err != nil {
return "", err
}
var emails []string
for _, client := range clients {
if client.Email != "" {
if s.contains(emails, client.Email) {
return client.Email, nil
}
if s.contains(allEmails, client.Email) {
return client.Email, nil
}
emails = append(emails, client.Email)
}
}
return "", nil
} }
// AddInbound creates a new inbound configuration. // AddInbound creates a new inbound configuration.
@ -339,7 +323,7 @@ func (s *InboundService) DelInbound(id int) (bool, error) {
} }
s.xrayApi.Close() s.xrayApi.Close()
} else { } else {
logger.Debug("No enabled inbound founded to removing by api", tag) logger.Debug("No enabled inbound found to remove by api for inbound id:", id)
} }
// Delete client traffics of inbounds // Delete client traffics of inbounds
@ -1280,12 +1264,8 @@ func (s *InboundService) disableInvalidClients(tx *gorm.DB) (bool, int64, error)
if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", result.Email)) { if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", result.Email)) {
logger.Debug("User is already disabled. Nothing to do more...") logger.Debug("User is already disabled. Nothing to do more...")
} else { } else {
if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", result.Email)) { logger.Debug("Error in disabling client by api:", err1)
logger.Debug("User is already disabled. Nothing to do more...") needRestart = true
} else {
logger.Debug("Error in disabling client by api:", err1)
needRestart = true
}
} }
} }
} }
@ -1458,16 +1438,16 @@ func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId int64) (boo
return false, err return false, err
} }
clients := settings["clients"].([]any) clients := settings["clients"].([]any)
var newClients []any
for client_index := range clients { for client_index := range clients {
c := clients[client_index].(map[string]any) c := clients[client_index].(map[string]any)
if c["email"] == clientEmail { if c["email"] == clientEmail {
c["tgId"] = tgId c["tgId"] = tgId
c["updated_at"] = time.Now().Unix() * 1000 c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c)) clients[client_index] = c
break
} }
} }
settings["clients"] = newClients settings["clients"] = clients
modifiedSettings, err := json.MarshalIndent(settings, "", " ") modifiedSettings, err := json.MarshalIndent(settings, "", " ")
if err != nil { if err != nil {
return false, err return false, err
@ -1545,16 +1525,16 @@ func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, bo
return false, false, err return false, false, err
} }
clients := settings["clients"].([]any) clients := settings["clients"].([]any)
var newClients []any
for client_index := range clients { for client_index := range clients {
c := clients[client_index].(map[string]any) c := clients[client_index].(map[string]any)
if c["email"] == clientEmail { if c["email"] == clientEmail {
c["enable"] = !clientOldEnabled c["enable"] = !clientOldEnabled
c["updated_at"] = time.Now().Unix() * 1000 c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c)) clients[client_index] = c
break
} }
} }
settings["clients"] = newClients settings["clients"] = clients
modifiedSettings, err := json.MarshalIndent(settings, "", " ") modifiedSettings, err := json.MarshalIndent(settings, "", " ")
if err != nil { if err != nil {
return false, false, err return false, false, err
@ -1625,16 +1605,16 @@ func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int
return false, err return false, err
} }
clients := settings["clients"].([]any) clients := settings["clients"].([]any)
var newClients []any
for client_index := range clients { for client_index := range clients {
c := clients[client_index].(map[string]any) c := clients[client_index].(map[string]any)
if c["email"] == clientEmail { if c["email"] == clientEmail {
c["limitIp"] = count c["limitIp"] = count
c["updated_at"] = time.Now().Unix() * 1000 c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c)) clients[client_index] = c
break
} }
} }
settings["clients"] = newClients settings["clients"] = clients
modifiedSettings, err := json.MarshalIndent(settings, "", " ") modifiedSettings, err := json.MarshalIndent(settings, "", " ")
if err != nil { if err != nil {
return false, err return false, err
@ -1684,16 +1664,16 @@ func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry
return false, err return false, err
} }
clients := settings["clients"].([]any) clients := settings["clients"].([]any)
var newClients []any
for client_index := range clients { for client_index := range clients {
c := clients[client_index].(map[string]any) c := clients[client_index].(map[string]any)
if c["email"] == clientEmail { if c["email"] == clientEmail {
c["expiryTime"] = expiry_time c["expiryTime"] = expiry_time
c["updated_at"] = time.Now().Unix() * 1000 c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c)) clients[client_index] = c
break
} }
} }
settings["clients"] = newClients settings["clients"] = clients
modifiedSettings, err := json.MarshalIndent(settings, "", " ") modifiedSettings, err := json.MarshalIndent(settings, "", " ")
if err != nil { if err != nil {
return false, err return false, err
@ -1746,16 +1726,16 @@ func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, tota
return false, err return false, err
} }
clients := settings["clients"].([]any) clients := settings["clients"].([]any)
var newClients []any
for client_index := range clients { for client_index := range clients {
c := clients[client_index].(map[string]any) c := clients[client_index].(map[string]any)
if c["email"] == clientEmail { if c["email"] == clientEmail {
c["totalGB"] = totalGB * 1024 * 1024 * 1024 c["totalGB"] = totalGB * 1024 * 1024 * 1024
c["updated_at"] = time.Now().Unix() * 1000 c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c)) clients[client_index] = c
break
} }
} }
settings["clients"] = newClients settings["clients"] = clients
modifiedSettings, err := json.MarshalIndent(settings, "", " ") modifiedSettings, err := json.MarshalIndent(settings, "", " ")
if err != nil { if err != nil {
return false, err return false, err

View file

@ -198,8 +198,7 @@ func (s *SettingService) ResetSettings() error {
if err != nil { if err != nil {
return err return err
} }
return db.Model(model.User{}). return db.Where("1 = 1").Delete(model.User{}).Error
Where("1 = 1").Error
} }
func (s *SettingService) getSetting(key string) (*model.Setting, error) { func (s *SettingService) getSetting(key string) (*model.Setting, error) {

View file

@ -344,7 +344,6 @@ func (s *Server) startTask() {
} }
// Make a traffic condition every day, 8:30 // Make a traffic condition every day, 8:30
var entry cron.EntryID
isTgbotenabled, err := s.settingService.GetTgbotEnabled() isTgbotenabled, err := s.settingService.GetTgbotEnabled()
if (err == nil) && (isTgbotenabled) { if (err == nil) && (isTgbotenabled) {
runtime, err := s.settingService.GetTgbotRuntime() runtime, err := s.settingService.GetTgbotRuntime()
@ -367,8 +366,6 @@ func (s *Server) startTask() {
if (err == nil) && (cpuThreshold > 0) { if (err == nil) && (cpuThreshold > 0) {
s.cron.AddJob("@every 10s", job.NewCheckCpuJob()) s.cron.AddJob("@every 10s", job.NewCheckCpuJob())
} }
} else {
s.cron.Remove(entry)
} }
} }
@ -453,7 +450,6 @@ func (s *Server) Start() (err error) {
// Stop gracefully shuts down the web server, stops Xray, cron jobs, and Telegram bot. // Stop gracefully shuts down the web server, stops Xray, cron jobs, and Telegram bot.
func (s *Server) Stop() error { func (s *Server) Stop() error {
s.cancel()
s.xrayService.StopXray() s.xrayService.StopXray()
if s.cron != nil { if s.cron != nil {
s.cron.Stop() s.cron.Stop()
@ -468,11 +464,15 @@ func (s *Server) Stop() error {
var err1 error var err1 error
var err2 error var err2 error
if s.httpServer != nil { if s.httpServer != nil {
err1 = s.httpServer.Shutdown(s.ctx) // Use a fresh timeout context for graceful shutdown
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second)
defer shutdownCancel()
err1 = s.httpServer.Shutdown(shutdownCtx)
} }
if s.listener != nil { if s.listener != nil {
err2 = s.listener.Close() err2 = s.listener.Close()
} }
s.cancel()
return common.Combine(err1, err2) return common.Combine(err1, err2)
} }