feat(config): enhance NodeConfig with TrafficReportThreshold, IPStrategy, DNS, Block, and Outbound fields

This commit is contained in:
Chang lue Tsen 2025-09-28 19:05:54 +09:00
parent 64f0bcc55a
commit 0d1df5f905
14 changed files with 395 additions and 115 deletions

View File

@ -59,9 +59,9 @@ type Proxy struct {
Multiplex string Multiplex string
// Obfs // Obfs
Obfs string // obfs, 'none', 'http', 'tls' //Obfs string // obfs, 'none', 'http', 'tls'
ObfsHost string // obfs host //ObfsHost string // obfs host
ObfsPath string // obfs path //ObfsPath string // obfs path
// Vless // Vless
XhttpMode string // xhttp mode XhttpMode string // xhttp mode
@ -76,6 +76,11 @@ type Proxy struct {
EncryptionPrivateKey string // encryption private key EncryptionPrivateKey string // encryption private key
EncryptionClientPadding string // encryption client padding EncryptionClientPadding string // encryption client padding
EncryptionPassword string // encryption password EncryptionPassword string // encryption password
Ratio float64 // Traffic ratio, default is 1
CertMode string // Certificate mode, `none``http``dns``self`
CertDNSProvider string // DNS provider for certificate
CertDNSEnv string // Environment for DNS provider
} }
type User struct { type User struct {

View File

@ -95,10 +95,17 @@ type (
} }
QueryServerConfigRequest { QueryServerConfigRequest {
ServerID int64 `path:"server_id"` ServerID int64 `path:"server_id"`
SecretKey string `header:"secret_key"` SecretKey string `form:"secret_key"`
Protocols []string `form:"protocols,omitempty"`
} }
QueryServerConfigResponse { QueryServerConfigResponse {
TrafficReportThreshold int64 `json:"traffic_report_threshold"`
IPStrategy string `json:"ip_strategy"`
DNS []NodeDNS `json:"dns"`
Block []string `json:"block"`
Outbound []NodeOutbound `json:"outbound"`
Protocols []Protocol `json:"protocols"` Protocols []Protocol `json:"protocols"`
Total int64 `json:"total"`
} }
) )

View File

@ -155,6 +155,24 @@ type (
NodeSecret string `json:"node_secret"` NodeSecret string `json:"node_secret"`
NodePullInterval int64 `json:"node_pull_interval"` NodePullInterval int64 `json:"node_pull_interval"`
NodePushInterval int64 `json:"node_push_interval"` NodePushInterval int64 `json:"node_push_interval"`
TrafficReportThreshold int64 `json:"traffic_report_threshold"`
IPStrategy string `json:"ip_strategy"`
DNS []NodeDNS `json:"dns"`
Block []string `json:"block"`
Outbound []NodeOutbound `json:"outbound"`
}
NodeDNS {
Proto string `json:"proto"`
Address string `json:"address"`
Domains []string `json:"domains"`
}
NodeOutbound {
Name string `json:"name"`
Protocol string `json:"protocol"`
Address string `json:"address"`
Port int64 `json:"port"`
Password string `json:"password"`
Rules []string `json:"rules"`
} }
InviteConfig { InviteConfig {
ForcedInvite bool `json:"forced_invite"` ForcedInvite bool `json:"forced_invite"`
@ -813,6 +831,10 @@ type (
EncryptionPrivateKey string `json:"encryption_private_key,omitempty"` // encryption private key EncryptionPrivateKey string `json:"encryption_private_key,omitempty"` // encryption private key
EncryptionClientPadding string `json:"encryption_client_padding,omitempty"` // encryption client padding EncryptionClientPadding string `json:"encryption_client_padding,omitempty"` // encryption client padding
EncryptionPassword string `json:"encryption_password,omitempty"` // encryption password EncryptionPassword string `json:"encryption_password,omitempty"` // encryption password
Ratio float64 `json:"ratio,omitempty"` // Traffic ratio, default is 1
CertMode string `json:"cert_mode,omitempty"` // Certificate mode, `none``http``dns``self`
CertDNSProvider string `json:"cert_dns_provider,omitempty"` // DNS provider for certificate
CertDNSEnv string `json:"cert_dns_env,omitempty"` // Environment for DNS provider
} }
) )

View File

@ -0,0 +1,8 @@
INSERT
IGNORE INTO `system` (`category`, `key`, `value`, `type`, `desc`, `created_at`, `updated_at`)
VALUE
('server', 'TrafficReportThreshold', '0', 'int', 'Traffic report threshold', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637'),
('server', 'IPStrategy', '', 'string', 'IP Strategy', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637'),
('server', 'DNS', '', 'string', 'DNS', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637'),
('server', 'Block', '', 'string', 'Block', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637'),
('server', 'Outbound', '', 'string', 'Proxy Outbound', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637');

View File

@ -3,7 +3,6 @@ package initialize
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"github.com/perfect-panel/server/pkg/logger" "github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/internal/config" "github.com/perfect-panel/server/internal/config"
@ -19,9 +18,40 @@ func Node(ctx *svc.ServiceContext) {
if err != nil { if err != nil {
panic(err) panic(err)
} }
var nodeConfig config.NodeConfig var nodeConfig config.NodeDBConfig
tool.SystemConfigSliceReflectToStruct(configs, &nodeConfig) tool.SystemConfigSliceReflectToStruct(configs, &nodeConfig)
ctx.Config.Node = nodeConfig c := config.NodeConfig{
NodeSecret: nodeConfig.NodeSecret,
NodePullInterval: nodeConfig.NodePullInterval,
NodePushInterval: nodeConfig.NodePushInterval,
IPStrategy: nodeConfig.IPStrategy,
TrafficReportThreshold: nodeConfig.TrafficReportThreshold,
}
if nodeConfig.DNS != "" {
var dns []config.NodeDNS
err = json.Unmarshal([]byte(nodeConfig.DNS), &dns)
if err != nil {
logger.Errorf("[Node] Unmarshal DNS config error: %s", err.Error())
panic(err)
}
c.DNS = dns
}
if nodeConfig.Block != "" {
var block []string
_ = json.Unmarshal([]byte(nodeConfig.Block), &block)
c.Block = tool.RemoveDuplicateElements(block...)
}
if nodeConfig.Outbound != "" {
var outbound []config.NodeOutbound
err = json.Unmarshal([]byte(nodeConfig.Outbound), &outbound)
if err != nil {
logger.Errorf("[Node] Unmarshal Outbound config error: %s", err.Error())
panic(err)
}
c.Outbound = outbound
}
ctx.Config.Node = c
// Manager initialization // Manager initialization
if ctx.DB.Model(&system.System{}).Where("`key` = ?", "NodeMultiplierConfig").Find(&system.System{}).RowsAffected == 0 { if ctx.DB.Model(&system.System{}).Where("`key` = ?", "NodeMultiplierConfig").Find(&system.System{}).RowsAffected == 0 {

View File

@ -1,6 +1,7 @@
package config package config
import ( import (
"encoding/json"
"github.com/perfect-panel/server/pkg/logger" "github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/orm" "github.com/perfect-panel/server/pkg/orm"
) )
@ -108,6 +109,73 @@ type NodeConfig struct {
NodeSecret string `yaml:"NodeSecret" default:""` NodeSecret string `yaml:"NodeSecret" default:""`
NodePullInterval int64 `yaml:"NodePullInterval" default:"60"` NodePullInterval int64 `yaml:"NodePullInterval" default:"60"`
NodePushInterval int64 `yaml:"NodePushInterval" default:"60"` NodePushInterval int64 `yaml:"NodePushInterval" default:"60"`
TrafficReportThreshold int64 `yaml:"TrafficReportThreshold" default:"0"`
IPStrategy string `yaml:"IPStrategy" default:""`
DNS []NodeDNS `yaml:"DNS"`
Block []string `yaml:"Block" `
Outbound []NodeOutbound `yaml:"Outbound"`
}
func (n *NodeConfig) Marshal() ([]byte, error) {
type Alias NodeConfig
return json.Marshal(&struct {
*Alias
}{
Alias: (*Alias)(n),
})
}
func (n *NodeConfig) Unmarshal(data []byte) error {
type Alias NodeConfig
aux := &struct {
*Alias
}{
Alias: (*Alias)(n),
}
return json.Unmarshal(data, &aux)
}
type NodeDNS struct {
Proto string `json:"proto"`
Address string `json:"address"`
Domains []string `json:"domains"`
}
func (n *NodeDNS) Marshal() ([]byte, error) {
type Alias NodeDNS
return json.Marshal(&struct {
*Alias
}{
Alias: (*Alias)(n),
})
}
func (n *NodeDNS) Unmarshal(data []byte) error {
type Alias NodeDNS
aux := &struct {
*Alias
}{
Alias: (*Alias)(n),
}
return json.Unmarshal(data, &aux)
}
type NodeOutbound struct {
Name string `json:"name"`
Protocol string `json:"protocol"`
Address string `json:"address"`
Port int64 `json:"port"`
Password string `json:"password"`
Rules []string `json:"rules"`
}
func (n *NodeOutbound) Marshal() ([]byte, error) {
type Alias NodeOutbound
return json.Marshal(&struct {
*Alias
}{
Alias: (*Alias)(n),
})
} }
type File struct { type File struct {
@ -152,3 +220,14 @@ type Log struct {
AutoClear bool `yaml:"AutoClear" default:"true"` AutoClear bool `yaml:"AutoClear" default:"true"`
ClearDays int64 `yaml:"ClearDays" default:"7"` ClearDays int64 `yaml:"ClearDays" default:"7"`
} }
type NodeDBConfig struct {
NodeSecret string
NodePullInterval int64
NodePushInterval int64
TrafficReportThreshold int64
IPStrategy string
DNS string
Block string
Outbound string
}

View File

@ -1,6 +1,7 @@
package server package server
import ( import (
"fmt"
"net/http" "net/http"
"strconv" "strconv"
@ -26,14 +27,15 @@ func QueryServerProtocolConfigHandler(svcCtx *svc.ServiceContext) func(c *gin.Co
} }
req.ServerID = serverID req.ServerID = serverID
key := c.GetHeader("secret_key") if err = c.ShouldBindQuery(&req); err != nil {
if key == "" || key != svcCtx.Config.Node.NodeSecret { logger.Debugf("[QueryServerProtocolConfigHandler] - ShouldBindQuery error: %v, Query: %v", err, c.Request.URL.Query())
logger.Debugf("[QueryServerProtocolConfigHandler] - secret_key error: %s", key) c.String(http.StatusBadRequest, "Invalid Params")
c.String(http.StatusUnauthorized, "Unauthorized")
c.Abort() c.Abort()
return return
} }
fmt.Printf("[QueryServerProtocolConfigHandler] - ShouldBindQuery request: %+v\n", req)
l := server.NewQueryServerProtocolConfigLogic(c.Request.Context(), svcCtx) l := server.NewQueryServerProtocolConfigLogic(c.Request.Context(), svcCtx)
resp, err := l.QueryServerProtocolConfig(&req) resp, err := l.QueryServerProtocolConfig(&req)
result.HttpResult(c, resp, err) result.HttpResult(c, resp, err)

View File

@ -2,7 +2,8 @@ package system
import ( import (
"context" "context"
"encoding/json"
"github.com/perfect-panel/server/internal/config"
"github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types" "github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger" "github.com/perfect-panel/server/pkg/logger"
@ -26,15 +27,45 @@ func NewGetNodeConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Get
} }
func (l *GetNodeConfigLogic) GetNodeConfig() (*types.NodeConfig, error) { func (l *GetNodeConfigLogic) GetNodeConfig() (*types.NodeConfig, error) {
resp := &types.NodeConfig{}
// get server config from db // get server config from db
configs, err := l.svcCtx.SystemModel.GetNodeConfig(l.ctx) configs, err := l.svcCtx.SystemModel.GetNodeConfig(l.ctx)
if err != nil { if err != nil {
l.Errorw("[GetNodeConfigLogic] GetNodeConfig get server config error: ", logger.Field("error", err.Error())) l.Errorw("[GetNodeConfigLogic] GetNodeConfig get server config error: ", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "GetNodeConfig get server config error: %v", err.Error()) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "GetNodeConfig get server config error: %v", err.Error())
} }
// reflect to response var dbConfig config.NodeDBConfig
tool.SystemConfigSliceReflectToStruct(configs, resp) tool.SystemConfigSliceReflectToStruct(configs, &dbConfig)
return resp, nil c := &types.NodeConfig{
NodeSecret: dbConfig.NodeSecret,
NodePullInterval: dbConfig.NodePullInterval,
NodePushInterval: dbConfig.NodePushInterval,
IPStrategy: dbConfig.IPStrategy,
TrafficReportThreshold: dbConfig.TrafficReportThreshold,
}
if dbConfig.DNS != "" {
var dns []types.NodeDNS
err = json.Unmarshal([]byte(dbConfig.DNS), &dns)
if err != nil {
logger.Errorf("[Node] Unmarshal DNS config error: %s", err.Error())
panic(err)
}
c.DNS = dns
}
if dbConfig.Block != "" {
var block []string
_ = json.Unmarshal([]byte(dbConfig.Block), &block)
c.Block = tool.RemoveDuplicateElements(block...)
}
if dbConfig.Outbound != "" {
var outbound []types.NodeOutbound
err = json.Unmarshal([]byte(dbConfig.Outbound), &outbound)
if err != nil {
logger.Errorf("[Node] Unmarshal Outbound config error: %s", err.Error())
panic(err)
}
c.Outbound = outbound
}
return c, nil
} }

View File

@ -41,7 +41,9 @@ func (l *UpdateNodeConfigLogic) UpdateNodeConfig(req *types.NodeConfig) error {
// Get the field name // Get the field name
fieldName := t.Field(i).Name fieldName := t.Field(i).Name
// Get the field value to string // Get the field value to string
fieldValue := tool.ConvertValueToString(v.Field(i)) var fieldValue string
fieldValue = tool.ConvertValueToString(v.Field(i))
// Update the server config // Update the server config
err = db.Model(&system.System{}).Where("`category` = 'server' and `key` = ?", fieldName).Update("value", fieldValue).Error err = db.Model(&system.System{}).Where("`category` = 'server' and `key` = ?", fieldName).Update("value", fieldValue).Error
if err != nil { if err != nil {

View File

@ -41,7 +41,53 @@ func (l *QueryServerProtocolConfigLogic) QueryServerProtocolConfig(req *types.Qu
} }
tool.DeepCopy(&protocols, dst) tool.DeepCopy(&protocols, dst)
// filter by req.Protocols
if len(req.Protocols) > 0 {
var filtered []types.Protocol
protocolSet := make(map[string]struct{})
for _, p := range req.Protocols {
protocolSet[p] = struct{}{}
}
for _, p := range protocols {
if _, exists := protocolSet[p.Type]; exists {
filtered = append(filtered, p)
}
}
protocols = filtered
}
var dns []types.NodeDNS
if len(l.svcCtx.Config.Node.DNS) > 0 {
for _, d := range l.svcCtx.Config.Node.DNS {
dns = append(dns, types.NodeDNS{
Proto: d.Proto,
Address: d.Address,
Domains: d.Domains,
})
}
}
var outbound []types.NodeOutbound
if len(l.svcCtx.Config.Node.Outbound) > 0 {
for _, o := range l.svcCtx.Config.Node.Outbound {
outbound = append(outbound, types.NodeOutbound{
Name: o.Name,
Protocol: o.Protocol,
Address: o.Address,
Port: o.Port,
Password: o.Password,
Rules: o.Rules,
})
}
}
return &types.QueryServerConfigResponse{ return &types.QueryServerConfigResponse{
TrafficReportThreshold: l.svcCtx.Config.Node.TrafficReportThreshold,
IPStrategy: l.svcCtx.Config.Node.IPStrategy,
DNS: dns,
Block: l.svcCtx.Config.Node.Block,
Outbound: outbound,
Protocols: protocols, Protocols: protocols,
Total: int64(len(protocols)),
}, nil }, nil
} }

View File

@ -144,6 +144,11 @@ type Protocol struct {
EncryptionPrivateKey string `json:"encryption_private_key,omitempty"` // encryption private key EncryptionPrivateKey string `json:"encryption_private_key,omitempty"` // encryption private key
EncryptionClientPadding string `json:"encryption_client_padding,omitempty"` // encryption client padding EncryptionClientPadding string `json:"encryption_client_padding,omitempty"` // encryption client padding
EncryptionPassword string `json:"encryption_password,omitempty"` // encryption password EncryptionPassword string `json:"encryption_password,omitempty"` // encryption password
Ratio float64 `json:"ratio,omitempty"` // Traffic ratio, default is 1
CertMode string `json:"cert_mode,omitempty"` // Certificate mode, `none``http``dns``self`
CertDNSProvider string `json:"cert_dns_provider,omitempty"` // DNS provider for certificate
CertDNSEnv string `json:"cert_dns_env"` // Environment for DNS provider
} }
// Marshal protocol to json // Marshal protocol to json

View File

@ -1220,6 +1220,26 @@ type NodeConfig struct {
NodeSecret string `json:"node_secret"` NodeSecret string `json:"node_secret"`
NodePullInterval int64 `json:"node_pull_interval"` NodePullInterval int64 `json:"node_pull_interval"`
NodePushInterval int64 `json:"node_push_interval"` NodePushInterval int64 `json:"node_push_interval"`
TrafficReportThreshold int64 `json:"traffic_report_threshold"`
IPStrategy string `json:"ip_strategy"`
DNS []NodeDNS `json:"dns"`
Block []string `json:"block"`
Outbound []NodeOutbound `json:"outbound"`
}
type NodeDNS struct {
Proto string `json:"proto"`
Address string `json:"address"`
Domains []string `json:"domains"`
}
type NodeOutbound struct {
Name string `json:"name"`
Protocol string `json:"protocol"`
Address string `json:"address"`
Port int64 `json:"port"`
Password string `json:"password"`
Rules []string `json:"rules"`
} }
type NodeRelay struct { type NodeRelay struct {
@ -1425,6 +1445,7 @@ type PrivacyPolicyConfig struct {
type Protocol struct { type Protocol struct {
Type string `json:"type"` Type string `json:"type"`
Port uint16 `json:"port"` Port uint16 `json:"port"`
Enable bool `json:"enable"`
Security string `json:"security,omitempty"` Security string `json:"security,omitempty"`
SNI string `json:"sni,omitempty"` SNI string `json:"sni,omitempty"`
AllowInsecure bool `json:"allow_insecure,omitempty"` AllowInsecure bool `json:"allow_insecure,omitempty"`
@ -1465,6 +1486,10 @@ type Protocol struct {
EncryptionPrivateKey string `json:"encryption_private_key,omitempty"` // encryption private key EncryptionPrivateKey string `json:"encryption_private_key,omitempty"` // encryption private key
EncryptionClientPadding string `json:"encryption_client_padding,omitempty"` // encryption client padding EncryptionClientPadding string `json:"encryption_client_padding,omitempty"` // encryption client padding
EncryptionPassword string `json:"encryption_password,omitempty"` // encryption password EncryptionPassword string `json:"encryption_password,omitempty"` // encryption password
Ratio float64 `json:"ratio,omitempty"` // Traffic ratio, default is 1
CertMode string `json:"cert_mode,omitempty"` // Certificate mode, `none``http``dns``self`
CertDNSProvider string `json:"cert_dns_provider,omitempty"` // DNS provider for certificate
CertDNSEnv string `json:"cert_dns_env,omitempty"` // Environment for DNS provider
} }
type PubilcRegisterConfig struct { type PubilcRegisterConfig struct {
@ -1585,11 +1610,18 @@ type QueryQuotaTaskStatusResponse struct {
type QueryServerConfigRequest struct { type QueryServerConfigRequest struct {
ServerID int64 `path:"server_id"` ServerID int64 `path:"server_id"`
SecretKey string `header:"secret_key"` SecretKey string `form:"secret_key"`
Protocols []string `form:"protocols,omitempty"`
} }
type QueryServerConfigResponse struct { type QueryServerConfigResponse struct {
TrafficReportThreshold int64 `json:"traffic_report_threshold"`
IPStrategy string `json:"ip_strategy"`
DNS []NodeDNS `json:"dns"`
Block []string `json:"block"`
Outbound []NodeOutbound `json:"outbound"`
Protocols []Protocol `json:"protocols"` Protocols []Protocol `json:"protocols"`
Total int64 `json:"total"`
} }
type QuerySubscribeGroupListResponse struct { type QuerySubscribeGroupListResponse struct {

View File

@ -1,6 +1,7 @@
package tool package tool
import ( import (
"encoding/json"
"fmt" "fmt"
"reflect" "reflect"
"strconv" "strconv"
@ -26,6 +27,16 @@ func ConvertValueToString(value reflect.Value) string {
default: default:
return "" return ""
} }
case reflect.Struct, reflect.Map, reflect.Slice, reflect.Array:
bytes, err := json.Marshal(value.Interface())
if err != nil {
fmt.Println("Error marshaling struct:", err.Error())
return ""
}
if string(bytes) == "null" {
return ""
}
return string(bytes)
default: default:
return "" return ""
} }