diff --git a/internal/kernel/singbox/config.go b/internal/kernel/singbox/config.go index 61d68ef..365f2b7 100644 --- a/internal/kernel/singbox/config.go +++ b/internal/kernel/singbox/config.go @@ -460,6 +460,10 @@ func buildVMess(base M, nc *panel.NodeConfig, users []panel.User, certFile, keyF } func buildVLESS(base M, nc *panel.NodeConfig, users []panel.User, certFile, keyFile string) M { + if nc.Decryption != "" && nc.Decryption != "none" { + nlog.Core().Warn("sing-box does not support VLESS encryption (decryption), use xray kernel for this feature") + } + base["type"] = "vless" userList := make([]M, 0, len(users)) diff --git a/internal/kernel/xray/config.go b/internal/kernel/xray/config.go index 0c0ad04..6f67c1b 100644 --- a/internal/kernel/xray/config.go +++ b/internal/kernel/xray/config.go @@ -72,7 +72,7 @@ func buildConfig(kcfg config.KernelConfig, nc *panel.NodeConfig, users []panel.U } else { nlog.Core().Warn("xray: unsupported protocol, no inbound configured — node will not accept connections", "protocol", nc.Protocol, - "supported", "vmess, vless, trojan, shadowsocks, socks, http") + "supported", "vmess, vless, trojan, shadowsocks, hysteria, socks, http") } // Merge panel routes and static config routes @@ -225,6 +225,8 @@ func buildInbound(nc *panel.NodeConfig, users []panel.User, certFile, keyFile st return buildSocks(base, users) case "http": return buildHTTP(base, nc, users, certFile, keyFile) + case "hysteria": + return buildHysteria(base, nc, users, certFile, keyFile) default: return nil } @@ -263,9 +265,13 @@ func buildVLESS(base M, nc *panel.NodeConfig, users []panel.User, certFile, keyF } clients = append(clients, client) } + decryption := "none" + if nc.Decryption != "" { + decryption = nc.Decryption + } base["settings"] = M{ "clients": clients, - "decryption": "none", + "decryption": decryption, } applyStreamSettings(base, nc, certFile, keyFile) @@ -392,6 +398,66 @@ func buildHTTP(base M, nc *panel.NodeConfig, users []panel.User, certFile, keyFi return base } +// buildHysteria creates a Hysteria v2 inbound for xray-core. +func buildHysteria(base M, nc *panel.NodeConfig, users []panel.User, certFile, keyFile string) M { + if nc.Version != 2 { + nlog.Core().Warn("xray: only supports hysteria v2, skipping v1 inbound") + return nil + } + + clients := make([]M, 0, len(users)) + for _, u := range users { + clients = append(clients, M{ + "auth": u.UUID, + "email": userEmail(u.ID), + }) + } + base["settings"] = M{"clients": clients} + + ss := M{ + "network": "hysteria", + "hysteriaSettings": M{ + "version": nc.Version, + "udpIdleTimeout": 60, + }, + } + + if nc.UpMbps > 0 || nc.DownMbps > 0 { + quicParams := M{} + if nc.UpMbps > 0 { + quicParams["brutalUp"] = fmt.Sprintf("%d mbps", nc.UpMbps) + } + if nc.DownMbps > 0 { + quicParams["brutalDown"] = fmt.Sprintf("%d mbps", nc.DownMbps) + } + ss["finalMask"] = M{ + "quicParams": quicParams, + } + } + + if certFile != "" && keyFile != "" { + ss["security"] = "tls" + ss["tlsSettings"] = M{ + "certificates": []M{ + { + "certificateFile": certFile, + "keyFile": keyFile, + }, + }, + "alpn": []string{"h3"}, + } + } else { + nlog.Core().Warn("hysteria requires TLS certificate files; configure cert_mode (self, file, http, dns, or content)") + } + + if nc.GetProxyProtocol() { + ss["sockopt"] = M{"acceptProxyProtocol": true} + } + + base["streamSettings"] = ss + return base +} + func applyStreamSettings(base M, nc *panel.NodeConfig, certFile, keyFile string) { ss := M{} diff --git a/internal/kernel/xray/xray.go b/internal/kernel/xray/xray.go index defefa7..d49166c 100644 --- a/internal/kernel/xray/xray.go +++ b/internal/kernel/xray/xray.go @@ -85,7 +85,7 @@ func (x *Xray) Name() string { return "xray" } func (x *Xray) Protocols() []string { return []string{ "vmess", "vless", "trojan", "shadowsocks", - "socks", "http", "dokodemo-door", + "hysteria", "socks", "http", "dokodemo-door", } } diff --git a/internal/panel/types.go b/internal/panel/types.go index ac7ade1..c9e613e 100644 --- a/internal/panel/types.go +++ b/internal/panel/types.go @@ -92,6 +92,7 @@ type NodeConfig struct { // VMess / VLESS TLS int `json:"tls,omitempty"` Flow string `json:"flow,omitempty"` + Decryption string `json:"decryption,omitempty"` TLSSettings map[string]interface{} `json:"tls_settings,omitempty"` // Trojan diff --git a/internal/panel/ws.go b/internal/panel/ws.go index 7bea8a0..ac66d34 100644 --- a/internal/panel/ws.go +++ b/internal/panel/ws.go @@ -192,7 +192,7 @@ func (w *WSClient) connect(ctx context.Context) error { q.Set("node_id", strconv.Itoa(w.nodeID)) u.RawQuery = q.Encode() - nlog.Core().Debug("ws connecting", "url", u.Host) + nlog.Core().Debug("ws connecting", "url", u.String()) dialer := websocket.Dialer{ HandshakeTimeout: w.cfg.HandshakeTimeout,