142 lines
3.8 KiB
Go
142 lines
3.8 KiB
Go
package hysteria2
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"vpnclient/internal/config"
|
|
)
|
|
|
|
func writeRuntimeConfig(path string, profile config.Profile) error {
|
|
ep, err := ParseEndpoint(profile.Proxy)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var insecureOverride *bool
|
|
if profile.Hy2Insecure {
|
|
v := true
|
|
insecureOverride = &v
|
|
}
|
|
ep.ApplyOverrides(profile.Hy2SNI, profile.Hy2Obfs, profile.Hy2ObfsPassword, profile.Hy2PinSHA256, insecureOverride)
|
|
|
|
socks := "127.0.0.1:1080"
|
|
httpListen := "127.0.0.1:1081"
|
|
if hp, ok := profile.SOCKSListenHostPort(); ok {
|
|
socks = hp
|
|
}
|
|
if hp, ok := profile.HTTPListenHostPort(); ok {
|
|
httpListen = hp
|
|
}
|
|
|
|
var b strings.Builder
|
|
b.WriteString("# generated by Navis — do not edit\n")
|
|
|
|
// Expanded form so congestion/obfs/tls can coexist with auth.
|
|
b.WriteString("server: " + yamlQuote(ep.Server) + "\n")
|
|
if ep.Auth != "" {
|
|
b.WriteString("auth: " + yamlQuote(ep.Auth) + "\n")
|
|
}
|
|
|
|
if ep.SNI != "" || ep.Insecure || ep.PinSHA256 != "" {
|
|
b.WriteString("tls:\n")
|
|
if ep.SNI != "" {
|
|
b.WriteString(" sni: " + yamlQuote(ep.SNI) + "\n")
|
|
}
|
|
if ep.Insecure {
|
|
b.WriteString(" insecure: true\n")
|
|
}
|
|
if ep.PinSHA256 != "" {
|
|
b.WriteString(" pinSHA256: " + yamlQuote(ep.PinSHA256) + "\n")
|
|
}
|
|
}
|
|
|
|
obfs := strings.ToLower(strings.TrimSpace(ep.Obfs))
|
|
if obfs == "" {
|
|
obfs = strings.ToLower(strings.TrimSpace(profile.Hy2Obfs))
|
|
}
|
|
obfsPass := ep.ObfsPassword
|
|
if obfsPass == "" {
|
|
obfsPass = profile.Hy2ObfsPassword
|
|
}
|
|
switch obfs {
|
|
case "salamander":
|
|
if obfsPass == "" {
|
|
return fmt.Errorf("hysteria2: salamander требует пароль (obfs-password)")
|
|
}
|
|
b.WriteString("obfs:\n")
|
|
b.WriteString(" type: salamander\n")
|
|
b.WriteString(" salamander:\n")
|
|
b.WriteString(" password: " + yamlQuote(obfsPass) + "\n")
|
|
case "gecko":
|
|
if obfsPass == "" {
|
|
return fmt.Errorf("hysteria2: gecko требует пароль (obfs-password)")
|
|
}
|
|
b.WriteString("obfs:\n")
|
|
b.WriteString(" type: gecko\n")
|
|
b.WriteString(" gecko:\n")
|
|
b.WriteString(" password: " + yamlQuote(obfsPass) + "\n")
|
|
}
|
|
|
|
// Congestion: BBR (default) or reno. Bandwidth enables Brutal instead.
|
|
up := ParseBandwidthMbps(profile.Hy2BandwidthUp)
|
|
down := ParseBandwidthMbps(profile.Hy2BandwidthDown)
|
|
cong := strings.ToLower(strings.TrimSpace(profile.Hy2Congestion))
|
|
if cong == "" {
|
|
cong = "bbr"
|
|
}
|
|
bbrProfile := strings.ToLower(strings.TrimSpace(profile.Hy2BBRProfile))
|
|
if bbrProfile == "" {
|
|
bbrProfile = "standard"
|
|
}
|
|
|
|
if up != "" || down != "" {
|
|
b.WriteString("bandwidth:\n")
|
|
if up != "" {
|
|
b.WriteString(" up: " + yamlQuote(up) + "\n")
|
|
}
|
|
if down != "" {
|
|
b.WriteString(" down: " + yamlQuote(down) + "\n")
|
|
}
|
|
} else {
|
|
// No Brutal → use BBR/reno
|
|
b.WriteString("congestion:\n")
|
|
b.WriteString(" type: " + yamlQuote(cong) + "\n")
|
|
if cong == "bbr" {
|
|
b.WriteString(" bbrProfile: " + yamlQuote(bbrProfile) + "\n")
|
|
}
|
|
}
|
|
|
|
if hop := strings.TrimSpace(profile.Hy2HopInterval); hop != "" {
|
|
b.WriteString("transport:\n")
|
|
b.WriteString(" type: udp\n")
|
|
b.WriteString(" udp:\n")
|
|
b.WriteString(" hopInterval: " + yamlQuote(hop) + "\n")
|
|
}
|
|
|
|
if profile.Hy2FastOpen {
|
|
b.WriteString("fastOpen: true\n")
|
|
}
|
|
if profile.Hy2Lazy {
|
|
b.WriteString("lazy: true\n")
|
|
}
|
|
|
|
// "Masquerade extras" on client = look like normal HTTPS via SNI already above;
|
|
// optional ALPN hint kept for compatibility with some panels.
|
|
if alpn := strings.TrimSpace(profile.Hy2ALPN); alpn != "" {
|
|
// Official client uses TLS defaults; keep as comment for operators.
|
|
b.WriteString("# alpn-hint: " + yamlQuote(alpn) + "\n")
|
|
}
|
|
|
|
b.WriteString("socks5:\n")
|
|
b.WriteString(" listen: " + yamlQuote(socks) + "\n")
|
|
b.WriteString("http:\n")
|
|
b.WriteString(" listen: " + yamlQuote(httpListen) + "\n")
|
|
|
|
return writeFile(path, b.String())
|
|
}
|
|
|
|
func writeFile(path, content string) error {
|
|
return os.WriteFile(path, []byte(content), 0o600)
|
|
}
|