153 lines
5.3 KiB
Go
153 lines
5.3 KiB
Go
package config
|
|
|
|
import "fmt"
|
|
|
|
// StandaloneConfig describes a locally-managed node that runs without panel
|
|
// handshake, polling, reporting, or WebSocket sync.
|
|
type StandaloneConfig struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
Node StandaloneNodeConfig `yaml:"node"`
|
|
Users []StandaloneUser `yaml:"users"`
|
|
}
|
|
|
|
// StandaloneNodeConfig captures the subset of panel node config that is needed
|
|
// to generate a working local inbound.
|
|
type StandaloneNodeConfig struct {
|
|
Protocol string `yaml:"protocol"`
|
|
ListenIP string `yaml:"listen_ip,omitempty"`
|
|
|
|
ServerPort int `yaml:"server_port"`
|
|
Network string `yaml:"network,omitempty"`
|
|
|
|
NetworkSettings map[string]any `yaml:"network_settings,omitempty"`
|
|
Routes []StandaloneRouteRule `yaml:"routes,omitempty"`
|
|
CustomRouteRules []StandaloneCustomRouteRule `yaml:"custom_route_rules,omitempty"`
|
|
|
|
Cipher string `yaml:"cipher,omitempty"`
|
|
Plugin string `yaml:"plugin,omitempty"`
|
|
PluginOpt string `yaml:"plugin_opts,omitempty"`
|
|
ServerKey string `yaml:"server_key,omitempty"`
|
|
|
|
TLS int `yaml:"tls,omitempty"`
|
|
Flow string `yaml:"flow,omitempty"`
|
|
Decryption string `yaml:"decryption,omitempty"`
|
|
TLSSettings map[string]any `yaml:"tls_settings,omitempty"`
|
|
|
|
Host string `yaml:"host,omitempty"`
|
|
ServerName string `yaml:"server_name,omitempty"`
|
|
|
|
Version int `yaml:"version,omitempty"`
|
|
UpMbps int `yaml:"up_mbps,omitempty"`
|
|
DownMbps int `yaml:"down_mbps,omitempty"`
|
|
Obfs string `yaml:"obfs,omitempty"`
|
|
ObfsPassword string `yaml:"obfs_password,omitempty"`
|
|
|
|
CongestionControl string `yaml:"congestion_control,omitempty"`
|
|
PaddingScheme string `yaml:"padding_scheme,omitempty"`
|
|
Transport string `yaml:"transport,omitempty"`
|
|
TrafficPattern string `yaml:"traffic_pattern,omitempty"`
|
|
|
|
Multiplex *StandaloneMultiplexConfig `yaml:"multiplex,omitempty"`
|
|
AcceptProxyProtocol bool `yaml:"accept_proxy_protocol,omitempty"`
|
|
}
|
|
|
|
type StandaloneRouteRule struct {
|
|
ID int `yaml:"id,omitempty"`
|
|
Match []string `yaml:"match,omitempty"`
|
|
Action string `yaml:"action,omitempty"`
|
|
ActionValue string `yaml:"action_value,omitempty"`
|
|
}
|
|
|
|
type StandaloneCustomRouteRule struct {
|
|
Name string `yaml:"name,omitempty"`
|
|
Disabled bool `yaml:"disabled,omitempty"`
|
|
Match StandaloneRouteMatch `yaml:"match,omitempty"`
|
|
Action StandaloneRouteAction `yaml:"action,omitempty"`
|
|
}
|
|
|
|
type StandaloneRouteMatch struct {
|
|
Domains []string `yaml:"domains,omitempty"`
|
|
DomainSuffixes []string `yaml:"domain_suffixes,omitempty"`
|
|
IPCIDRs []string `yaml:"ip_cidrs,omitempty"`
|
|
Ports []string `yaml:"ports,omitempty"`
|
|
Networks []string `yaml:"networks,omitempty"`
|
|
SourceCIDRs []string `yaml:"source_cidrs,omitempty"`
|
|
SourcePorts []string `yaml:"source_ports,omitempty"`
|
|
}
|
|
|
|
type StandaloneRouteAction struct {
|
|
Type string `yaml:"type,omitempty"`
|
|
Target string `yaml:"target,omitempty"`
|
|
}
|
|
|
|
type StandaloneMultiplexConfig struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
Protocol string `yaml:"protocol,omitempty"`
|
|
MaxConnections int `yaml:"max_connections,omitempty"`
|
|
MinStreams int `yaml:"min_streams,omitempty"`
|
|
MaxStreams int `yaml:"max_streams,omitempty"`
|
|
Padding bool `yaml:"padding,omitempty"`
|
|
Brutal *StandaloneBrutalConfig `yaml:"brutal,omitempty"`
|
|
}
|
|
|
|
type StandaloneBrutalConfig struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
UpMbps int `yaml:"up_mbps,omitempty"`
|
|
DownMbps int `yaml:"down_mbps,omitempty"`
|
|
}
|
|
|
|
type StandaloneUser struct {
|
|
ID int `yaml:"id"`
|
|
UUID string `yaml:"uuid"`
|
|
SpeedLimit int `yaml:"speed_limit,omitempty"`
|
|
DeviceLimit int `yaml:"device_limit,omitempty"`
|
|
}
|
|
|
|
func (c *Config) IsStandalone() bool {
|
|
return c.Standalone != nil && c.Standalone.Enabled
|
|
}
|
|
|
|
func (c *Config) validateStandalone() error {
|
|
if c.Standalone == nil {
|
|
return fmt.Errorf("standalone config is required when standalone mode is enabled")
|
|
}
|
|
if len(c.Nodes) > 0 {
|
|
return fmt.Errorf("standalone mode does not support top-level 'nodes'")
|
|
}
|
|
|
|
node := c.Standalone.Node
|
|
if node.Protocol == "" {
|
|
return fmt.Errorf("standalone.node.protocol is required")
|
|
}
|
|
if node.ServerPort <= 0 || node.ServerPort > 65535 {
|
|
return fmt.Errorf("standalone.node.server_port must be between 1 and 65535")
|
|
}
|
|
if node.TLS < 0 || node.TLS > 2 {
|
|
return fmt.Errorf("standalone.node.tls must be 0 (off), 1 (tls), or 2 (reality)")
|
|
}
|
|
if len(c.Standalone.Users) == 0 {
|
|
return fmt.Errorf("standalone.users must not be empty")
|
|
}
|
|
|
|
ids := make(map[int]struct{}, len(c.Standalone.Users))
|
|
uuids := make(map[string]struct{}, len(c.Standalone.Users))
|
|
for i, user := range c.Standalone.Users {
|
|
if user.ID <= 0 {
|
|
return fmt.Errorf("standalone.users[%d].id must be positive", i)
|
|
}
|
|
if user.UUID == "" {
|
|
return fmt.Errorf("standalone.users[%d].uuid is required", i)
|
|
}
|
|
if _, exists := ids[user.ID]; exists {
|
|
return fmt.Errorf("standalone.users[%d].id duplicates %d", i, user.ID)
|
|
}
|
|
if _, exists := uuids[user.UUID]; exists {
|
|
return fmt.Errorf("standalone.users[%d].uuid duplicates %q", i, user.UUID)
|
|
}
|
|
ids[user.ID] = struct{}{}
|
|
uuids[user.UUID] = struct{}{}
|
|
}
|
|
|
|
return nil
|
|
}
|