Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d82af6f9bd | |||
| 6b785c3404 | |||
| df1a271efa | |||
| bd9bd8590c | |||
| d186875ab7 | |||
| 3f7657c080 | |||
| a5f4c46066 |
@@ -0,0 +1,125 @@
|
||||
package migration
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"s-ui/database/model"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func migrate_dns(db *gorm.DB) error {
|
||||
var configStr string
|
||||
err := db.Model(model.Setting{}).Select("value").Where("key = ?", "config").First(&configStr).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if configStr == "" {
|
||||
return nil
|
||||
}
|
||||
var config map[string]interface{}
|
||||
err = json.Unmarshal([]byte(configStr), &config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dnsConfig, ok := config["dns"].(map[string]interface{}); ok {
|
||||
if dnsServers, ok := dnsConfig["servers"].([]interface{}); ok {
|
||||
for index, dnsServer := range dnsServers {
|
||||
if dnsServer, ok := dnsServer.(map[string]interface{}); ok {
|
||||
if addr, ok := dnsServer["address"].(string); ok && addr != "" {
|
||||
switch addr {
|
||||
case "local":
|
||||
delete(dnsServer, "address")
|
||||
dnsServer["type"] = "local"
|
||||
case "fakeip":
|
||||
delete(dnsServer, "address")
|
||||
dnsServer["type"] = "fakeip"
|
||||
default:
|
||||
addrParsed, err := url.Parse(addr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
switch addrParsed.Scheme {
|
||||
case "":
|
||||
dnsServer["type"] = "udp"
|
||||
dnsServer["server"] = addr
|
||||
case "udp", "tcp", "tls", "quic", "https", "h3":
|
||||
dnsServer["type"] = addrParsed.Scheme
|
||||
dnsServer["server"] = addrParsed.Host
|
||||
case "dhcp":
|
||||
dnsServer["type"] = addrParsed.Scheme
|
||||
if addrParsed.Host != "auto" && addrParsed.Host != "" {
|
||||
dnsServer["interface"] = addrParsed.Host
|
||||
}
|
||||
case "rcode":
|
||||
dnsServer["type"] = "predefined"
|
||||
dnsServer["responses"] = []map[string]string{
|
||||
{
|
||||
"rcode": strings.ToUpper(addrParsed.Host),
|
||||
},
|
||||
}
|
||||
}
|
||||
delete(dnsServer, "address")
|
||||
if addrParsed.Port() != "" {
|
||||
port, err := strconv.Atoi(addrParsed.Port())
|
||||
if err == nil {
|
||||
dnsServer["server_port"] = port
|
||||
}
|
||||
}
|
||||
if address_resolver, ok := dnsServer["address_resolver"].(string); ok && address_resolver != "" {
|
||||
delete(dnsServer, "address_resolver")
|
||||
dnsServer["domain_resolver"] = address_resolver
|
||||
}
|
||||
delete(dnsServer, "strategy")
|
||||
}
|
||||
dnsServers[index] = dnsServer
|
||||
}
|
||||
}
|
||||
}
|
||||
dnsConfig["servers"] = dnsServers
|
||||
}
|
||||
config["dns"] = dnsConfig
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// save changes
|
||||
configs, err := json.MarshalIndent(config, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return db.Model(model.Setting{}).Where("key = ?", "config").Update("value", string(configs)).Error
|
||||
}
|
||||
|
||||
func remove_outbound_strategy(db *gorm.DB) error {
|
||||
var outbounds []model.Outbound
|
||||
err := db.Find(&outbounds).Where("json_extract(options, '$.domain_strategy') IS NOT NULL").Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, outbound := range outbounds {
|
||||
var restFields map[string]json.RawMessage
|
||||
if err := json.Unmarshal(outbound.Options, &restFields); err != nil {
|
||||
return err
|
||||
}
|
||||
delete(restFields, "domain_strategy")
|
||||
outbound.Options, _ = json.MarshalIndent(restFields, "", " ")
|
||||
db.Save(&outbound)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func to1_3(db *gorm.DB) error {
|
||||
err := migrate_dns(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = remove_outbound_strategy(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+11
-1
@@ -56,10 +56,20 @@ func MigrateDb() {
|
||||
log.Fatal("Migration to 1.2 failed: ", err)
|
||||
return
|
||||
}
|
||||
dbVersion = "1.2"
|
||||
}
|
||||
|
||||
// Before 1.3
|
||||
if dbVersion[0:3] == "1.2" {
|
||||
err = to1_3(tx)
|
||||
if err != nil {
|
||||
log.Fatal("Migration to 1.3 failed: ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Set version
|
||||
err = tx.Raw("UPDATE settings SET value = ? WHERE key = ?", currentVersion, "version").Error
|
||||
err = tx.Exec("UPDATE settings SET value = ? WHERE key = ?", currentVersion, "version").Error
|
||||
if err != nil {
|
||||
log.Fatal("Update version failed: ", err)
|
||||
return
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.3.0-beta.0
|
||||
1.3.0-beta.2
|
||||
+1
-1
Submodule frontend updated: c4ac3ad924...2d7eb9d640
@@ -9,7 +9,7 @@ require (
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b
|
||||
github.com/sagernet/sing-box v1.12.0-beta.19
|
||||
github.com/sagernet/sing-box v1.12.0-beta.21
|
||||
github.com/sagernet/sing-dns v0.4.5
|
||||
github.com/shirou/gopsutil/v4 v4.25.4
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
|
||||
@@ -107,12 +107,12 @@ require (
|
||||
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
||||
github.com/sagernet/quic-go v0.52.0-beta.1 // indirect
|
||||
github.com/sagernet/sing-mux v0.3.2 // indirect
|
||||
github.com/sagernet/sing-quic v0.5.0-beta.1 // indirect
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
|
||||
github.com/sagernet/sing-quic v0.5.0-beta.2 // indirect
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8 // indirect
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1 // indirect
|
||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 // indirect
|
||||
github.com/sagernet/sing-tun v0.6.6-0.20250428031943-0686f8c4f210 // indirect
|
||||
github.com/sagernet/sing-vmess v0.2.2-0.20250503051933-9b4cf17393f8 // indirect
|
||||
github.com/sagernet/sing-vmess v0.2.4-0.20250527060135-661c827800bc // indirect
|
||||
github.com/sagernet/smux v1.5.34-mod.2 // indirect
|
||||
github.com/sagernet/tailscale v1.80.3-mod.5 // indirect
|
||||
github.com/sagernet/wireguard-go v0.0.1-beta.7 // indirect
|
||||
|
||||
@@ -220,14 +220,20 @@ github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b h1:ZjTCYPb5f7aHdf
|
||||
github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing-box v1.12.0-beta.19 h1:UrEuYcewe9C68aGuQyE+dDRjtv+uZXXh5DK9hkQ0UTE=
|
||||
github.com/sagernet/sing-box v1.12.0-beta.19/go.mod h1:zhoIuo39/5gsmJPIMK5P9Z0/SiRmFJsGtfl+8j+Cdcw=
|
||||
github.com/sagernet/sing-box v1.12.0-beta.21 h1:4WKaD0NAfgMDrDJaoQs7QC1tF/zIQT2DRcALW+CSDFM=
|
||||
github.com/sagernet/sing-box v1.12.0-beta.21/go.mod h1:0IuY97uJ8ydg+rA2xdy/Wn3n1182jlf7mEeBUYP7xWw=
|
||||
github.com/sagernet/sing-dns v0.4.5 h1:D9REN14qx2FTrZRBrtFLL99f2CuFzQ9S7mIf8uV5hZI=
|
||||
github.com/sagernet/sing-dns v0.4.5/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8=
|
||||
github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE=
|
||||
github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
|
||||
github.com/sagernet/sing-quic v0.5.0-beta.1 h1:nC0i/s8LhlZB8ev6laZCXF/uiwAE4kRdT4PcDdE4rI4=
|
||||
github.com/sagernet/sing-quic v0.5.0-beta.1/go.mod h1:SAv/qdeDN+75msGG5U5ZIwG+3Ua50jVIKNrRSY8pkx0=
|
||||
github.com/sagernet/sing-quic v0.5.0-beta.2 h1:j7KAbBuGmsKwSxVAQL5soJ+wDqxim4/llK2kxB0hSKk=
|
||||
github.com/sagernet/sing-quic v0.5.0-beta.2/go.mod h1:SAv/qdeDN+75msGG5U5ZIwG+3Ua50jVIKNrRSY8pkx0=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
|
||||
@@ -236,6 +242,8 @@ github.com/sagernet/sing-tun v0.6.6-0.20250428031943-0686f8c4f210 h1:6H4BZaTqKI3
|
||||
github.com/sagernet/sing-tun v0.6.6-0.20250428031943-0686f8c4f210/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||
github.com/sagernet/sing-vmess v0.2.2-0.20250503051933-9b4cf17393f8 h1:zW+zAOCxUIqBCgnZiPovt1uQ3S+zBS+w0NGp+1zITGA=
|
||||
github.com/sagernet/sing-vmess v0.2.2-0.20250503051933-9b4cf17393f8/go.mod h1:IL8Rr+EGwuqijszZkNrEFTQDKhilEpkqFqOlvdpS6/w=
|
||||
github.com/sagernet/sing-vmess v0.2.4-0.20250527060135-661c827800bc h1:kd3olNfnf/1EAAHDQm0flN9eihyjpeQDKdGONlLtXfc=
|
||||
github.com/sagernet/sing-vmess v0.2.4-0.20250527060135-661c827800bc/go.mod h1:IL8Rr+EGwuqijszZkNrEFTQDKhilEpkqFqOlvdpS6/w=
|
||||
github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=
|
||||
github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc=
|
||||
github.com/sagernet/tailscale v1.80.3-mod.5 h1:7V7z+p2C//TGtff20pPnDCt3qP6uFyY62peJoKF9z/A=
|
||||
|
||||
@@ -56,6 +56,7 @@ var defaultValueMap = map[string]string{
|
||||
"subShowInfo": "false",
|
||||
"subURI": "",
|
||||
"subJsonExt": "",
|
||||
"subClashExt": "",
|
||||
"config": defaultConfig,
|
||||
"version": config.GetVersion(),
|
||||
}
|
||||
@@ -392,6 +393,10 @@ func (s *SettingService) GetSubJsonExt() (string, error) {
|
||||
return s.getString("subJsonExt")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubClashExt() (string, error) {
|
||||
return s.getString("subClashExt")
|
||||
}
|
||||
|
||||
func (s *SettingService) fileExists(path string) error {
|
||||
_, err := os.Stat(path)
|
||||
return err
|
||||
|
||||
@@ -0,0 +1,332 @@
|
||||
package sub
|
||||
|
||||
import (
|
||||
"s-ui/logger"
|
||||
"s-ui/service"
|
||||
"s-ui/util"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type ClashService struct {
|
||||
service.SettingService
|
||||
JsonService
|
||||
LinkService
|
||||
}
|
||||
|
||||
const basicClashConfig = `mixed-port: 7890
|
||||
allow-lan: false
|
||||
mode: rule
|
||||
log-level: info
|
||||
external-controller: 127.0.0.1:9090
|
||||
tun:
|
||||
enable: true
|
||||
stack: system
|
||||
auto-route: true
|
||||
auto-detect-interface: true
|
||||
dns-hijack:
|
||||
- any:53
|
||||
dns:
|
||||
enable: true
|
||||
ipv6: false
|
||||
enhanced-mode: fake-ip
|
||||
fake-ip-range: 198.18.0.1/16
|
||||
default-nameserver:
|
||||
- 8.8.8.8
|
||||
- 1.1.1.1
|
||||
nameserver:
|
||||
- https://doh.pub/dns-query
|
||||
- https://1.0.0.1/dns-query
|
||||
fallback:
|
||||
- tcp://9.9.9.9:53
|
||||
fake-ip-filter:
|
||||
- "*.lan"
|
||||
- localhost
|
||||
- "*.local"
|
||||
rules:
|
||||
- GEOIP,Private,DIRECT
|
||||
- MATCH,Proxy
|
||||
`
|
||||
|
||||
const ProxyGroups = `- name: Proxy
|
||||
type: select
|
||||
proxies: []
|
||||
- name: Auto
|
||||
type: url-test
|
||||
proxies: []
|
||||
url: http://www.gstatic.com/generate_204
|
||||
interval: 300
|
||||
tolerance: 50
|
||||
`
|
||||
|
||||
func (s *ClashService) GetClash(subId string) (*string, []string, error) {
|
||||
|
||||
client, inDatas, err := s.getData(subId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
outbounds, outTags, err := s.getOutbounds(client.Config, inDatas)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
links := s.LinkService.GetLinks(&client.Links, "external", "")
|
||||
for index, link := range links {
|
||||
json, tag, err := util.GetOutbound(link, index)
|
||||
if err == nil && len(tag) > 0 {
|
||||
*outbounds = append(*outbounds, *json)
|
||||
*outTags = append(*outTags, tag)
|
||||
}
|
||||
}
|
||||
|
||||
othersStr, err := s.getClashConfig()
|
||||
if err != nil || len(othersStr) == 0 {
|
||||
othersStr = basicClashConfig
|
||||
}
|
||||
|
||||
result, err := s.ConvertToClashMeta(outbounds)
|
||||
resultStr := othersStr + "\n" + string(result)
|
||||
|
||||
updateInterval, _ := s.SettingService.GetSubUpdates()
|
||||
headers := util.GetHeaders(client, updateInterval)
|
||||
|
||||
return &resultStr, headers, nil
|
||||
}
|
||||
|
||||
func (s *ClashService) getClashConfig() (string, error) {
|
||||
subClashExt, err := s.SettingService.GetSubClashExt()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return subClashExt, nil
|
||||
}
|
||||
|
||||
func (s *ClashService) ConvertToClashMeta(outbounds *[]map[string]interface{}) ([]byte, error) {
|
||||
var proxies []interface{}
|
||||
proxyTags := make([]string, 0)
|
||||
for _, obMap := range *outbounds {
|
||||
|
||||
t, _ := obMap["type"].(string)
|
||||
if t == "selector" || t == "urltest" || t == "direct" {
|
||||
continue
|
||||
}
|
||||
|
||||
proxy := make(map[string]interface{})
|
||||
proxy["name"] = obMap["tag"]
|
||||
proxy["type"] = t
|
||||
proxy["server"] = obMap["server"]
|
||||
proxy["port"] = obMap["server_port"]
|
||||
|
||||
switch t {
|
||||
case "vmess", "vless", "tuic":
|
||||
proxy["uuid"] = obMap["uuid"]
|
||||
if t == "vmess" {
|
||||
proxy["alterId"] = obMap["alter_id"]
|
||||
proxy["cipher"] = "auto"
|
||||
}
|
||||
if t == "vless" {
|
||||
if flow, ok := obMap["flow"].(string); ok {
|
||||
proxy["flow"] = flow
|
||||
}
|
||||
}
|
||||
case "trojan":
|
||||
proxy["password"] = obMap["password"]
|
||||
case "socks", "http":
|
||||
if t == "socks" {
|
||||
proxy["type"] = "socks5"
|
||||
}
|
||||
proxy["username"] = obMap["username"]
|
||||
proxy["password"] = obMap["password"]
|
||||
case "hysteria", "hysteria2":
|
||||
if _, ok := obMap["up_mbps"].(float64); ok {
|
||||
proxy["up"] = obMap["up_mbps"]
|
||||
} else {
|
||||
proxy["up"] = 1000
|
||||
}
|
||||
if _, ok := obMap["down_mbps"].(float64); ok {
|
||||
proxy["down"] = obMap["down_mbps"]
|
||||
} else {
|
||||
proxy["down"] = 1000
|
||||
}
|
||||
if t == "hysteria" {
|
||||
proxy["auth-str"] = obMap["auth_str"]
|
||||
if obfs, ok := obMap["obfs"].(string); ok {
|
||||
proxy["obfs"] = obfs
|
||||
}
|
||||
} else {
|
||||
proxy["password"] = obMap["password"]
|
||||
if obfs, ok := obMap["obfs"].(map[string]interface{}); ok {
|
||||
proxy["obfs"] = obfs["type"]
|
||||
proxy["obfs-password"] = obfs["password"]
|
||||
}
|
||||
if ports, ok := obMap["server_ports"].([]string); ok {
|
||||
proxy["ports"] = strings.ReplaceAll(strings.Join(ports, ","), ":", "-")
|
||||
}
|
||||
}
|
||||
case "anytls":
|
||||
proxy["password"] = obMap["password"]
|
||||
if tls, ok := obMap["tls"].(map[string]interface{}); ok {
|
||||
proxy["sni"] = tls["server_name"]
|
||||
proxy["skip-cert-verify"] = tls["insecure"]
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
// TLS params
|
||||
tls, isTls := obMap["tls"].(map[string]interface{})
|
||||
if isTls {
|
||||
tlsEnabled, ok := tls["enabled"].(bool)
|
||||
if ok && !tlsEnabled {
|
||||
isTls = false
|
||||
}
|
||||
}
|
||||
if isTls {
|
||||
// ignore ech outbounds
|
||||
if _, ok := tls["ech"].(interface{}); ok {
|
||||
continue
|
||||
}
|
||||
proxy["tls"] = tls["enabled"]
|
||||
|
||||
// ALPN if exists
|
||||
if alpn, ok := tls["alpn"].([]interface{}); ok {
|
||||
proxy["alpn"] = alpn
|
||||
}
|
||||
|
||||
// Add reality if exists
|
||||
if reality, ok := tls["reality"].(map[string]interface{}); ok && reality["enabled"].(bool) {
|
||||
reality_opts := make(map[string]interface{})
|
||||
if pbk, ok := reality["public_key"].(string); ok {
|
||||
reality_opts["public-key"] = pbk
|
||||
}
|
||||
if sid, ok := reality["short_id"].(string); ok {
|
||||
reality_opts["short-id"] = sid
|
||||
}
|
||||
proxy["reality-opts"] = reality_opts
|
||||
}
|
||||
if utls, ok := tls["utls"].(map[string]interface{}); ok {
|
||||
if enabled, ok := utls["enabled"].(bool); ok && enabled {
|
||||
if fp, ok := utls["fingerprint"].(string); ok {
|
||||
proxy["client-fingerprint"] = fp
|
||||
}
|
||||
}
|
||||
}
|
||||
if sni, ok := tls["server_name"].(string); ok {
|
||||
if t == "http" {
|
||||
proxy["sni"] = sni
|
||||
} else {
|
||||
proxy["servername"] = sni
|
||||
}
|
||||
}
|
||||
if insecure, ok := tls["insecure"].(bool); ok && insecure {
|
||||
proxy["skip-cert-verify"] = insecure
|
||||
}
|
||||
}
|
||||
|
||||
// Transport if exist
|
||||
if transport, ok := obMap["transport"].(map[string]interface{}); ok {
|
||||
tt, _ := transport["type"].(string)
|
||||
switch tt {
|
||||
case "http":
|
||||
httpOpts := make(map[string]interface{})
|
||||
if path, ok := transport["path"].([]interface{}); ok {
|
||||
httpOpts["path"] = path[0]
|
||||
} else if path, ok := transport["path"].(string); ok {
|
||||
httpOpts["path"] = path
|
||||
}
|
||||
if host, ok := transport["host"].([]interface{}); ok {
|
||||
httpOpts["host"] = host[0]
|
||||
}
|
||||
if isTls {
|
||||
proxy["network"] = "h2"
|
||||
proxy["h2-opts"] = httpOpts
|
||||
} else {
|
||||
proxy["network"] = "http"
|
||||
proxy["http-opts"] = httpOpts
|
||||
}
|
||||
case "ws", "httpupgrade":
|
||||
proxy["network"] = "ws"
|
||||
wsOpts := make(map[string]interface{})
|
||||
if path, ok := transport["path"].(string); ok {
|
||||
wsOpts["path"] = path
|
||||
}
|
||||
if headers, ok := transport["headers"].([]interface{}); ok {
|
||||
wsOpts["headers"] = headers
|
||||
}
|
||||
if ed, ok := transport["early_data_header_name"].(string); ok {
|
||||
wsOpts["early-data-header-name"] = ed
|
||||
}
|
||||
if tt == "httpupgrade" {
|
||||
wsOpts["v2ray-http-upgrade"] = true
|
||||
}
|
||||
proxy["ws-opts"] = wsOpts
|
||||
case "grpc":
|
||||
proxy["network"] = "grpc"
|
||||
grpcOpts := make(map[string]interface{})
|
||||
if service_name, ok := transport["service_name"].(string); ok {
|
||||
grpcOpts["grpc-service-name"] = service_name
|
||||
}
|
||||
proxy["grpc-opts"] = grpcOpts
|
||||
}
|
||||
}
|
||||
|
||||
// Multiplex
|
||||
if mux, ok := obMap["multiplex"].(map[string]interface{}); ok {
|
||||
if enabled, ok := mux["enabled"].(bool); ok && enabled {
|
||||
smux := make(map[string]interface{})
|
||||
smux["enabled"] = true
|
||||
if protocol, ok := mux["protocol"].(string); ok {
|
||||
smux["protocol"] = protocol
|
||||
}
|
||||
if _, ok := mux["max_connections"].(float64); ok {
|
||||
smux["max-connections"] = mux["max_connections"]
|
||||
}
|
||||
if _, ok := mux["min_streams"].(float64); ok {
|
||||
smux["min-streams"] = mux["min_streams"]
|
||||
}
|
||||
if _, ok := mux["max_streams"].(float64); ok {
|
||||
smux["max-streams"] = mux["max_streams"]
|
||||
}
|
||||
if _, ok := mux["padding"].(bool); ok {
|
||||
smux["padding"] = mux["padding"]
|
||||
}
|
||||
if brutal, ok := mux["brutal"].(map[string]interface{}); ok {
|
||||
if enabled, ok := brutal["enabled"].(bool); ok && enabled {
|
||||
brutalOpts := make(map[string]interface{})
|
||||
brutalOpts["enabled"] = true
|
||||
if _, ok := brutal["up_mbps"].(float64); ok {
|
||||
brutalOpts["up"] = brutal["up_mbps"]
|
||||
}
|
||||
if _, ok := brutal["down_mbps"].(float64); ok {
|
||||
brutalOpts["down"] = brutal["down_mbps"]
|
||||
}
|
||||
smux["brutal-opts"] = brutalOpts
|
||||
}
|
||||
}
|
||||
proxy["smux"] = smux
|
||||
}
|
||||
}
|
||||
|
||||
proxies = append(proxies, proxy)
|
||||
proxyTags = append(proxyTags, obMap["tag"].(string))
|
||||
}
|
||||
|
||||
var proxyGroups []map[string]interface{}
|
||||
err := yaml.Unmarshal([]byte(ProxyGroups), &proxyGroups)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
}
|
||||
|
||||
proxyGroups[1]["proxies"] = proxyTags
|
||||
proxyGroups[0]["proxies"] = append([]string{proxyGroups[1]["name"].(string)}, proxyTags...)
|
||||
|
||||
output := map[string]interface{}{
|
||||
"proxies": proxies,
|
||||
"proxy-groups": proxyGroups,
|
||||
}
|
||||
|
||||
return yaml.Marshal(output)
|
||||
}
|
||||
+18
-8
@@ -46,17 +46,17 @@ type JsonService struct {
|
||||
LinkService
|
||||
}
|
||||
|
||||
func (j *JsonService) GetJson(subId string, format string) (*string, error) {
|
||||
func (j *JsonService) GetJson(subId string, format string) (*string, []string, error) {
|
||||
var jsonConfig map[string]interface{}
|
||||
|
||||
client, inDatas, err := j.getData(subId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
outbounds, outTags, err := j.getOutbounds(client.Config, inDatas)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
links := j.LinkService.GetLinks(&client.Links, "external", "")
|
||||
@@ -72,7 +72,7 @@ func (j *JsonService) GetJson(subId string, format string) (*string, error) {
|
||||
|
||||
err = json.Unmarshal([]byte(defaultJson), &jsonConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
jsonConfig["outbounds"] = outbounds
|
||||
@@ -82,7 +82,11 @@ func (j *JsonService) GetJson(subId string, format string) (*string, error) {
|
||||
|
||||
result, _ := json.MarshalIndent(jsonConfig, "", " ")
|
||||
resultStr := string(result)
|
||||
return &resultStr, nil
|
||||
|
||||
updateInterval, _ := j.SettingService.GetSubUpdates()
|
||||
headers := util.GetHeaders(client, updateInterval)
|
||||
|
||||
return &resultStr, headers, nil
|
||||
}
|
||||
|
||||
func (j *JsonService) getData(subId string) (*model.Client, []*model.Inbound, error) {
|
||||
@@ -211,7 +215,7 @@ func (j *JsonService) addDefaultOutbounds(outbounds *[]map[string]interface{}, o
|
||||
}
|
||||
|
||||
func (j *JsonService) addOthers(jsonConfig *map[string]interface{}) error {
|
||||
rules := []interface{}{
|
||||
rules_start := []interface{}{
|
||||
map[string]interface{}{
|
||||
"action": "sniff",
|
||||
},
|
||||
@@ -220,6 +224,8 @@ func (j *JsonService) addOthers(jsonConfig *map[string]interface{}) error {
|
||||
"action": "route",
|
||||
"outbound": "direct",
|
||||
},
|
||||
}
|
||||
rules_end := []interface{}{
|
||||
map[string]interface{}{
|
||||
"clash_mode": "Global",
|
||||
"action": "route",
|
||||
@@ -229,7 +235,7 @@ func (j *JsonService) addOthers(jsonConfig *map[string]interface{}) error {
|
||||
route := map[string]interface{}{
|
||||
"auto_detect_interface": true,
|
||||
"final": "proxy",
|
||||
"rules": rules,
|
||||
"rules": rules_start,
|
||||
}
|
||||
|
||||
othersStr, err := j.SettingService.GetSubJsonExt()
|
||||
@@ -261,7 +267,11 @@ func (j *JsonService) addOthers(jsonConfig *map[string]interface{}) error {
|
||||
route["rule_set"] = othersJson["rule_set"]
|
||||
}
|
||||
if settingRules, ok := othersJson["rules"].([]interface{}); ok {
|
||||
route["rules"] = append(rules, settingRules...)
|
||||
rules := append(rules_start, settingRules...)
|
||||
route["rules"] = append(rules, rules_end...)
|
||||
}
|
||||
if defaultDomainResolver, ok := othersJson["default_domain_resolver"].(string); ok {
|
||||
route["default_domain_resolver"] = defaultDomainResolver
|
||||
}
|
||||
(*jsonConfig)["route"] = route
|
||||
|
||||
|
||||
+19
-12
@@ -11,6 +11,7 @@ type SubHandler struct {
|
||||
service.SettingService
|
||||
SubService
|
||||
JsonService
|
||||
ClashService
|
||||
}
|
||||
|
||||
func NewSubHandler(g *gin.RouterGroup) {
|
||||
@@ -23,29 +24,35 @@ func (s *SubHandler) initRouter(g *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
func (s *SubHandler) subs(c *gin.Context) {
|
||||
var headers []string
|
||||
var result *string
|
||||
var err error
|
||||
subId := c.Param("subid")
|
||||
format, isFormat := c.GetQuery("format")
|
||||
if isFormat {
|
||||
result, err := s.JsonService.GetJson(subId, format)
|
||||
switch format {
|
||||
case "json":
|
||||
result, headers, err = s.JsonService.GetJson(subId, format)
|
||||
case "clash":
|
||||
result, headers, err = s.ClashService.GetClash(subId)
|
||||
}
|
||||
if err != nil || result == nil {
|
||||
logger.Error(err)
|
||||
c.String(400, "Error!")
|
||||
} else {
|
||||
c.String(200, *result)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
result, headers, err := s.SubService.GetSubs(subId)
|
||||
result, headers, err = s.SubService.GetSubs(subId)
|
||||
if err != nil || result == nil {
|
||||
logger.Error(err)
|
||||
c.String(400, "Error!")
|
||||
} else {
|
||||
|
||||
// Add headers
|
||||
c.Writer.Header().Set("Subscription-Userinfo", headers[0])
|
||||
c.Writer.Header().Set("Profile-Update-Interval", headers[1])
|
||||
c.Writer.Header().Set("Profile-Title", headers[2])
|
||||
|
||||
c.String(200, *result)
|
||||
return
|
||||
}
|
||||
}
|
||||
// Add headers
|
||||
c.Writer.Header().Set("Subscription-Userinfo", headers[0])
|
||||
c.Writer.Header().Set("Profile-Update-Interval", headers[1])
|
||||
c.Writer.Header().Set("Profile-Title", headers[2])
|
||||
|
||||
c.String(200, *result)
|
||||
}
|
||||
|
||||
+2
-4
@@ -6,6 +6,7 @@ import (
|
||||
"s-ui/database"
|
||||
"s-ui/database/model"
|
||||
"s-ui/service"
|
||||
"s-ui/util"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -34,11 +35,8 @@ func (s *SubService) GetSubs(subId string) (*string, []string, error) {
|
||||
linksArray := s.LinkService.GetLinks(&client.Links, "all", clientInfo)
|
||||
result := strings.Join(linksArray, "\n")
|
||||
|
||||
var headers []string
|
||||
updateInterval, _ := s.SettingService.GetSubUpdates()
|
||||
headers = append(headers, fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", client.Up, client.Down, client.Volume, client.Expiry))
|
||||
headers = append(headers, fmt.Sprintf("%d", updateInterval))
|
||||
headers = append(headers, subId)
|
||||
headers := util.GetHeaders(client, updateInterval)
|
||||
|
||||
subEncode, _ := s.SettingService.GetSubEncode()
|
||||
if subEncode {
|
||||
|
||||
+37
-1
@@ -10,7 +10,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var InboundTypeWithLink = []string{"shadowsocks", "naive", "hysteria", "hysteria2", "tuic", "vless", "trojan", "vmess"}
|
||||
var InboundTypeWithLink = []string{"shadowsocks", "naive", "hysteria", "hysteria2", "anytls", "tuic", "vless", "trojan", "vmess"}
|
||||
|
||||
func LinkGenerator(clientConfig json.RawMessage, i *model.Inbound, hostname string) []string {
|
||||
inbound, err := i.MarshalFull()
|
||||
@@ -73,6 +73,8 @@ func LinkGenerator(clientConfig json.RawMessage, i *model.Inbound, hostname stri
|
||||
return tuicLink(userConfig["tuic"], *inbound, Addrs)
|
||||
case "vless":
|
||||
return vlessLink(userConfig["vless"], *inbound, Addrs)
|
||||
case "anytls":
|
||||
return anytlsLink(userConfig["anytls"], Addrs)
|
||||
case "trojan":
|
||||
return trojanLink(userConfig["trojan"], *inbound, Addrs)
|
||||
case "vmess":
|
||||
@@ -280,6 +282,40 @@ func hysteria2Link(
|
||||
return links
|
||||
}
|
||||
|
||||
func anytlsLink(
|
||||
userConfig map[string]interface{},
|
||||
addrs []map[string]interface{}) []string {
|
||||
|
||||
password, _ := userConfig["password"].(string)
|
||||
baseUri := fmt.Sprintf("%s%s@", "anytls://", password)
|
||||
var links []string
|
||||
|
||||
for _, addr := range addrs {
|
||||
params := map[string]string{}
|
||||
if tls, ok := addr["tls"].(map[string]interface{}); ok {
|
||||
if sni, ok := tls["server_name"].(string); ok {
|
||||
params["sni"] = sni
|
||||
}
|
||||
if alpn, ok := tls["alpn"].([]interface{}); ok {
|
||||
alpnList := make([]string, len(alpn))
|
||||
for i, v := range alpn {
|
||||
alpnList[i] = v.(string)
|
||||
}
|
||||
params["alpn"] = strings.Join(alpnList, ",")
|
||||
}
|
||||
if insecure, ok := tls["insecure"].(bool); ok && insecure {
|
||||
params["insecure"] = "1"
|
||||
}
|
||||
}
|
||||
|
||||
port, _ := addr["server_port"].(float64)
|
||||
uri := fmt.Sprintf("%s%s:%d", baseUri, addr["server"].(string), uint(port))
|
||||
links = append(links, addParams(uri, params, addr["remark"].(string)))
|
||||
}
|
||||
|
||||
return links
|
||||
}
|
||||
|
||||
func tuicLink(
|
||||
userConfig map[string]interface{},
|
||||
inbound map[string]interface{},
|
||||
|
||||
@@ -24,6 +24,8 @@ func GetOutbound(uri string, i int) (*map[string]interface{}, string, error) {
|
||||
return hy(u, i)
|
||||
case "hy2", "hysteria2":
|
||||
return hy2(u, i)
|
||||
case "anytls":
|
||||
return anytls(u, i)
|
||||
case "tuic":
|
||||
return tuic(u, i)
|
||||
case "ss", "shadowsocks":
|
||||
@@ -293,6 +295,42 @@ func hy2(u *url.URL, i int) (*map[string]interface{}, string, error) {
|
||||
return &hy2, tag, nil
|
||||
}
|
||||
|
||||
func anytls(u *url.URL, i int) (*map[string]interface{}, string, error) {
|
||||
query, _ := url.ParseQuery(u.RawQuery)
|
||||
host, portStr, _ := net.SplitHostPort(u.Host)
|
||||
port := 443
|
||||
if len(portStr) > 0 {
|
||||
port, _ = strconv.Atoi(portStr)
|
||||
}
|
||||
|
||||
tls := map[string]interface{}{
|
||||
"enabled": true,
|
||||
"server_name": query.Get("sni"),
|
||||
}
|
||||
alpn := query.Get("alpn")
|
||||
insecure := query.Get("insecure")
|
||||
if len(alpn) > 0 {
|
||||
tls["alpn"] = strings.Split(alpn, ",")
|
||||
}
|
||||
if insecure == "1" || insecure == "true" {
|
||||
tls["insecure"] = true
|
||||
}
|
||||
|
||||
tag := u.Fragment
|
||||
if i > 0 {
|
||||
tag = fmt.Sprintf("%d.%s", i, u.Fragment)
|
||||
}
|
||||
anytls := map[string]interface{}{
|
||||
"type": "anytls",
|
||||
"tag": tag,
|
||||
"server": host,
|
||||
"server_port": port,
|
||||
"password": u.User.Username(),
|
||||
"tls": tls,
|
||||
}
|
||||
return &anytls, tag, nil
|
||||
}
|
||||
|
||||
func tuic(u *url.URL, i int) (*map[string]interface{}, string, error) {
|
||||
query, _ := url.ParseQuery(u.RawQuery)
|
||||
host, portStr, _ := net.SplitHostPort(u.Host)
|
||||
|
||||
@@ -209,6 +209,7 @@ func trojanOut(out *map[string]interface{}, inbound map[string]interface{}) {
|
||||
}
|
||||
|
||||
func vmessOut(out *map[string]interface{}, inbound map[string]interface{}) {
|
||||
(*out)["alter_id"] = 0
|
||||
delete(*out, "transport")
|
||||
if transport, ok := inbound["transport"]; ok {
|
||||
(*out)["transport"] = transport
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"s-ui/database/model"
|
||||
)
|
||||
|
||||
func GetHeaders(client *model.Client, updateInterval int) []string {
|
||||
var headers []string
|
||||
headers = append(headers, fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", client.Up, client.Down, client.Volume, client.Expiry))
|
||||
headers = append(headers, fmt.Sprintf("%d", updateInterval))
|
||||
headers = append(headers, client.Name)
|
||||
return headers
|
||||
}
|
||||
Reference in New Issue
Block a user