feat(proxy): enhance proxy and group handling with new configuration options
This commit is contained in:
parent
82e447c55e
commit
224365ce79
8
go.mod
8
go.mod
@ -56,6 +56,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Masterminds/sprig/v3 v3.3.0
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/goccy/go-json v0.10.4
|
||||
github.com/golang-migrate/migrate/v4 v4.18.2
|
||||
@ -66,7 +67,10 @@ require (
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.3.0 // indirect
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
|
||||
github.com/alibabacloud-go/debug v1.0.1 // indirect
|
||||
github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect
|
||||
@ -99,6 +103,7 @@ require (
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
@ -107,12 +112,15 @@ require (
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/openzipkin/zipkin-go v0.4.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/smartwalle/ncrypto v1.0.4 // indirect
|
||||
github.com/smartwalle/ngx v1.0.9 // indirect
|
||||
github.com/smartwalle/nsign v1.0.9 // indirect
|
||||
|
||||
16
go.sum
16
go.sum
@ -1,6 +1,8 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
|
||||
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||
@ -8,6 +10,12 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg6
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/GUAIK-ORG/go-snowflake v0.0.0-20200116064823-220c4260e85f h1:RDkg3pyE1qGbBpRWmvSN9RNZC5nUrOaEPiEpEb8y2f0=
|
||||
github.com/GUAIK-ORG/go-snowflake v0.0.0-20200116064823-220c4260e85f/go.mod h1:zA7AF9RTfpluCfz0omI4t5KCMaWHUMicsZoMccnaT44=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
|
||||
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
|
||||
@ -208,6 +216,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hibiken/asynq v0.24.1 h1:+5iIEAyA9K/lcSPvx3qoPtsKJeKI5u9aOIvUmSsazEw=
|
||||
github.com/hibiken/asynq v0.24.1/go.mod h1:u5qVeSbrnfT+vtG5Mq8ZPzQu/BmCKMHvTGb91uy9Tts=
|
||||
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||
@ -249,6 +259,10 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
||||
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
@ -287,6 +301,8 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/smartwalle/alipay/v3 v3.2.23 h1:i1VwJeu70EmwpsXXz6GZZnMAtRx5MTfn2dPoql/L3zE=
|
||||
github.com/smartwalle/alipay/v3 v3.2.23/go.mod h1:lVqFiupPf8YsAXaq5JXcwqnOUC2MCF+2/5vub+RlagE=
|
||||
github.com/smartwalle/ncrypto v1.0.4 h1:P2rqQxDepJwgeO5ShoC+wGcK2wNJDmcdBOWAksuIgx8=
|
||||
|
||||
@ -9,11 +9,12 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
RelayModeNone = "none"
|
||||
RelayModeAll = "all"
|
||||
RelayModeRandom = "random"
|
||||
RuleGroupTypeBan = "ban"
|
||||
RuleGroupTypeAuto = "auto"
|
||||
RelayModeNone = "none"
|
||||
RelayModeAll = "all"
|
||||
RelayModeRandom = "random"
|
||||
RuleGroupTypeReject = "reject"
|
||||
RuleGroupTypeDefault = "default"
|
||||
RuleGroupTypeDirect = "direct"
|
||||
)
|
||||
|
||||
type ServerFilter struct {
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"embed"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/server"
|
||||
"github.com/perfect-panel/server/pkg/adapter/clash"
|
||||
"github.com/perfect-panel/server/pkg/adapter/general"
|
||||
@ -13,9 +15,11 @@ import (
|
||||
"github.com/perfect-panel/server/pkg/adapter/v2rayn"
|
||||
)
|
||||
|
||||
//go:embed template/*
|
||||
var TemplateFS embed.FS
|
||||
|
||||
var (
|
||||
AutoSelect = "Auto - UrlTest"
|
||||
Selection = "Selection"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
@ -30,69 +34,63 @@ type Adapter struct {
|
||||
|
||||
func NewAdapter(cfg *Config) *Adapter {
|
||||
// 转换服务器列表
|
||||
proxies := adapterProxies(cfg.Nodes)
|
||||
defaultGroup := FindDefaultGroup(cfg.Rules)
|
||||
proxies, nodes, tags := adapterProxies(cfg.Nodes)
|
||||
// 转换规则组
|
||||
g, r := adapterRules(cfg.Rules)
|
||||
|
||||
// 生成代理组
|
||||
proxyGroup, nodes := generateProxyGroup(proxies)
|
||||
|
||||
// 加入兜底节点
|
||||
for i, group := range g {
|
||||
if group.Direct {
|
||||
continue
|
||||
}
|
||||
if len(group.Proxies) == 0 {
|
||||
p := append([]string{AutoSelect, Selection}, nodes...)
|
||||
g[i].Proxies = append(p, "DIRECT")
|
||||
}
|
||||
g, r, d := adapterRules(cfg.Rules)
|
||||
if d == "" {
|
||||
d = AutoSelect
|
||||
}
|
||||
|
||||
// 生成默认代理组
|
||||
proxyGroup := append(generateDefaultGroup(), g...)
|
||||
// 合并代理组
|
||||
proxyGroup = RemoveEmptyGroup(append(proxyGroup, g...))
|
||||
// 处理标签
|
||||
proxyGroup = adapterTags(cfg.Tags, proxyGroup)
|
||||
|
||||
proxyGroup = SortGroups(proxyGroup, nodes, tags, d)
|
||||
return &Adapter{
|
||||
Adapter: proxy.Adapter{
|
||||
Proxies: proxies,
|
||||
Group: SortGroups(proxyGroup, defaultGroup),
|
||||
Rules: r,
|
||||
Nodes: nodes,
|
||||
Default: defaultGroup,
|
||||
Proxies: proxies,
|
||||
Group: proxyGroup,
|
||||
Rules: r,
|
||||
Nodes: nodes,
|
||||
Default: d,
|
||||
TemplateFS: &TemplateFS,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// BuildClash generates a Clash configuration for the given UUID.
|
||||
func (m *Adapter) BuildClash(uuid string) ([]byte, error) {
|
||||
client := clash.NewClash(m.Adapter)
|
||||
return client.Build(uuid)
|
||||
}
|
||||
|
||||
// BuildGeneral generates a general configuration for the given UUID.
|
||||
func (m *Adapter) BuildGeneral(uuid string) []byte {
|
||||
return general.GenerateBase64General(m.Proxies, uuid)
|
||||
}
|
||||
|
||||
// BuildLoon generates a Loon configuration for the given UUID.
|
||||
func (m *Adapter) BuildLoon(uuid string) []byte {
|
||||
return loon.BuildLoon(m.Proxies, uuid)
|
||||
}
|
||||
|
||||
// BuildQuantumultX generates a Quantumult X configuration for the given UUID.
|
||||
func (m *Adapter) BuildQuantumultX(uuid string) string {
|
||||
return quantumultx.BuildQuantumultX(m.Proxies, uuid)
|
||||
}
|
||||
|
||||
// BuildSingbox generates a Singbox configuration for the given UUID.
|
||||
func (m *Adapter) BuildSingbox(uuid string) ([]byte, error) {
|
||||
return singbox.BuildSingbox(m.Adapter, uuid)
|
||||
}
|
||||
|
||||
func (m *Adapter) BuildShadowrocket(uuid string, userInfo shadowrocket.UserInfo) []byte {
|
||||
return shadowrocket.BuildShadowrocket(m.Proxies, uuid, userInfo)
|
||||
}
|
||||
|
||||
// BuildSurfboard generates a Surfboard configuration for the given site name and user info.
|
||||
func (m *Adapter) BuildSurfboard(siteName string, user surfboard.UserInfo) []byte {
|
||||
return surfboard.BuildSurfboard(m.Adapter, siteName, user)
|
||||
}
|
||||
|
||||
// BuildV2rayN generates a V2rayN configuration for the given UUID.
|
||||
func (m *Adapter) BuildV2rayN(uuid string) []byte {
|
||||
return v2rayn.NewV2rayN(m.Adapter).Build(uuid)
|
||||
}
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
package clash
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"text/template"
|
||||
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"github.com/perfect-panel/server/pkg/adapter/proxy"
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
"gopkg.in/yaml.v3"
|
||||
@ -20,22 +23,16 @@ func NewClash(adapter proxy.Adapter) *Clash {
|
||||
|
||||
func (c *Clash) Build(uuid string) ([]byte, error) {
|
||||
var proxies []Proxy
|
||||
for _, v := range c.Proxies {
|
||||
p, err := c.parseProxy(v, uuid)
|
||||
for _, proxied := range c.Adapter.Proxies {
|
||||
p, err := c.parseProxy(proxied, uuid)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to parse proxy for %s: %s", v.Name, err.Error())
|
||||
logger.Errorw("Failed to parse proxy", logger.Field("error", err), logger.Field("proxy", p.Name))
|
||||
continue
|
||||
}
|
||||
proxies = append(proxies, *p)
|
||||
}
|
||||
var rawConfig RawConfig
|
||||
if err := yaml.Unmarshal([]byte(DefaultTemplate), &rawConfig); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal template: %w", err)
|
||||
}
|
||||
rawConfig.Proxies = proxies
|
||||
// generate proxy groups
|
||||
var groups []ProxyGroup
|
||||
for _, group := range c.Group {
|
||||
for _, group := range c.Adapter.Group {
|
||||
groups = append(groups, ProxyGroup{
|
||||
Name: group.Name,
|
||||
Type: string(group.Type),
|
||||
@ -44,9 +41,38 @@ func (c *Clash) Build(uuid string) ([]byte, error) {
|
||||
Interval: group.Interval,
|
||||
})
|
||||
}
|
||||
rawConfig.ProxyGroups = groups
|
||||
rawConfig.Rules = append(c.Rules, fmt.Sprintf("MATCH,%s", c.Default))
|
||||
return yaml.Marshal(&rawConfig)
|
||||
var rules = append(c.Rules, fmt.Sprintf("MATCH,%s", c.Default))
|
||||
|
||||
tmplBytes, err := c.TemplateFS.ReadFile("template/clash.tpl")
|
||||
if err != nil {
|
||||
logger.Errorw("Failed to read template file", logger.Field("error", err))
|
||||
return nil, fmt.Errorf("failed to read template file: %w", err)
|
||||
}
|
||||
tpl, err := template.New("clash.yaml").Funcs(sprig.FuncMap()).Funcs(template.FuncMap{
|
||||
"toYaml": func(v interface{}) string {
|
||||
out, err := yaml.Marshal(v)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("# YAML encode error: %v", err.Error())
|
||||
}
|
||||
return string(out)
|
||||
},
|
||||
}).Parse(string(tmplBytes))
|
||||
if err != nil {
|
||||
logger.Errorw("[Clash] Failed to parse template", logger.Field("error", err))
|
||||
return nil, fmt.Errorf("failed to parse template: %w", err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
err = tpl.Execute(&buf, map[string]interface{}{
|
||||
"Proxies": proxies,
|
||||
"ProxyGroups": groups,
|
||||
"Rules": rules,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Errorw("[Clash] Failed to execute template", logger.Field("error", err))
|
||||
return nil, fmt.Errorf("failed to execute template: %w", err)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (c *Clash) parseProxy(p proxy.Proxy, uuid string) (*Proxy, error) {
|
||||
|
||||
@ -1,31 +1,50 @@
|
||||
package clash
|
||||
|
||||
const DefaultTemplate = `
|
||||
mixed-port: 7890
|
||||
mode: rule
|
||||
ipv6: true
|
||||
allow-lan: true
|
||||
bind-address: "*"
|
||||
mode: rule
|
||||
log-level: info
|
||||
external-controller: 127.0.0.1:9090
|
||||
global-client-fingerprint: chrome
|
||||
mixed-port: 7890
|
||||
log-level: error
|
||||
unified-delay: true
|
||||
geox-url:
|
||||
mmdb: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb"
|
||||
tcp-concurrent: true
|
||||
external-controller: 0.0.0.0:9090
|
||||
|
||||
tun:
|
||||
enable: true
|
||||
stack: system
|
||||
auto-route: true
|
||||
|
||||
dns:
|
||||
enable: true
|
||||
cache-algorithm: arc
|
||||
listen: 0.0.0.0:1053
|
||||
ipv6: true
|
||||
enhanced-mode: fake-ip
|
||||
fake-ip-range: 198.18.0.1/16
|
||||
use-hosts: true
|
||||
fake-ip-filter:
|
||||
- "*.lan"
|
||||
- "lens.l.google.com"
|
||||
- "*.srv.nintendo.net"
|
||||
- "*.stun.playstation.net"
|
||||
- "xbox.*.*.microsoft.com"
|
||||
- "*.xboxlive.com"
|
||||
- "*.msftncsi.com"
|
||||
- "*.msftconnecttest.com"
|
||||
default-nameserver:
|
||||
- 120.53.53.53
|
||||
- 1.12.12.12
|
||||
- 119.29.29.29
|
||||
- 223.5.5.5
|
||||
nameserver:
|
||||
- https://120.53.53.53/dns-query#skip-cert-verify=true
|
||||
- tls://1.12.12.12#skip-cert-verify=true
|
||||
proxy-server-nameserver:
|
||||
- https://120.53.53.53/dns-query#skip-cert-verify=true
|
||||
- tls://1.12.12.12#skip-cert-verify=true
|
||||
- system
|
||||
- 119.29.29.29
|
||||
- 223.5.5.5
|
||||
fallback:
|
||||
- 8.8.8.8
|
||||
- 1.1.1.1
|
||||
fallback-filter:
|
||||
geoip: true
|
||||
geoip-code: CN
|
||||
|
||||
proxies:
|
||||
|
||||
|
||||
@ -1,22 +1,26 @@
|
||||
package proxy
|
||||
|
||||
import "embed"
|
||||
|
||||
// Adapter represents a proxy adapter
|
||||
type Adapter struct {
|
||||
Proxies []Proxy
|
||||
Group []Group
|
||||
Rules []string // rule
|
||||
Nodes []string // all node
|
||||
Default string // Default Node
|
||||
Proxies []Proxy
|
||||
Group []Group
|
||||
Rules []string // rule
|
||||
Nodes []string // all node
|
||||
Default string // Default Node
|
||||
TemplateFS *embed.FS // Template file system
|
||||
}
|
||||
|
||||
// Proxy represents a proxy server
|
||||
type Proxy struct {
|
||||
Name string
|
||||
Server string
|
||||
Port int
|
||||
Protocol string
|
||||
Country string
|
||||
Option any
|
||||
Name string // Name of the proxy
|
||||
Server string // Server address of the proxy
|
||||
Port int // Port of the proxy server
|
||||
Protocol string // Protocol type (e.g., shadowsocks, vless, vmess, trojan, hysteria2, tuic, anytls)
|
||||
Country string // Country of the proxy
|
||||
Tags []string // Tags for the proxy
|
||||
Option any // Additional options for the proxy configuration
|
||||
}
|
||||
|
||||
// Group represents a group of proxies
|
||||
@ -26,7 +30,10 @@ type Group struct {
|
||||
Proxies []string
|
||||
URL string
|
||||
Interval int
|
||||
Direct bool
|
||||
Reject bool // Reject group
|
||||
Direct bool // Direct group
|
||||
Tags []string // Tags for the group
|
||||
Default bool // Default group
|
||||
}
|
||||
|
||||
type GroupType string
|
||||
|
||||
51
pkg/adapter/template/clash.tpl
Normal file
51
pkg/adapter/template/clash.tpl
Normal file
@ -0,0 +1,51 @@
|
||||
mode: rule
|
||||
ipv6: true
|
||||
allow-lan: true
|
||||
bind-address: "*"
|
||||
mixed-port: 7890
|
||||
log-level: error
|
||||
unified-delay: true
|
||||
tcp-concurrent: true
|
||||
external-controller: 0.0.0.0:9090
|
||||
|
||||
tun:
|
||||
enable: true
|
||||
stack: system
|
||||
auto-route: true
|
||||
|
||||
dns:
|
||||
enable: true
|
||||
cache-algorithm: arc
|
||||
listen: 0.0.0.0:1053
|
||||
ipv6: true
|
||||
enhanced-mode: fake-ip
|
||||
fake-ip-range: 198.18.0.1/16
|
||||
fake-ip-filter:
|
||||
- "*.lan"
|
||||
- "lens.l.google.com"
|
||||
- "*.srv.nintendo.net"
|
||||
- "*.stun.playstation.net"
|
||||
- "xbox.*.*.microsoft.com"
|
||||
- "*.xboxlive.com"
|
||||
- "*.msftncsi.com"
|
||||
- "*.msftconnecttest.com"
|
||||
default-nameserver:
|
||||
- 119.29.29.29
|
||||
- 223.5.5.5
|
||||
nameserver:
|
||||
- system
|
||||
- 119.29.29.29
|
||||
- 223.5.5.5
|
||||
fallback:
|
||||
- 8.8.8.8
|
||||
- 1.1.1.1
|
||||
fallback-filter:
|
||||
geoip: true
|
||||
geoip-code: CN
|
||||
|
||||
proxies:
|
||||
{{.Proxies | toYaml | indent 2}}
|
||||
proxy-groups:
|
||||
{{.ProxyGroups | toYaml | indent 2}}
|
||||
rules:
|
||||
{{.Rules | toYaml | indent 2}}
|
||||
@ -2,6 +2,7 @@ package adapter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/server"
|
||||
@ -11,14 +12,21 @@ import (
|
||||
"github.com/perfect-panel/server/pkg/tool"
|
||||
)
|
||||
|
||||
// addNode creates a new proxy node based on the provided server data and host/port.
|
||||
func addNode(data *server.Server, host string, port int) *proxy.Proxy {
|
||||
var option any
|
||||
tags := strings.Split(data.Tags, ",")
|
||||
if len(tags) > 0 {
|
||||
tags = tool.RemoveDuplicateElements(tags...)
|
||||
}
|
||||
|
||||
node := proxy.Proxy{
|
||||
Name: data.Name,
|
||||
Server: host,
|
||||
Port: port,
|
||||
Country: data.Country,
|
||||
Protocol: data.Protocol,
|
||||
Tags: tags,
|
||||
}
|
||||
switch data.Protocol {
|
||||
case "shadowsocks":
|
||||
@ -82,64 +90,45 @@ func addNode(data *server.Server, host string, port int) *proxy.Proxy {
|
||||
return &node
|
||||
}
|
||||
|
||||
func addProxyToGroup(proxyName, groupName string, groups []proxy.Group) []proxy.Group {
|
||||
for i, group := range groups {
|
||||
if group.Name == groupName {
|
||||
groups[i].Proxies = tool.RemoveDuplicateElements(append(group.Proxies, proxyName)...)
|
||||
return groups
|
||||
}
|
||||
}
|
||||
groups = append(groups, proxy.Group{
|
||||
Name: groupName,
|
||||
Type: proxy.GroupTypeSelect,
|
||||
Proxies: []string{proxyName},
|
||||
})
|
||||
return groups
|
||||
}
|
||||
|
||||
func adapterRules(groups []*server.RuleGroup) (proxyGroup []proxy.Group, rules []string) {
|
||||
func adapterRules(groups []*server.RuleGroup) (proxyGroup []proxy.Group, rules []string, defaultGroup string) {
|
||||
for _, group := range groups {
|
||||
if group.Default {
|
||||
log.Printf("[Debug] 规则组 %s 是默认组", group.Name)
|
||||
defaultGroup = group.Name
|
||||
}
|
||||
switch group.Type {
|
||||
case server.RuleGroupTypeBan:
|
||||
case server.RuleGroupTypeReject:
|
||||
proxyGroup = append(proxyGroup, proxy.Group{
|
||||
Name: group.Name,
|
||||
Type: proxy.GroupTypeSelect,
|
||||
Proxies: []string{"REJECT", "DIRECT"},
|
||||
Direct: true,
|
||||
Proxies: []string{"REJECT", "DIRECT", AutoSelect},
|
||||
Reject: true,
|
||||
})
|
||||
case server.RuleGroupTypeAuto:
|
||||
case server.RuleGroupTypeDirect:
|
||||
proxyGroup = append(proxyGroup, proxy.Group{
|
||||
Name: group.Name,
|
||||
Type: proxy.GroupTypeURLTest,
|
||||
URL: "https://www.gstatic.com/generate_204",
|
||||
Proxies: RemoveEmptyString(strings.Split(group.Tags, ",")),
|
||||
Type: proxy.GroupTypeSelect,
|
||||
Proxies: []string{"DIRECT", AutoSelect},
|
||||
Direct: true,
|
||||
})
|
||||
default:
|
||||
proxyGroup = append(proxyGroup, proxy.Group{
|
||||
Name: group.Name,
|
||||
Type: proxy.GroupTypeSelect,
|
||||
Proxies: RemoveEmptyString(strings.Split(group.Tags, ",")),
|
||||
Proxies: []string{},
|
||||
Tags: RemoveEmptyString(strings.Split(group.Tags, ",")),
|
||||
Default: group.Default,
|
||||
})
|
||||
}
|
||||
|
||||
rules = append(rules, strings.Split(group.Rules, "\n")...)
|
||||
}
|
||||
return
|
||||
log.Printf("[Dapter] 生成规则组: %d", len(proxyGroup))
|
||||
return proxyGroup, tool.RemoveDuplicateElements(rules...), defaultGroup
|
||||
}
|
||||
|
||||
func adapterTags(tags map[string][]*server.Server, group []proxy.Group) (proxyGroup []proxy.Group) {
|
||||
for tag, servers := range tags {
|
||||
proxies := adapterProxies(servers)
|
||||
if len(proxies) != 0 {
|
||||
for _, p := range proxies {
|
||||
group = addProxyToGroup(p.Name, tag, group)
|
||||
}
|
||||
}
|
||||
}
|
||||
return group
|
||||
}
|
||||
|
||||
func generateProxyGroup(servers []proxy.Proxy) (proxyGroup []proxy.Group, nodes []string) {
|
||||
// generateDefaultGroup generates a default proxy group with auto-selection and manual selection options.
|
||||
func generateDefaultGroup() (proxyGroup []proxy.Group) {
|
||||
proxyGroup = append(proxyGroup, proxy.Group{
|
||||
Name: AutoSelect,
|
||||
Type: proxy.GroupTypeURLTest,
|
||||
@ -148,23 +137,12 @@ func generateProxyGroup(servers []proxy.Proxy) (proxyGroup []proxy.Group, nodes
|
||||
Interval: 300,
|
||||
})
|
||||
|
||||
// 设置手动选择分组
|
||||
proxyGroup = append(proxyGroup, proxy.Group{
|
||||
Name: Selection,
|
||||
Type: proxy.GroupTypeSelect,
|
||||
Proxies: []string{AutoSelect},
|
||||
})
|
||||
|
||||
for _, node := range servers {
|
||||
proxyGroup = addProxyToGroup(node.Name, AutoSelect, proxyGroup)
|
||||
proxyGroup = addProxyToGroup(node.Name, Selection, proxyGroup)
|
||||
nodes = append(nodes, node.Name)
|
||||
}
|
||||
return proxyGroup, tool.RemoveDuplicateElements(nodes...)
|
||||
return proxyGroup
|
||||
}
|
||||
|
||||
func adapterProxies(servers []*server.Server) []proxy.Proxy {
|
||||
func adapterProxies(servers []*server.Server) ([]proxy.Proxy, []string, map[string][]string) {
|
||||
var proxies []proxy.Proxy
|
||||
var tags = make(map[string][]string)
|
||||
for _, node := range servers {
|
||||
switch node.RelayMode {
|
||||
case server.RelayModeAll:
|
||||
@ -179,8 +157,20 @@ func adapterProxies(servers []*server.Server) []proxy.Proxy {
|
||||
continue
|
||||
}
|
||||
if relay.Prefix != "" {
|
||||
n.Name = relay.Prefix + "-" + n.Name
|
||||
n.Name = relay.Prefix + n.Name
|
||||
}
|
||||
if node.Tags != "" {
|
||||
t := tool.RemoveDuplicateElements(strings.Split(node.Tags, ",")...)
|
||||
for _, tag := range t {
|
||||
if tag != "" {
|
||||
if _, ok := tags[tag]; !ok {
|
||||
tags[tag] = []string{}
|
||||
}
|
||||
tags[tag] = append(tags[tag], n.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proxies = append(proxies, *n)
|
||||
}
|
||||
case server.RelayModeRandom:
|
||||
@ -196,18 +186,46 @@ func adapterProxies(servers []*server.Server) []proxy.Proxy {
|
||||
continue
|
||||
}
|
||||
if relay.Prefix != "" {
|
||||
n.Name = relay.Prefix + " - " + node.Name
|
||||
n.Name = relay.Prefix + node.Name
|
||||
}
|
||||
if node.Tags != "" {
|
||||
t := tool.RemoveDuplicateElements(strings.Split(node.Tags, ",")...)
|
||||
for _, tag := range t {
|
||||
if tag != "" {
|
||||
if _, ok := tags[tag]; !ok {
|
||||
tags[tag] = []string{}
|
||||
}
|
||||
tags[tag] = append(tags[tag], n.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
proxies = append(proxies, *n)
|
||||
default:
|
||||
logger.Info("Not Relay Mode", logger.Field("node", node.Name), logger.Field("relayMode", node.RelayMode))
|
||||
n := addNode(node, node.ServerAddr, 0)
|
||||
if n != nil {
|
||||
if node.Tags != "" {
|
||||
t := tool.RemoveDuplicateElements(strings.Split(node.Tags, ",")...)
|
||||
for _, tag := range t {
|
||||
if tag != "" {
|
||||
if _, ok := tags[tag]; !ok {
|
||||
tags[tag] = []string{}
|
||||
}
|
||||
tags[tag] = append(tags[tag], n.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
proxies = append(proxies, *n)
|
||||
}
|
||||
}
|
||||
}
|
||||
return proxies
|
||||
|
||||
var nodes []string
|
||||
for _, p := range proxies {
|
||||
nodes = append(nodes, p.Name)
|
||||
}
|
||||
|
||||
return proxies, tool.RemoveDuplicateElements(nodes...), tags
|
||||
}
|
||||
|
||||
// RemoveEmptyString 切片去除空值
|
||||
@ -221,61 +239,61 @@ func RemoveEmptyString(arr []string) []string {
|
||||
return result
|
||||
}
|
||||
|
||||
// RemoveEmptyGroup removes empty groups from the provided slice of proxy groups.
|
||||
func RemoveEmptyGroup(arr []proxy.Group) []proxy.Group {
|
||||
var result []proxy.Group
|
||||
var removeNames []string
|
||||
for _, group := range arr {
|
||||
if group.Name == "手动选择" {
|
||||
group.Proxies = tool.RemoveStringElement(group.Proxies, removeNames...)
|
||||
}
|
||||
if len(group.Proxies) > 0 {
|
||||
result = append(result, group)
|
||||
} else {
|
||||
removeNames = append(removeNames, group.Name)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// FindDefaultGroup finds the default rule group from a list of rule groups.
|
||||
func FindDefaultGroup(groups []*server.RuleGroup) string {
|
||||
for _, group := range groups {
|
||||
if group.Default {
|
||||
return group.Name
|
||||
}
|
||||
}
|
||||
return AutoSelect
|
||||
}
|
||||
|
||||
// SortGroups sorts the provided slice of proxy groups by their names.
|
||||
func SortGroups(groups []proxy.Group, defaultName string) []proxy.Group {
|
||||
func SortGroups(groups []proxy.Group, nodes []string, tags map[string][]string, defaultName string) []proxy.Group {
|
||||
var sortedGroups []proxy.Group
|
||||
var selectedGroup proxy.Group
|
||||
var defaultGroup, autoSelectGroup proxy.Group
|
||||
// 在所有分组找到默认分组并将他放到第一个
|
||||
for _, group := range groups {
|
||||
if group.Name == "" || group.Name == "DIRECT" || group.Name == "REJECT" {
|
||||
continue
|
||||
}
|
||||
if group.Name == defaultName {
|
||||
group.Proxies = tool.RemoveStringElement(group.Proxies, defaultName, "REJECT")
|
||||
sortedGroups = append([]proxy.Group{group}, sortedGroups...)
|
||||
continue
|
||||
} else if group.Name == Selection {
|
||||
group.Proxies = tool.RemoveStringElement(group.Proxies, defaultName)
|
||||
selectedGroup = group
|
||||
continue
|
||||
} else if group.Name == AutoSelect {
|
||||
group.Proxies = tool.RemoveStringElement(group.Proxies, defaultName, group.Name)
|
||||
sortedGroups = append([]proxy.Group{group}, sortedGroups...)
|
||||
// 如果是默认分组
|
||||
if group.Default {
|
||||
group.Proxies = append([]string{AutoSelect}, nodes...)
|
||||
group.Proxies = append(group.Proxies, "DIRECT")
|
||||
defaultGroup = group
|
||||
continue
|
||||
}
|
||||
if group.Reject || group.Direct {
|
||||
if defaultName != AutoSelect {
|
||||
group.Proxies = append(group.Proxies, defaultName)
|
||||
}
|
||||
sortedGroups = append(sortedGroups, group)
|
||||
continue
|
||||
}
|
||||
|
||||
if group.Name == AutoSelect {
|
||||
group.Proxies = nodes
|
||||
autoSelectGroup = group
|
||||
continue
|
||||
}
|
||||
// Tags 分组
|
||||
if len(group.Tags) > 0 {
|
||||
var proxies []string
|
||||
for _, tag := range group.Tags {
|
||||
if node, ok := tags[tag]; ok {
|
||||
proxies = append(proxies, node...)
|
||||
}
|
||||
}
|
||||
group.Proxies = append(tool.RemoveDuplicateElements(proxies...), AutoSelect, "DIRECT")
|
||||
sortedGroups = append(sortedGroups, group)
|
||||
continue
|
||||
}
|
||||
|
||||
group.Proxies = append([]string{AutoSelect}, nodes...)
|
||||
group.Proxies = append(group.Proxies, "DIRECT")
|
||||
group.Proxies = tool.RemoveElementBySlice(group.Proxies, group.Name)
|
||||
sortedGroups = append(sortedGroups, group)
|
||||
}
|
||||
// 将手动选择分组放到最后
|
||||
if selectedGroup.Name != "" {
|
||||
sortedGroups = append(sortedGroups, selectedGroup)
|
||||
|
||||
if defaultGroup.Name != "" {
|
||||
sortedGroups = append([]proxy.Group{defaultGroup}, sortedGroups...)
|
||||
}
|
||||
if autoSelectGroup.Name != "" && autoSelectGroup.Name != defaultGroup.Name {
|
||||
sortedGroups = append(sortedGroups, autoSelectGroup)
|
||||
}
|
||||
|
||||
return sortedGroups
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user