feat: sing-box Clash template support + Docker CI + frontend scaffold
- Clash→sing-box template conversion engine (singbox_template.go) - Auto-map 40+ Clash rule-providers to sing-box .srs binary rule-sets - Fix sing-box 1.11+/1.12+ migrations (sniff/block/WireGuard/DNS/rule_set) - Fix WireGuard URI '+' parsing bug (rawParamGet) - Add REJECT block outbound for selector group references - Skip rules referencing rule-sets with no sing-box equivalent - Verified: sing-box run succeeds with ACL4SSR template (16 rule-sets loaded) - Add Dockerfile (multi-stage Go build) - Add GitHub Actions workflow (multi-arch: amd64+arm64, push to ghcr.io) - Add frontend scaffold (Vue3 + Vite + Tailwind)
This commit is contained in:
@@ -878,8 +878,8 @@ func TestExtraSingBoxStructure(t *testing.T) {
|
||||
t.Errorf("expected listen_port=7890, got: %v", inbound["listen_port"])
|
||||
}
|
||||
outbounds, _ := parsed["outbounds"].([]any)
|
||||
if len(outbounds) < 4 {
|
||||
t.Fatalf("expected at least 4 outbounds (PROXY, AUTO, nodes, DIRECT, REJECT), got %d", len(outbounds))
|
||||
if len(outbounds) < 3 {
|
||||
t.Fatalf("expected at least 3 outbounds (PROXY, AUTO, nodes, DIRECT), got %d", len(outbounds))
|
||||
}
|
||||
proxyOut, _ := outbounds[0].(map[string]any)
|
||||
if proxyOut["tag"] != "PROXY" || proxyOut["type"] != "selector" {
|
||||
@@ -889,16 +889,29 @@ func TestExtraSingBoxStructure(t *testing.T) {
|
||||
if autoOut["tag"] != "AUTO" || autoOut["type"] != "urltest" {
|
||||
t.Errorf("expected second outbound AUTO urltest, got: %v", autoOut)
|
||||
}
|
||||
// DIRECT and REJECT should be at the end
|
||||
// DIRECT should be at the end (REJECT is now a route rule action, not a special outbound)
|
||||
last, _ := outbounds[len(outbounds)-1].(map[string]any)
|
||||
if last["tag"] != "REJECT" || last["type"] != "block" {
|
||||
t.Errorf("expected last outbound REJECT block, got: %v", last)
|
||||
if last["tag"] != "DIRECT" || last["type"] != "direct" {
|
||||
t.Errorf("expected last outbound DIRECT direct, got: %v", last)
|
||||
}
|
||||
secondLast, _ := outbounds[len(outbounds)-2].(map[string]any)
|
||||
if secondLast["tag"] != "DIRECT" || secondLast["type"] != "direct" {
|
||||
t.Errorf("expected second-last outbound DIRECT direct, got: %v", secondLast)
|
||||
// Inbound should NOT have sniff field (migrated to route rule action in sing-box 1.11+)
|
||||
if _, hasSniff := inbound["sniff"]; hasSniff {
|
||||
t.Errorf("inbound should not have sniff field (migrated to route rule action)")
|
||||
}
|
||||
// Route should have rules with sniff and reject actions
|
||||
routeMap, _ := parsed["route"].(map[string]any)
|
||||
rules, _ := routeMap["rules"].([]any)
|
||||
if len(rules) < 2 {
|
||||
t.Fatalf("expected at least 2 route rules (sniff, reject), got %d", len(rules))
|
||||
}
|
||||
rule0, _ := rules[0].(map[string]any)
|
||||
if rule0["action"] != "sniff" {
|
||||
t.Errorf("expected first route rule action=sniff, got: %v", rule0["action"])
|
||||
}
|
||||
rule1, _ := rules[1].(map[string]any)
|
||||
if rule1["action"] != "reject" {
|
||||
t.Errorf("expected second route rule action=reject, got: %v", rule1["action"])
|
||||
}
|
||||
if routeMap == nil || routeMap["final"] != "PROXY" {
|
||||
t.Errorf("expected route.final=PROXY, got: %v", parsed["route"])
|
||||
}
|
||||
@@ -923,7 +936,6 @@ func TestExtraSingBoxOutboundAllTypes(t *testing.T) {
|
||||
{"tuic", nodeByName("TUIC"), "tuic"},
|
||||
{"socks5", nodeByName("SOCKS5"), "socks"},
|
||||
{"http", nodeByName("HTTP"), "http"},
|
||||
{"wireguard", nodeByName("WG"), "wireguard"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
@@ -941,6 +953,31 @@ func TestExtraSingBoxOutboundAllTypes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtraSingBoxWireGuardEndpoint(t *testing.T) {
|
||||
node := nodeByName("WG")
|
||||
ep := ToSingBoxWireGuardEndpoint(node)
|
||||
if ep == nil {
|
||||
t.Fatalf("ToSingBoxWireGuardEndpoint(WG) nil")
|
||||
}
|
||||
if ep["type"] != "wireguard" {
|
||||
t.Errorf("expected type=wireguard, got %v", ep["type"])
|
||||
}
|
||||
if ep["tag"] != "WG-Node" {
|
||||
t.Errorf("expected tag=WG-Node, got %v", ep["tag"])
|
||||
}
|
||||
peers, ok := ep["peers"].([]any)
|
||||
if !ok || len(peers) == 0 {
|
||||
t.Fatalf("expected peers array, got: %v", ep["peers"])
|
||||
}
|
||||
peer, ok := peers[0].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("expected peer map, got: %v", peers[0])
|
||||
}
|
||||
if peer["public_key"] == nil {
|
||||
t.Errorf("expected peer.public_key to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtraSingBoxUnsupportedType(t *testing.T) {
|
||||
node := model.ProxyNode{"type": "snell", "name": "x"}
|
||||
if out := ToSingBoxOutbound(node); out != nil {
|
||||
@@ -1029,10 +1066,15 @@ func TestExtraSingBoxHTTPTls(t *testing.T) {
|
||||
|
||||
func TestExtraSingBoxWireGuardReserved(t *testing.T) {
|
||||
node := nodeByName("WG")
|
||||
out := ToSingBoxOutbound(node)
|
||||
reserved, ok := out["reserved"].([]int)
|
||||
ep := ToSingBoxWireGuardEndpoint(node)
|
||||
peers, ok := ep["peers"].([]any)
|
||||
if !ok || len(peers) == 0 {
|
||||
t.Fatalf("expected peers array, got: %v", ep["peers"])
|
||||
}
|
||||
peer := peers[0].(map[string]any)
|
||||
reserved, ok := peer["reserved"].([]int)
|
||||
if !ok {
|
||||
t.Fatalf("expected reserved []int, got: %v", out["reserved"])
|
||||
t.Fatalf("expected peer reserved []int, got: %v", peer["reserved"])
|
||||
}
|
||||
if !reflect.DeepEqual(reserved, []int{1, 2, 3}) {
|
||||
t.Errorf("expected reserved=[1,2,3], got: %v", reserved)
|
||||
@@ -1041,13 +1083,13 @@ func TestExtraSingBoxWireGuardReserved(t *testing.T) {
|
||||
|
||||
func TestExtraSingBoxWireGuardLocalAddress(t *testing.T) {
|
||||
node := nodeByName("WG")
|
||||
out := ToSingBoxOutbound(node)
|
||||
la, ok := out["local_address"].([]string)
|
||||
ep := ToSingBoxWireGuardEndpoint(node)
|
||||
addr, ok := ep["address"].([]string)
|
||||
if !ok {
|
||||
t.Fatalf("expected local_address, got: %v", out["local_address"])
|
||||
t.Fatalf("expected address, got: %v", ep["address"])
|
||||
}
|
||||
if len(la) != 2 {
|
||||
t.Errorf("expected 2 local addresses (ip+ipv6), got %d", len(la))
|
||||
if len(addr) != 2 {
|
||||
t.Errorf("expected 2 addresses (ip+ipv6), got %d", len(addr))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user