mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-16 08:10:58 +00:00
style: adopt golangci-lint v2 and resolve all findings
Add .golangci.yml (v2): the standard linters plus bodyclose, errorlint, noctx, misspell, rowserrcheck, sqlclosecheck, unconvert, usestdlibvars, with gofumpt + goimports formatters. Enable the std-error-handling exclusion preset for idiomatic Close/Remove/Setenv ignores; scope-exclude SA1019 (parser.ParseDir in tools/openapigen) and ST1005 (intentional capitalized user-facing error copy that tests assert verbatim). No inline nolint directives were introduced. Resolve all 217 findings behavior-preserving: gofumpt/goimports formatting, explicit blank assignment on intentionally ignored errors, errors.Is/errors.As and %w wrapping, context-aware stdlib calls (CommandContext/QueryContext/NewRequestWithContext/Dialer), staticcheck simplifications, removed redundant conversions, http.StatusOK and http.MethodGet, inlined the go:fix intPtr helper, and deferred sql rows Close. Add a golangci CI job mirroring the existing Go jobs.
This commit is contained in:
@@ -34,10 +34,14 @@ func TestResetClientExpiryTimeByEmail_MultiInbound(t *testing.T) {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
first := &model.Inbound{Tag: "vless-a", Enable: true, Port: 50001, Protocol: model.VLESS,
|
||||
StreamSettings: `{"network":"tcp","security":"reality"}`, Settings: clientJSON(oldExpiry)}
|
||||
second := &model.Inbound{Tag: "vless-b", Enable: true, Port: 50002, Protocol: model.VLESS,
|
||||
StreamSettings: `{"network":"ws","security":"tls"}`, Settings: clientJSON(oldExpiry)}
|
||||
first := &model.Inbound{
|
||||
Tag: "vless-a", Enable: true, Port: 50001, Protocol: model.VLESS,
|
||||
StreamSettings: `{"network":"tcp","security":"reality"}`, Settings: clientJSON(oldExpiry),
|
||||
}
|
||||
second := &model.Inbound{
|
||||
Tag: "vless-b", Enable: true, Port: 50002, Protocol: model.VLESS,
|
||||
StreamSettings: `{"network":"ws","security":"tls"}`, Settings: clientJSON(oldExpiry),
|
||||
}
|
||||
for _, ib := range []*model.Inbound{first, second} {
|
||||
if err := db.Create(ib).Error; err != nil {
|
||||
t.Fatalf("create inbound %s: %v", ib.Tag, err)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/logger"
|
||||
|
||||
@@ -40,8 +40,10 @@ func flowOf(t *testing.T, svc *ClientService, email string) string {
|
||||
return rec.Flow
|
||||
}
|
||||
|
||||
const realityStream = `{"network":"tcp","security":"reality"}`
|
||||
const wsStream = `{"network":"ws","security":"none"}`
|
||||
const (
|
||||
realityStream = `{"network":"tcp","security":"reality"}`
|
||||
wsStream = `{"network":"ws","security":"none"}`
|
||||
)
|
||||
|
||||
// TestBulkAdjust_FlowSetAndClear covers the happy path: a vision flow is applied
|
||||
// on an eligible VLESS inbound and later cleared with the "none" directive. Both
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/util/common"
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/xray"
|
||||
|
||||
@@ -84,7 +84,7 @@ func (s *ClientService) BulkResetTraffic(inboundSvc *InboundService, emails []st
|
||||
if err == nil && !rec.Enable {
|
||||
updated := rec.ToClient()
|
||||
updated.Enable = true
|
||||
s.Update(inboundSvc, rec.Id, *updated)
|
||||
_, _ = s.Update(inboundSvc, rec.Id, *updated)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package email
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -115,10 +116,10 @@ func (s *EmailService) TestConnection() SMTPTestResult {
|
||||
|
||||
switch encryptionType {
|
||||
case "tls":
|
||||
conn, err = tls.DialWithDialer(dialer, "tcp", addr, &tls.Config{
|
||||
conn, err = (&tls.Dialer{NetDialer: dialer, Config: &tls.Config{
|
||||
ServerName: host,
|
||||
InsecureSkipVerify: false,
|
||||
})
|
||||
}}).DialContext(context.Background(), "tcp", addr)
|
||||
default:
|
||||
conn, err = dialer.Dial("tcp", addr)
|
||||
}
|
||||
@@ -188,10 +189,10 @@ func (s *EmailService) TestConnection() SMTPTestResult {
|
||||
func (s *EmailService) sendWithTLS(addr string, auth smtp.Auth, from string, to []string, msg []byte, host string) error {
|
||||
// Dial with explicit timeout
|
||||
dialer := &net.Dialer{Timeout: 10 * time.Second}
|
||||
conn, err := tls.DialWithDialer(dialer, "tcp", addr, &tls.Config{
|
||||
conn, err := (&tls.Dialer{NetDialer: dialer, Config: &tls.Config{
|
||||
ServerName: host,
|
||||
InsecureSkipVerify: false,
|
||||
})
|
||||
}}).DialContext(context.Background(), "tcp", addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -289,7 +290,7 @@ func buildMessage(from string, to []string, subject, body string) []byte {
|
||||
}
|
||||
var msg strings.Builder
|
||||
for k, v := range headers {
|
||||
msg.WriteString(fmt.Sprintf("%s: %s\r\n", k, v))
|
||||
fmt.Fprintf(&msg, "%s: %s\r\n", k, v)
|
||||
}
|
||||
msg.WriteString("\r\n")
|
||||
msg.WriteString(body)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -46,7 +47,7 @@ func (s *FallbackService) GetParentForChild(childId int) (*model.InboundFallback
|
||||
Where("child_id = ?", childId).
|
||||
Order("sort_order ASC, id ASC").
|
||||
First(&row).Error
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
@@ -5,6 +5,7 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
@@ -153,7 +154,7 @@ func (s *InboundService) GetInbounds(userId int) ([]*model.Inbound, error) {
|
||||
db := database.GetDB()
|
||||
var inbounds []*model.Inbound
|
||||
err := db.Model(model.Inbound{}).Preload("ClientStats").Where("user_id = ?", userId).Order("id ASC").Find(&inbounds).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
s.enrichClientStats(db, inbounds)
|
||||
@@ -196,7 +197,7 @@ func (s *InboundService) GetInboundsSlim(userId int) ([]*model.Inbound, error) {
|
||||
db := database.GetDB()
|
||||
var inbounds []*model.Inbound
|
||||
err := db.Model(model.Inbound{}).Preload("ClientStats").Where("user_id = ?", userId).Order("id ASC").Find(&inbounds).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
s.annotateFallbackParents(db, inbounds)
|
||||
@@ -319,7 +320,7 @@ func (s *InboundService) GetInboundOptions(userId int) ([]InboundOption, error)
|
||||
Where("user_id = ?", userId).
|
||||
Order("id ASC").
|
||||
Scan(&rows).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]InboundOption, 0, len(rows))
|
||||
@@ -343,7 +344,7 @@ func (s *InboundService) GetAllInbounds() ([]*model.Inbound, error) {
|
||||
db := database.GetDB()
|
||||
var inbounds []*model.Inbound
|
||||
err := db.Model(model.Inbound{}).Preload("ClientStats").Find(&inbounds).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
s.enrichClientStats(db, inbounds)
|
||||
@@ -354,7 +355,7 @@ func (s *InboundService) GetInboundsByTrafficReset(period string) ([]*model.Inbo
|
||||
db := database.GetDB()
|
||||
var inbounds []*model.Inbound
|
||||
err := db.Model(model.Inbound{}).Where("traffic_reset = ?", period).Find(&inbounds).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
return inbounds, nil
|
||||
@@ -362,7 +363,7 @@ func (s *InboundService) GetInboundsByTrafficReset(period string) ([]*model.Inbo
|
||||
|
||||
func (s *InboundService) GetClients(inbound *model.Inbound) ([]model.Client, error) {
|
||||
settings := map[string][]model.Client{}
|
||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
_ = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
if settings == nil {
|
||||
return nil, fmt.Errorf("setting is null")
|
||||
}
|
||||
@@ -1348,7 +1349,7 @@ func (s *InboundService) GetInboundTags() (string, error) {
|
||||
db := database.GetDB()
|
||||
var inboundTags []string
|
||||
err := db.Model(model.Inbound{}).Select("tag").Find(&inboundTags).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return "", err
|
||||
}
|
||||
tags, _ := json.Marshal(inboundTags)
|
||||
@@ -1359,7 +1360,7 @@ func (s *InboundService) GetClientReverseTags() (string, error) {
|
||||
db := database.GetDB()
|
||||
var inbounds []model.Inbound
|
||||
err := db.Model(model.Inbound{}).Select("settings").Where("protocol = ?", "vless").Find(&inbounds).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return "[]", err
|
||||
}
|
||||
|
||||
@@ -1404,7 +1405,7 @@ func (s *InboundService) SearchInbounds(query string) ([]*model.Inbound, error)
|
||||
db := database.GetDB()
|
||||
var inbounds []*model.Inbound
|
||||
err := db.Model(model.Inbound{}).Preload("ClientStats").Where("remark like ?", "%"+query+"%").Find(&inbounds).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
return inbounds, nil
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/logger"
|
||||
|
||||
@@ -27,7 +27,7 @@ func (s *InboundService) disableInvalidInbounds(tx *gorm.DB) (bool, int64, error
|
||||
if err != nil {
|
||||
return false, 0, err
|
||||
}
|
||||
s.xrayApi.Init(p.GetAPIPort())
|
||||
_ = s.xrayApi.Init(p.GetAPIPort())
|
||||
for _, tag := range tags {
|
||||
err1 := s.xrayApi.DelInbound(tag)
|
||||
if err1 == nil {
|
||||
@@ -141,7 +141,7 @@ func (s *InboundService) disableInvalidClients(tx *gorm.DB) (bool, int64, []int,
|
||||
}
|
||||
|
||||
if p != nil && len(localTargets) > 0 {
|
||||
s.xrayApi.Init(p.GetAPIPort())
|
||||
_ = s.xrayApi.Init(p.GetAPIPort())
|
||||
for _, t := range localTargets {
|
||||
err1 := s.xrayApi.RemoveUser(t.Tag, t.Email)
|
||||
if err1 == nil {
|
||||
@@ -231,7 +231,7 @@ func (s *InboundService) markClientsDisabledInSettings(tx *gorm.DB, inboundID in
|
||||
if _, hit := emails[email]; !hit {
|
||||
continue
|
||||
}
|
||||
if cur, _ := entry["enable"].(bool); cur == false {
|
||||
if cur, _ := entry["enable"].(bool); !cur {
|
||||
continue
|
||||
}
|
||||
entry["enable"] = false
|
||||
|
||||
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -93,12 +94,12 @@ func (s *InboundService) MigrationRequirements() {
|
||||
// Fix inbounds based problems
|
||||
var inbounds []*model.Inbound
|
||||
err = tx.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan", "shadowsocks", "hysteria"}).Find(&inbounds).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return
|
||||
}
|
||||
for inbound_index := range inbounds {
|
||||
settings := map[string]any{}
|
||||
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||
_ = json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||
if raw, exists := settings["clients"]; exists && raw == nil {
|
||||
settings["clients"] = []any{}
|
||||
}
|
||||
@@ -117,7 +118,7 @@ func (s *InboundService) MigrationRequirements() {
|
||||
|
||||
// Convert string tgId to int64
|
||||
if _, ok := c["tgId"]; ok {
|
||||
var tgId any = c["tgId"]
|
||||
tgId := c["tgId"]
|
||||
if tgIdStr, ok2 := tgId.(string); ok2 {
|
||||
tgIdInt64, err := strconv.ParseInt(strings.ReplaceAll(tgIdStr, " ", ""), 10, 64)
|
||||
if err == nil {
|
||||
@@ -170,7 +171,7 @@ func (s *InboundService) MigrationRequirements() {
|
||||
var count int64
|
||||
tx.Model(xray.ClientTraffic{}).Where("email = ?", modelClient.Email).Count(&count)
|
||||
if count == 0 {
|
||||
s.AddClientStat(tx, inbounds[inbound_index].Id, &modelClient)
|
||||
_ = s.AddClientStat(tx, inbounds[inbound_index].Id, &modelClient)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,7 +213,7 @@ func (s *InboundService) MigrationRequirements() {
|
||||
for _, ep := range externalProxy {
|
||||
var reverses any
|
||||
var stream map[string]any
|
||||
json.Unmarshal([]byte(ep.StreamSettings), &stream)
|
||||
_ = json.Unmarshal([]byte(ep.StreamSettings), &stream)
|
||||
if tlsSettings, ok := stream["tlsSettings"].(map[string]any); ok {
|
||||
if settings, ok := tlsSettings["settings"].(map[string]any); ok {
|
||||
if domains, ok := settings["domains"].([]any); ok {
|
||||
|
||||
@@ -999,7 +999,7 @@ func (s *InboundService) GetClientsLastOnline() (map[string]int64, error) {
|
||||
db := database.GetDB()
|
||||
var rows []xray.ClientTraffic
|
||||
err := db.Model(&xray.ClientTraffic{}).Select("email, last_online").Find(&rows).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
result := make(map[string]int64, len(rows))
|
||||
@@ -1029,7 +1029,7 @@ func (s *InboundService) FilterAndSortClientEmails(emails []string) ([]string, [
|
||||
clients := make([]xray.ClientTraffic, 0, len(uniqEmails))
|
||||
for _, batch := range chunkStrings(uniqEmails, sqliteMaxVars) {
|
||||
var page []xray.ClientTraffic
|
||||
if err := db.Where("email IN ?", batch).Find(&page).Error; err != nil && err != gorm.ErrRecordNotFound {
|
||||
if err := db.Where("email IN ?", batch).Find(&page).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil, err
|
||||
}
|
||||
clients = append(clients, page...)
|
||||
|
||||
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -224,7 +225,7 @@ func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.Cl
|
||||
}
|
||||
for inbound_index := range inbounds {
|
||||
settings := map[string]any{}
|
||||
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||
_ = json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||
clients, ok := settings["clients"].([]any)
|
||||
if ok {
|
||||
var newClients []any
|
||||
@@ -357,7 +358,7 @@ func (s *InboundService) autoRenewClients(tx *gorm.DB) (bool, int64, error) {
|
||||
}
|
||||
for inbound_index := range inbounds {
|
||||
settings := map[string]any{}
|
||||
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||
_ = json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||
clients, _ := settings["clients"].([]any)
|
||||
if len(clients) == 0 {
|
||||
continue
|
||||
@@ -760,7 +761,7 @@ func (s *InboundService) DelDepletedClients(id int) (err error) {
|
||||
continue
|
||||
}
|
||||
if len(newClients) == 0 {
|
||||
s.DelInbound(inbound.Id)
|
||||
_, _ = s.DelInbound(inbound.Id)
|
||||
continue
|
||||
}
|
||||
settings["clients"] = newClients
|
||||
@@ -827,7 +828,7 @@ func (s *InboundService) GetClientTrafficTgBot(tgId int64) ([]*xray.ClientTraffi
|
||||
|
||||
// Retrieve inbounds where settings contain the given tgId
|
||||
err := db.Model(model.Inbound{}).Where("settings LIKE ?", fmt.Sprintf(`%%"tgId": %d%%`, tgId)).Find(&inbounds).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
logger.Errorf("Error retrieving inbounds with tgId %d: %v", tgId, err)
|
||||
return nil, err
|
||||
}
|
||||
@@ -853,7 +854,7 @@ func (s *InboundService) GetClientTrafficTgBot(tgId int64) ([]*xray.ClientTraffi
|
||||
for _, batch := range chunkStrings(uniqEmails, sqliteMaxVars) {
|
||||
var page []*xray.ClientTraffic
|
||||
if err = db.Model(xray.ClientTraffic{}).Where("email IN ?", batch).Find(&page).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
continue
|
||||
}
|
||||
logger.Errorf("Error retrieving ClientTraffic for emails %v: %v", batch, err)
|
||||
@@ -1008,7 +1009,7 @@ func (s *InboundService) SearchClientTraffic(query string) (traffic *xray.Client
|
||||
// Search for inbound settings that contain the query
|
||||
err = db.Model(model.Inbound{}).Where("settings LIKE ?", "%\""+query+"\"%").First(inbound).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
logger.Warningf("Inbound settings containing query %s not found: %v", query, err)
|
||||
return nil, err
|
||||
}
|
||||
@@ -1041,7 +1042,7 @@ func (s *InboundService) SearchClientTraffic(query string) (traffic *xray.Client
|
||||
// Retrieve ClientTraffic based on the found email
|
||||
err = db.Model(xray.ClientTraffic{}).Where("email = ?", traffic.Email).First(traffic).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
logger.Warningf("ClientTraffic for email %s not found: %v", traffic.Email, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ func TestUpdateInbound_RegeneratesAutoTagOnPortChange(t *testing.T) {
|
||||
// the returned object) which is what the save would use.
|
||||
func TestUpdateInbound_NodeTagKeepsPrefixWhenNodeIdOmitted(t *testing.T) {
|
||||
setupConflictDB(t)
|
||||
seedInboundConflictNode(t, "n1-in-443-tcp", "0.0.0.0", 443, model.VLESS, `{"network":"tcp"}`, `{"clients":[]}`, intPtr(1))
|
||||
seedInboundConflictNode(t, "n1-in-443-tcp", "0.0.0.0", 443, model.VLESS, `{"network":"tcp"}`, `{"clients":[]}`, new(1))
|
||||
|
||||
var existing model.Inbound
|
||||
if err := database.GetDB().Where("tag = ?", "n1-in-443-tcp").First(&existing).Error; err != nil {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -21,7 +22,11 @@ var nordHTTPClient = &http.Client{Timeout: 15 * time.Second}
|
||||
const maxResponseSize = 10 << 20
|
||||
|
||||
func (s *NordService) GetCountries() (string, error) {
|
||||
resp, err := nordHTTPClient.Get("https://api.nordvpn.com/v1/countries")
|
||||
req, reqErr := http.NewRequestWithContext(context.Background(), http.MethodGet, "https://api.nordvpn.com/v1/countries", nil)
|
||||
if reqErr != nil {
|
||||
return "", reqErr
|
||||
}
|
||||
resp, err := nordHTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -44,7 +49,11 @@ func (s *NordService) GetServers(countryId string) (string, error) {
|
||||
}
|
||||
}
|
||||
url := fmt.Sprintf("https://api.nordvpn.com/v2/servers?limit=0&filters[servers_technologies][id]=35&filters[country_id]=%s", countryId)
|
||||
resp, err := nordHTTPClient.Get(url)
|
||||
req, reqErr := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
|
||||
if reqErr != nil {
|
||||
return "", reqErr
|
||||
}
|
||||
resp, err := nordHTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -89,7 +98,7 @@ func (s *NordService) SetKey(privateKey string) (string, error) {
|
||||
"token": "",
|
||||
}
|
||||
data, _ := json.Marshal(nordData)
|
||||
err := s.SettingService.SetNord(string(data))
|
||||
err := s.SetNord(string(data))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -98,7 +107,7 @@ func (s *NordService) SetKey(privateKey string) (string, error) {
|
||||
|
||||
func (s *NordService) GetCredentials(token string) (string, error) {
|
||||
url := "https://api.nordvpn.com/v1/users/services/credentials"
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -134,7 +143,7 @@ func (s *NordService) GetCredentials(token string) (string, error) {
|
||||
"token": token,
|
||||
}
|
||||
data, _ := json.Marshal(nordData)
|
||||
err = s.SettingService.SetNord(string(data))
|
||||
err = s.SetNord(string(data))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -143,9 +152,9 @@ func (s *NordService) GetCredentials(token string) (string, error) {
|
||||
}
|
||||
|
||||
func (s *NordService) GetNordData() (string, error) {
|
||||
return s.SettingService.GetNord()
|
||||
return s.GetNord()
|
||||
}
|
||||
|
||||
func (s *NordService) DelNordData() error {
|
||||
return s.SettingService.SetNord("")
|
||||
return s.SetNord("")
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -27,11 +28,11 @@ const (
|
||||
)
|
||||
|
||||
func (s *WarpService) GetWarpData() (string, error) {
|
||||
return s.SettingService.GetWarp()
|
||||
return s.GetWarp()
|
||||
}
|
||||
|
||||
func (s *WarpService) DelWarpData() error {
|
||||
return s.SettingService.SetWarp("")
|
||||
return s.SetWarp("")
|
||||
}
|
||||
|
||||
func (s *WarpService) GetWarpConfig() (string, error) {
|
||||
@@ -41,7 +42,7 @@ func (s *WarpService) GetWarpConfig() (string, error) {
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/reg/%s", warpAPIBase, warpData["device_id"])
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -67,7 +68,7 @@ func (s *WarpService) RegWarp(secretKey string, publicKey string) (string, error
|
||||
return "", err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, warpAPIBase+"/reg", bytes.NewReader(reqBody))
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, warpAPIBase+"/reg", bytes.NewReader(reqBody))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -116,7 +117,7 @@ func (s *WarpService) RegWarp(secretKey string, publicKey string) (string, error
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := s.SettingService.SetWarp(string(warpJSON)); err != nil {
|
||||
if err := s.SetWarp(string(warpJSON)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -142,7 +143,7 @@ func (s *WarpService) SetWarpLicense(license string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(reqBody))
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodPut, url, bytes.NewReader(reqBody))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -167,7 +168,7 @@ func (s *WarpService) SetWarpLicense(license string) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := s.SettingService.SetWarp(string(newWarpData)); err != nil {
|
||||
if err := s.SetWarp(string(newWarpData)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(newWarpData), nil
|
||||
@@ -213,7 +214,7 @@ func (s *WarpService) ChangeWarpIP() (string, error) {
|
||||
|
||||
// loadWarpCreds reads the stored warp JSON and ensures access_token + device_id are set.
|
||||
func (s *WarpService) loadWarpCreds() (map[string]string, error) {
|
||||
warp, err := s.SettingService.GetWarp()
|
||||
warp, err := s.GetWarp()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -832,7 +832,7 @@ func (s *NodeService) withOutboundBridge(nodeID int, outboundTag string, fn func
|
||||
return
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
listener, err := (&net.ListenConfig{}).Listen(context.Background(), "tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
fn("")
|
||||
return
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/web/runtime"
|
||||
@@ -33,10 +34,12 @@ func (f *fakeNodeRuntime) UpdateUser(context.Context, *model.Inbound, string, mo
|
||||
f.updateUser.Add(1)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeNodeRuntime) DeleteUser(context.Context, *model.Inbound, string) error {
|
||||
f.deleteUser.Add(1)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeNodeRuntime) AddClient(context.Context, *model.Inbound, model.Client) error {
|
||||
f.addClient.Add(1)
|
||||
return nil
|
||||
|
||||
@@ -5,11 +5,12 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/web/runtime"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/xray"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func initTrafficTestDB(t *testing.T) *gorm.DB {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -196,7 +197,7 @@ func (s *OutboundService) testOutboundTCP(outboundJSON string) (*TestOutboundRes
|
||||
func probeTCPEndpoint(endpoint string, timeout time.Duration) TestEndpointResult {
|
||||
r := TestEndpointResult{Address: endpoint}
|
||||
start := time.Now()
|
||||
conn, err := net.DialTimeout("tcp", endpoint, timeout)
|
||||
conn, err := (&net.Dialer{Timeout: timeout}).DialContext(context.Background(), "tcp", endpoint)
|
||||
r.Delay = time.Since(start).Milliseconds()
|
||||
if err != nil {
|
||||
r.Error = err.Error()
|
||||
|
||||
@@ -101,7 +101,7 @@ func (s *OutboundService) TestOutbound(outboundJSON string, testURL string, allO
|
||||
func (s *OutboundService) TestOutbounds(outboundsJSON string, testURL string, allOutboundsJSON string, mode string) ([]*TestOutboundResult, error) {
|
||||
var raw []json.RawMessage
|
||||
if err := json.Unmarshal([]byte(outboundsJSON), &raw); err != nil {
|
||||
return nil, fmt.Errorf("invalid outbounds JSON: %v", err)
|
||||
return nil, fmt.Errorf("invalid outbounds JSON: %w", err)
|
||||
}
|
||||
if len(raw) > maxBatchItems {
|
||||
return nil, fmt.Errorf("too many outbounds in one request (max %d)", maxBatchItems)
|
||||
@@ -253,7 +253,7 @@ func (s *OutboundService) testOutboundsParsed(items []map[string]any, testURL st
|
||||
func runHTTPProbeBatch(items []*httpBatchItem, allOutbounds []any, testURL string) (retryPerItem bool, err error) {
|
||||
ports, release, err := reserveLoopbackPorts(len(items))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Failed to reserve test ports: %v", err)
|
||||
return false, fmt.Errorf("Failed to reserve test ports: %w", err)
|
||||
}
|
||||
defer release()
|
||||
|
||||
@@ -261,14 +261,14 @@ func runHTTPProbeBatch(items []*httpBatchItem, allOutbounds []any, testURL strin
|
||||
|
||||
configPath, err := createTestConfigPath()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Failed to create test config path: %v", err)
|
||||
return false, fmt.Errorf("Failed to create test config path: %w", err)
|
||||
}
|
||||
defer os.Remove(configPath)
|
||||
|
||||
proc := newBatchProcess(cfg, configPath)
|
||||
defer func() {
|
||||
if proc.IsRunning() {
|
||||
proc.Stop()
|
||||
_ = proc.Stop()
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -279,9 +279,9 @@ func runHTTPProbeBatch(items []*httpBatchItem, allOutbounds []any, testURL strin
|
||||
if err := proc.Start(); err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
// Binary missing — per-item retries would all fail the same way.
|
||||
return false, fmt.Errorf("Failed to start test xray instance: %v", err)
|
||||
return false, fmt.Errorf("Failed to start test xray instance: %w", err)
|
||||
}
|
||||
return true, fmt.Errorf("Failed to start test xray instance: %v", err)
|
||||
return true, fmt.Errorf("Failed to start test xray instance: %w", err)
|
||||
}
|
||||
|
||||
if err := waitForPortsReady(proc, ports, batchPortsReadyTimeout); err != nil {
|
||||
@@ -330,7 +330,7 @@ func waitForPortsReady(proc batchProcess, ports []int, timeout time.Duration) *p
|
||||
if !proc.IsRunning() {
|
||||
return &portsReadyError{msg: "Xray process exited: " + proc.GetResult(), exited: true}
|
||||
}
|
||||
conn, err := net.DialTimeout("tcp", fmt.Sprintf("127.0.0.1:%d", port), 100*time.Millisecond)
|
||||
conn, err := (&net.Dialer{Timeout: 100 * time.Millisecond}).DialContext(context.Background(), "tcp", fmt.Sprintf("127.0.0.1:%d", port))
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
break
|
||||
@@ -529,7 +529,7 @@ func reserveLoopbackPorts(n int) ([]int, func(), error) {
|
||||
}
|
||||
ports := make([]int, 0, n)
|
||||
for range n {
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
l, err := (&net.ListenConfig{}).Listen(context.Background(), "tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
release()
|
||||
return nil, nil, err
|
||||
|
||||
@@ -281,7 +281,7 @@ func (s *OutboundSubscriptionService) fetchAndStore(sub *model.OutboundSubscript
|
||||
return rejectPrivateHost(ctx, req.URL.Hostname())
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", sub.Url, nil)
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, sub.Url, nil)
|
||||
if err != nil {
|
||||
s.recordError(sub, err)
|
||||
return nil, err
|
||||
@@ -295,7 +295,7 @@ func (s *OutboundSubscriptionService) fetchAndStore(sub *model.OutboundSubscript
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
err := fmt.Errorf("http %d", resp.StatusCode)
|
||||
s.recordError(sub, err)
|
||||
return nil, err
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package panel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -157,7 +158,7 @@ func (s *PanelService) startUpdate(useDev bool) error {
|
||||
|
||||
if systemdRun, err := exec.LookPath("systemd-run"); err == nil {
|
||||
unitName := fmt.Sprintf("x-ui-web-update-%d", time.Now().Unix())
|
||||
cmd := exec.Command(systemdRun,
|
||||
cmd := exec.CommandContext(context.Background(), systemdRun,
|
||||
"--unit", unitName,
|
||||
"--setenv", "XUI_MAIN_FOLDER="+mainFolder,
|
||||
"--setenv", "XUI_SERVICE="+serviceFolder,
|
||||
@@ -179,7 +180,7 @@ func (s *PanelService) startUpdate(useDev bool) error {
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command(bash, "-lc", updateScript)
|
||||
cmd := exec.CommandContext(context.Background(), bash, "-lc", updateScript)
|
||||
cmd.Env = append(os.Environ(),
|
||||
"XUI_MAIN_FOLDER="+mainFolder,
|
||||
"XUI_SERVICE="+serviceFolder,
|
||||
@@ -199,7 +200,11 @@ func (s *PanelService) startUpdate(useDev bool) error {
|
||||
|
||||
func downloadPanelUpdater() (string, error) {
|
||||
client := (&service.SettingService{}).NewProxiedHTTPClient(15 * time.Second)
|
||||
resp, err := client.Get(panelUpdaterURL)
|
||||
req, reqErr := http.NewRequestWithContext(context.Background(), http.MethodGet, panelUpdaterURL, nil)
|
||||
if reqErr != nil {
|
||||
return "", fmt.Errorf("download panel updater: %w", reqErr)
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("download panel updater: %w", err)
|
||||
}
|
||||
@@ -228,7 +233,7 @@ func downloadPanelUpdater() (string, error) {
|
||||
if n > maxPanelUpdaterBytes {
|
||||
return "", fmt.Errorf("panel updater exceeds %d bytes", maxPanelUpdaterBytes)
|
||||
}
|
||||
if err := file.Chmod(0700); err != nil {
|
||||
if err := file.Chmod(0o700); err != nil {
|
||||
return "", err
|
||||
}
|
||||
ok = true
|
||||
@@ -254,7 +259,11 @@ func fetchPanelRelease(tag string) (*service.Release, error) {
|
||||
url = "https://api.github.com/repos/MHSanaei/3x-ui/releases/tags/" + tag
|
||||
}
|
||||
client := (&service.SettingService{}).NewProxiedHTTPClient(10 * time.Second)
|
||||
resp, err := client.Get(url)
|
||||
req, reqErr := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
|
||||
if reqErr != nil {
|
||||
return nil, reqErr
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -3,14 +3,15 @@ package panel
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/xlzd/gotp"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/logger"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/util/crypto"
|
||||
ldaputil "github.com/mhsanaei/3x-ui/v3/internal/util/ldap"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/web/service"
|
||||
"github.com/xlzd/gotp"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// UserService provides business logic for user management and authentication.
|
||||
@@ -43,7 +44,7 @@ func (s *UserService) CheckUser(username string, password string, twoFactorCode
|
||||
Where("username = ?", username).
|
||||
First(user).
|
||||
Error
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("invalid credentials")
|
||||
} else if err != nil {
|
||||
logger.Warning("check user err:", err)
|
||||
@@ -89,7 +90,6 @@ func (s *UserService) CheckUser(username string, password string, twoFactorCode
|
||||
|
||||
if twoFactorEnable {
|
||||
twoFactorToken, err := s.settingService.GetTwoFactorToken()
|
||||
|
||||
if err != nil {
|
||||
logger.Warning("check two factor token err:", err)
|
||||
return nil, err
|
||||
@@ -114,7 +114,6 @@ func (s *UserService) BumpLoginEpoch() error {
|
||||
func (s *UserService) UpdateUser(id int, username string, password string) error {
|
||||
db := database.GetDB()
|
||||
hashedPassword, err := crypto.HashPasswordAsBcrypt(password)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -125,8 +124,8 @@ func (s *UserService) UpdateUser(id int, username string, password string) error
|
||||
}
|
||||
|
||||
if twoFactorEnable {
|
||||
s.settingService.SetTwoFactorEnable(false)
|
||||
s.settingService.SetTwoFactorToken("")
|
||||
_ = s.settingService.SetTwoFactorEnable(false)
|
||||
_ = s.settingService.SetTwoFactorToken("")
|
||||
}
|
||||
|
||||
return db.Model(model.User{}).
|
||||
|
||||
@@ -65,7 +65,7 @@ func (s *WebSocketService) readPump(client *websocket.Client, conn *ws.Conn) {
|
||||
}()
|
||||
|
||||
conn.SetReadLimit(wsClientReadLimit)
|
||||
conn.SetReadDeadline(time.Now().Add(wsPongWait))
|
||||
_ = conn.SetReadDeadline(time.Now().Add(wsPongWait))
|
||||
conn.SetPongHandler(func(string) error {
|
||||
return conn.SetReadDeadline(time.Now().Add(wsPongWait))
|
||||
})
|
||||
@@ -94,9 +94,9 @@ func (s *WebSocketService) writePump(client *websocket.Client, conn *ws.Conn) {
|
||||
for {
|
||||
select {
|
||||
case msg, ok := <-client.Send:
|
||||
conn.SetWriteDeadline(time.Now().Add(wsWriteWait))
|
||||
_ = conn.SetWriteDeadline(time.Now().Add(wsWriteWait))
|
||||
if !ok {
|
||||
conn.WriteMessage(ws.CloseMessage, []byte{})
|
||||
_ = conn.WriteMessage(ws.CloseMessage, []byte{})
|
||||
return
|
||||
}
|
||||
if err := conn.WriteMessage(ws.TextMessage, msg); err != nil {
|
||||
@@ -105,7 +105,7 @@ func (s *WebSocketService) writePump(client *websocket.Client, conn *ws.Conn) {
|
||||
}
|
||||
|
||||
case <-ticker.C:
|
||||
conn.SetWriteDeadline(time.Now().Add(wsWriteWait))
|
||||
_ = conn.SetWriteDeadline(time.Now().Add(wsWriteWait))
|
||||
if err := conn.WriteMessage(ws.PingMessage, nil); err != nil {
|
||||
logger.Debugf("WebSocket ping error for client %s: %v", client.ID, err)
|
||||
return
|
||||
|
||||
@@ -6,10 +6,11 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/op/go-logging"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
xuilogger "github.com/mhsanaei/3x-ui/v3/internal/logger"
|
||||
"github.com/op/go-logging"
|
||||
)
|
||||
|
||||
// the panel logger is a process-wide singleton. init it once per test
|
||||
@@ -57,9 +58,6 @@ func seedInboundConflictNode(t *testing.T, tag, listen string, port int, protoco
|
||||
}
|
||||
}
|
||||
|
||||
//go:fix inline
|
||||
func intPtr(v int) *int { return new(v) }
|
||||
|
||||
func TestInboundTransports(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
@@ -410,7 +408,7 @@ func TestResolveInboundTag_RespectsCallerTagWhenFree(t *testing.T) {
|
||||
Port: 5000,
|
||||
Protocol: model.VLESS,
|
||||
StreamSettings: `{"network":"tcp"}`,
|
||||
NodeID: intPtr(1),
|
||||
NodeID: new(1),
|
||||
}
|
||||
got, err := svc.resolveInboundTag(pushed, 0)
|
||||
if err != nil {
|
||||
@@ -481,7 +479,7 @@ func TestGenerateInboundTag_NodePrefix(t *testing.T) {
|
||||
Listen: "0.0.0.0",
|
||||
Port: 443,
|
||||
Protocol: model.VLESS,
|
||||
NodeID: intPtr(1),
|
||||
NodeID: new(1),
|
||||
}
|
||||
got, err := svc.generateInboundTag(in, 0)
|
||||
if err != nil {
|
||||
@@ -503,7 +501,7 @@ func TestGenerateInboundTag_NodePrefixedDoesNotCollideWithLocal(t *testing.T) {
|
||||
Listen: "0.0.0.0",
|
||||
Port: 443,
|
||||
Protocol: model.VLESS,
|
||||
NodeID: intPtr(1),
|
||||
NodeID: new(1),
|
||||
}
|
||||
got, err := svc.generateInboundTag(in, 0)
|
||||
if err != nil {
|
||||
@@ -653,7 +651,7 @@ func TestIsAutoGeneratedTag(t *testing.T) {
|
||||
{"canonical", "in-443-tcp", 443, nil, tcp, true},
|
||||
{"canonical udp", "in-443-udp", 443, nil, transportUDP, true},
|
||||
{"dedup suffix", "in-443-tcp-2", 443, nil, tcp, true},
|
||||
{"node prefixed", "n1-in-443-tcp", 443, intPtr(1), tcp, true},
|
||||
{"node prefixed", "n1-in-443-tcp", 443, new(1), tcp, true},
|
||||
{"legacy listen-scoped is now custom", "in-127.0.0.1:443-tcp", 443, nil, tcp, false},
|
||||
{"custom tag", "my-cool-tag", 443, nil, tcp, false},
|
||||
{"stale port", "in-443-tcp", 8443, nil, tcp, false},
|
||||
@@ -708,7 +706,7 @@ func TestCheckPortConflict_ReservedAPIPortAllowedOnNode(t *testing.T) {
|
||||
Port: defaultXrayAPIPort,
|
||||
Protocol: model.VLESS,
|
||||
StreamSettings: `{"network":"tcp"}`,
|
||||
NodeID: intPtr(1),
|
||||
NodeID: new(1),
|
||||
}
|
||||
if got, err := svc.checkPortConflict(candidate, 0); err != nil || got != nil {
|
||||
t.Fatalf("node inbound on the reserved API port must be allowed; got=%v err=%v", got, err)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"archive/zip"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
@@ -233,7 +234,7 @@ func (s *ServerService) isFail2banInstalled() bool {
|
||||
return s.fail2banInstalled
|
||||
}
|
||||
|
||||
err := exec.Command("fail2ban-client", "-h").Run()
|
||||
err := exec.CommandContext(context.Background(), "fail2ban-client", "-h").Run()
|
||||
s.fail2banInstalled = err == nil
|
||||
s.fail2banCheckedAt = time.Now()
|
||||
return s.fail2banInstalled
|
||||
@@ -351,7 +352,11 @@ func getPublicIP(url string) string {
|
||||
Timeout: 3 * time.Second,
|
||||
}
|
||||
|
||||
resp, err := client.Get(url)
|
||||
req, reqErr := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
|
||||
if reqErr != nil {
|
||||
return "N/A"
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "N/A"
|
||||
}
|
||||
@@ -772,7 +777,11 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
|
||||
bufferSize = 8192
|
||||
)
|
||||
|
||||
resp, err := s.settingService.NewProxiedHTTPClient(10 * time.Second).Get(XrayURL)
|
||||
req, reqErr := http.NewRequestWithContext(context.Background(), http.MethodGet, XrayURL, nil)
|
||||
if reqErr != nil {
|
||||
return nil, reqErr
|
||||
}
|
||||
resp, err := s.settingService.NewProxiedHTTPClient(10 * time.Second).Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -872,7 +881,11 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
|
||||
fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)
|
||||
url := fmt.Sprintf("https://github.com/XTLS/Xray-core/releases/download/%s/%s", version, fileName)
|
||||
client := s.settingService.NewProxiedHTTPClient(60 * time.Second)
|
||||
resp, err := client.Get(url)
|
||||
req, reqErr := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
|
||||
if reqErr != nil {
|
||||
return "", reqErr
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -934,7 +947,11 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
|
||||
// fetchXrayDigestSHA256 downloads the .dgst sidecar XTLS publishes next to each
|
||||
// release asset and returns the SHA2-256 hex digest it lists.
|
||||
func (s *ServerService) fetchXrayDigestSHA256(client *http.Client, dgstURL string) (string, error) {
|
||||
resp, err := client.Get(dgstURL)
|
||||
req, reqErr := http.NewRequestWithContext(context.Background(), http.MethodGet, dgstURL, nil)
|
||||
if reqErr != nil {
|
||||
return "", fmt.Errorf("download xray checksum: %w", reqErr)
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("download xray checksum: %w", err)
|
||||
}
|
||||
@@ -1009,7 +1026,7 @@ func (s *ServerService) UpdateXray(version string) error {
|
||||
return err
|
||||
}
|
||||
defer zipFile.Close()
|
||||
if err := os.MkdirAll(filepath.Dir(fileName), 0755); err != nil {
|
||||
if err := os.MkdirAll(filepath.Dir(fileName), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
tmpFile, err := os.CreateTemp(filepath.Dir(fileName), ".xray-*")
|
||||
@@ -1031,7 +1048,7 @@ func (s *ServerService) UpdateXray(version string) error {
|
||||
if n > maxXrayBinaryBytes {
|
||||
return fmt.Errorf("xray binary exceeds %d bytes", maxXrayBinaryBytes)
|
||||
}
|
||||
if err := tmpFile.Chmod(0755); err != nil {
|
||||
if err := tmpFile.Chmod(0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tmpFile.Close(); err != nil {
|
||||
@@ -1099,7 +1116,7 @@ func (s *ServerService) GetLogs(count string, level string, syslog string) []str
|
||||
}
|
||||
|
||||
// Use hardcoded command with validated parameters
|
||||
cmd := exec.Command("journalctl", "-u", "x-ui", "--no-pager", "-n", strconv.Itoa(countInt), "-p", level)
|
||||
cmd := exec.CommandContext(context.Background(), "journalctl", "-u", "x-ui", "--no-pager", "-n", strconv.Itoa(countInt), "-p", level)
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err = cmd.Run()
|
||||
@@ -1121,8 +1138,8 @@ func (s *ServerService) GetXrayLogs(
|
||||
showBlocked string,
|
||||
showProxy string,
|
||||
freedoms []string,
|
||||
blackholes []string) []LogEntry {
|
||||
|
||||
blackholes []string,
|
||||
) []LogEntry {
|
||||
const (
|
||||
Direct = iota
|
||||
Blocked
|
||||
@@ -1149,12 +1166,12 @@ func (s *ServerService) GetXrayLogs(
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
|
||||
if line == "" || strings.Contains(line, "api -> api") {
|
||||
//skipping empty lines and api calls
|
||||
// skipping empty lines and api calls
|
||||
continue
|
||||
}
|
||||
|
||||
if filter != "" && !strings.Contains(line, filter) {
|
||||
//applying filter if it's not empty
|
||||
// applying filter if it's not empty
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1580,7 +1597,7 @@ func (s *ServerService) exportPostgresDB() ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, common.NewErrorf("invalid PostgreSQL DSN: %v", err)
|
||||
}
|
||||
cmd := exec.Command(bin, "--format=custom", "--no-owner", "--no-privileges", "--dbname", dbname)
|
||||
cmd := exec.CommandContext(context.Background(), bin, "--format=custom", "--no-owner", "--no-privileges", "--dbname", dbname)
|
||||
cmd.Env = env
|
||||
var out, stderr bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
@@ -1642,7 +1659,7 @@ func (s *ServerService) importPostgresDB(file multipart.File) error {
|
||||
logger.Warningf("Failed to close existing DB before restore: %v", errClose)
|
||||
}
|
||||
|
||||
cmd := exec.Command(bin,
|
||||
cmd := exec.CommandContext(context.Background(), bin,
|
||||
"--clean", "--if-exists", "--no-owner", "--no-privileges",
|
||||
"--single-transaction", "--dbname", dbname, tempPath,
|
||||
)
|
||||
@@ -1721,7 +1738,7 @@ func (s *ServerService) UpdateGeofile(fileName string) error {
|
||||
|
||||
downloadFile := func(url, destPath string) error {
|
||||
var req *http.Request
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return common.NewErrorf("Failed to create HTTP request for %s: %v", url, err)
|
||||
}
|
||||
@@ -1818,7 +1835,7 @@ func (s *ServerService) UpdateGeofile(fileName string) error {
|
||||
|
||||
func (s *ServerService) GetNewX25519Cert() (any, error) {
|
||||
// Run the command
|
||||
cmd := exec.Command(xray.GetBinaryPath(), "x25519")
|
||||
cmd := exec.CommandContext(context.Background(), xray.GetBinaryPath(), "x25519")
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
@@ -1844,7 +1861,7 @@ func (s *ServerService) GetNewX25519Cert() (any, error) {
|
||||
|
||||
func (s *ServerService) GetNewmldsa65() (any, error) {
|
||||
// Run the command
|
||||
cmd := exec.Command(xray.GetBinaryPath(), "mldsa65")
|
||||
cmd := exec.CommandContext(context.Background(), xray.GetBinaryPath(), "mldsa65")
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
@@ -2048,7 +2065,7 @@ func (s *ServerService) GetRemoteCertHash(server string) ([]string, error) {
|
||||
|
||||
func (s *ServerService) GetNewEchCert(sni string) (any, error) {
|
||||
// Run the command
|
||||
cmd := exec.Command(xray.GetBinaryPath(), "tls", "ech", "--serverName", sni)
|
||||
cmd := exec.CommandContext(context.Background(), xray.GetBinaryPath(), "tls", "ech", "--serverName", sni)
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
@@ -2071,7 +2088,7 @@ func (s *ServerService) GetNewEchCert(sni string) (any, error) {
|
||||
}
|
||||
|
||||
func (s *ServerService) GetNewVlessEnc() (any, error) {
|
||||
cmd := exec.Command(xray.GetBinaryPath(), "vlessenc")
|
||||
cmd := exec.CommandContext(context.Background(), xray.GetBinaryPath(), "vlessenc")
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
if err := cmd.Run(); err != nil {
|
||||
@@ -2166,7 +2183,7 @@ func (s *ServerService) GetNewUUID() (map[string]string, error) {
|
||||
|
||||
func (s *ServerService) GetNewmlkem768() (any, error) {
|
||||
// Run the command
|
||||
cmd := exec.Command(xray.GetBinaryPath(), "mlkem768")
|
||||
cmd := exec.CommandContext(context.Background(), xray.GetBinaryPath(), "mlkem768")
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/config"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
|
||||
|
||||
@@ -283,7 +283,7 @@ func (t *Tgbot) Start(i18nFS embed.FS) error {
|
||||
logger.Warning("Failed to parse admin ID from Telegram bot chat ID:", err)
|
||||
return err
|
||||
}
|
||||
parsedAdminIds = append(parsedAdminIds, int64(id))
|
||||
parsedAdminIds = append(parsedAdminIds, id)
|
||||
}
|
||||
}
|
||||
tgBotMutex.Lock()
|
||||
@@ -472,7 +472,7 @@ func StopBot() {
|
||||
userStateMgr.reset()
|
||||
|
||||
if handler != nil {
|
||||
handler.Stop()
|
||||
_ = handler.Stop()
|
||||
}
|
||||
|
||||
if cancel != nil {
|
||||
|
||||
@@ -74,13 +74,13 @@ func (t *Tgbot) BuildClientDraftMessage() string {
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString("📝 *New client draft*\r\n")
|
||||
b.WriteString(fmt.Sprintf("📧 Email: `%s`\r\n", client_Email))
|
||||
b.WriteString(fmt.Sprintf("🔗 Attached: %s\r\n", attached))
|
||||
b.WriteString(fmt.Sprintf("📊 Traffic: %s\r\n", traffic))
|
||||
b.WriteString(fmt.Sprintf("📅 Expire: %s\r\n", expiry))
|
||||
b.WriteString(fmt.Sprintf("🔢 IP limit: %s\r\n", ipLimit))
|
||||
b.WriteString(fmt.Sprintf("👤 TG user: %s\r\n", tgID))
|
||||
b.WriteString(fmt.Sprintf("💬 Comment: %s\r\n", comment))
|
||||
fmt.Fprintf(&b, "📧 Email: `%s`\r\n", client_Email)
|
||||
fmt.Fprintf(&b, "🔗 Attached: %s\r\n", attached)
|
||||
fmt.Fprintf(&b, "📊 Traffic: %s\r\n", traffic)
|
||||
fmt.Fprintf(&b, "📅 Expire: %s\r\n", expiry)
|
||||
fmt.Fprintf(&b, "🔢 IP limit: %s\r\n", ipLimit)
|
||||
fmt.Fprintf(&b, "👤 TG user: %s\r\n", tgID)
|
||||
fmt.Fprintf(&b, "💬 Comment: %s\r\n", comment)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
@@ -216,7 +216,6 @@ func (t *Tgbot) buildSubscriptionURLs(email string) (string, string, error) {
|
||||
}
|
||||
subJsonURL = fmt.Sprintf("%s%s", subJsonURI, client.SubID)
|
||||
} else {
|
||||
|
||||
subJsonURL = fmt.Sprintf("%s://%s%s%s", scheme, host, subJsonPath, client.SubID)
|
||||
}
|
||||
|
||||
@@ -258,7 +257,7 @@ func (t *Tgbot) sendClientIndividualLinks(chatId int64, email string) {
|
||||
}
|
||||
|
||||
// Try to fetch raw subscription links. Prefer plain text response.
|
||||
req, err := http.NewRequest("GET", subURL, nil)
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, subURL, nil)
|
||||
if err != nil {
|
||||
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
|
||||
return
|
||||
@@ -376,7 +375,7 @@ func (t *Tgbot) sendClientQRLinks(chatId int64, email string) {
|
||||
|
||||
// Also generate a few individual links' QRs (first up to 5)
|
||||
subPageURL := subURL
|
||||
req, err := http.NewRequest("GET", subPageURL, nil)
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, subPageURL, nil)
|
||||
if err == nil {
|
||||
req.Header.Set("Accept", "text/plain, */*;q=0.1")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
|
||||
@@ -31,7 +31,7 @@ func (t *Tgbot) getInboundUsages() string {
|
||||
|
||||
clients, listErr := t.clientService.ListForInbound(nil, inbound.Id)
|
||||
if listErr == nil {
|
||||
info.WriteString(fmt.Sprintf("👥 Clients: %d\r\n", len(clients)))
|
||||
fmt.Fprintf(&info, "👥 Clients: %d\r\n", len(clients))
|
||||
}
|
||||
|
||||
if inbound.ExpiryTime == 0 {
|
||||
@@ -126,11 +126,9 @@ func (t *Tgbot) getInboundClientsFor(inboundID int, action string) (*telego.Inli
|
||||
for _, client := range clients {
|
||||
buttons = append(buttons, tu.InlineKeyboardButton(client.Email).WithCallbackData(t.encodeQuery(action+" "+client.Email)))
|
||||
}
|
||||
|
||||
} else {
|
||||
return nil, errors.New(t.I18nBot("tgbot.answers.getClientsFailed"))
|
||||
}
|
||||
|
||||
}
|
||||
cols := 0
|
||||
if len(buttons) < 6 {
|
||||
@@ -252,11 +250,9 @@ func (t *Tgbot) getInboundClients(id int) (*telego.InlineKeyboardMarkup, error)
|
||||
for _, client := range clients {
|
||||
buttons = append(buttons, tu.InlineKeyboardButton(client.Email).WithCallbackData(t.encodeQuery("client_get_usage "+client.Email)))
|
||||
}
|
||||
|
||||
} else {
|
||||
return nil, errors.New(t.I18nBot("tgbot.answers.getClientsFailed"))
|
||||
}
|
||||
|
||||
}
|
||||
cols := 0
|
||||
if len(buttons) < 6 {
|
||||
|
||||
@@ -49,7 +49,7 @@ func (t *Tgbot) SendBackupToAdmins() {
|
||||
return
|
||||
}
|
||||
for i, adminId := range adminIds {
|
||||
t.sendBackup(int64(adminId))
|
||||
t.sendBackup(adminId)
|
||||
// Add delay between sends to avoid Telegram rate limits
|
||||
if i < len(adminIds)-1 {
|
||||
time.Sleep(1 * time.Second)
|
||||
@@ -63,7 +63,7 @@ func (t *Tgbot) sendExhaustedToAdmins() {
|
||||
return
|
||||
}
|
||||
for _, adminId := range adminIds {
|
||||
t.getExhausted(int64(adminId))
|
||||
t.getExhausted(adminId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -141,7 +141,6 @@ func (t *Tgbot) OnReceive() {
|
||||
userStateMgr.clear(message.Chat.ID)
|
||||
t.addClient(message.Chat.ID, t.BuildClientDraftMessage())
|
||||
}
|
||||
|
||||
} else {
|
||||
if message.UsersShared != nil {
|
||||
if checkAdmin(message.From.ID) {
|
||||
@@ -167,7 +166,7 @@ func (t *Tgbot) OnReceive() {
|
||||
return nil
|
||||
}, th.AnyMessage())
|
||||
|
||||
h.Start()
|
||||
_ = h.Start()
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -205,7 +204,7 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
|
||||
if isAdmin {
|
||||
t.searchClient(chatId, commandArgs[0])
|
||||
} else {
|
||||
t.getClientUsage(chatId, int64(message.From.ID), commandArgs[0])
|
||||
t.getClientUsage(chatId, message.From.ID, commandArgs[0])
|
||||
}
|
||||
} else {
|
||||
msg += t.I18nBot("tgbot.commands.usage")
|
||||
@@ -595,12 +594,12 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||
|
||||
if traffic.ExpiryTime > 0 {
|
||||
if traffic.ExpiryTime-time.Now().Unix()*1000 < 0 {
|
||||
date = -int64(days * 24 * 60 * 60000)
|
||||
date = -(days * 24 * 60 * 60000)
|
||||
} else {
|
||||
date = traffic.ExpiryTime + int64(days*24*60*60000)
|
||||
date = traffic.ExpiryTime + days*24*60*60000
|
||||
}
|
||||
} else {
|
||||
date = traffic.ExpiryTime - int64(days*24*60*60000)
|
||||
date = traffic.ExpiryTime - days*24*60*60000
|
||||
}
|
||||
|
||||
}
|
||||
@@ -685,12 +684,12 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||
var date int64
|
||||
if client_ExpiryTime > 0 {
|
||||
if client_ExpiryTime-time.Now().Unix()*1000 < 0 {
|
||||
date = -int64(days * 24 * 60 * 60000)
|
||||
date = -(days * 24 * 60 * 60000)
|
||||
} else {
|
||||
date = client_ExpiryTime + int64(days*24*60*60000)
|
||||
date = client_ExpiryTime + days*24*60*60000
|
||||
}
|
||||
} else {
|
||||
date = client_ExpiryTime - int64(days*24*60*60000)
|
||||
date = client_ExpiryTime - days*24*60*60000
|
||||
}
|
||||
client_ExpiryTime = date
|
||||
|
||||
@@ -1111,7 +1110,6 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||
}
|
||||
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.chooseInbound"), inbounds)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1458,7 +1456,6 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||
case "get_sorted_traffic_usage_report":
|
||||
t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
|
||||
emails, err := t.inboundService.GetAllEmails()
|
||||
|
||||
if err != nil {
|
||||
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation"), tu.ReplyKeyboardRemove())
|
||||
return
|
||||
|
||||
@@ -143,7 +143,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||
continue
|
||||
}
|
||||
settings := map[string]any{}
|
||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
_ = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
|
||||
dbClients, listErr := s.inboundService.clientService.ListForInbound(nil, inbound.Id)
|
||||
if listErr != nil {
|
||||
@@ -240,7 +240,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||
if len(inbound.StreamSettings) > 0 {
|
||||
// Unmarshal stream JSON
|
||||
var stream map[string]any
|
||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||
_ = json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||
|
||||
// Remove the "settings" field under "tlsSettings" and "realitySettings"
|
||||
tlsSettings, ok1 := stream["tlsSettings"].(map[string]any)
|
||||
@@ -930,7 +930,7 @@ func (s *XrayService) RestartXray(isForce bool) error {
|
||||
logger.Info("Xray config changes applied through the core API, no restart needed")
|
||||
return nil
|
||||
}
|
||||
p.Stop()
|
||||
_ = p.Stop()
|
||||
}
|
||||
|
||||
p = xray.NewProcess(xrayConfig)
|
||||
|
||||
@@ -29,7 +29,7 @@ func (s *XraySettingService) SaveXraySetting(newXraySettings string) error {
|
||||
if hoisted, err := EnsureStatsRouting(newXraySettings); err == nil {
|
||||
newXraySettings = hoisted
|
||||
}
|
||||
return s.SettingService.saveSetting("xrayTemplateConfig", newXraySettings)
|
||||
return s.saveSetting("xrayTemplateConfig", newXraySettings)
|
||||
}
|
||||
|
||||
func (s *XraySettingService) CheckXrayConfig(XrayTemplateConfig string) error {
|
||||
|
||||
Reference in New Issue
Block a user