package sboxrun import ( "encoding/json" "fmt" ) // Config captures the user-visible proxy settings + which processes // to route through it. Everything else (TUN interface, log level, // Clash API endpoint) is hard-coded sensible defaults. type Config struct { ProxyHost string // upstream SOCKS5 host ProxyPort int // upstream SOCKS5 port UseAuth bool Login string Password string TargetProcs []string // exe names to route via upstream (e.g. ["Discord.exe"]) ClashAPIPort int // 0 → 9090 default LogLevel string // "info" | "debug" | "warn" — empty → "info" LogPath string // absolute path for sing-box log output (empty = sing-box stdout, lost when admin-detached) } // BuildSingBoxConfig generates the sing-box JSON config string. It's // a minimal config: TUN inbound (with auto_route + WFP per-process // rule), SOCKS5 outbound to upstream, direct outbound for everything // else, and a route rule that sends TargetProcs through the SOCKS5. // // Clash API on 127.0.0.1:9090 (or ClashAPIPort) lets the GUI poll // connection stats live. func BuildSingBoxConfig(c Config) (string, error) { if c.ProxyHost == "" || c.ProxyPort == 0 { return "", fmt.Errorf("ProxyHost and ProxyPort are required") } if len(c.TargetProcs) == 0 { return "", fmt.Errorf("at least one target process is required") } logLevel := c.LogLevel if logLevel == "" { logLevel = "info" } clashPort := c.ClashAPIPort if clashPort == 0 { clashPort = 9090 } upstream := map[string]any{ "type": "socks", "tag": "upstream", "server": c.ProxyHost, "server_port": c.ProxyPort, "version": "5", "udp_over_tcp": false, } if c.UseAuth { upstream["username"] = c.Login upstream["password"] = c.Password } logBlock := map[string]any{ "level": logLevel, "timestamp": true, } if c.LogPath != "" { logBlock["output"] = c.LogPath } cfg := map[string]any{ "log": logBlock, "inbounds": []any{ map[string]any{ "type": "tun", "tag": "tun-in", "interface_name": "drover-tun", "address": []string{"172.18.0.1/30"}, "auto_route": true, "strict_route": false, "stack": "system", "sniff": true, }, }, "outbounds": []any{ upstream, map[string]any{"type": "direct", "tag": "direct"}, }, "route": map[string]any{ "auto_detect_interface": true, "final": "direct", "rules": []any{ // Route only the target processes via upstream map[string]any{ "process_name": c.TargetProcs, "outbound": "upstream", }, }, }, "experimental": map[string]any{ "clash_api": map[string]any{ "external_controller": fmt.Sprintf("127.0.0.1:%d", clashPort), }, }, } out, err := json.MarshalIndent(cfg, "", " ") if err != nil { return "", err } return string(out), nil }