From 8d396e63d684b14813cb978618bfccd624003497 Mon Sep 17 00:00:00 2001 From: Rogee Date: Tue, 28 Jul 2026 19:15:11 +0800 Subject: [PATCH] fix: replace sing-box with mihomo for egress probe (xhttp support) mihomo (Clash Meta) natively supports xhttp and all Clash transports. Build minimal mihomo YAML config directly from the proxy node instead of converting to sing-box JSON format. --- Dockerfile | 10 +++--- internal/handler/egress_info.go | 56 ++++++++++++++++----------------- 2 files changed, 32 insertions(+), 34 deletions(-) diff --git a/Dockerfile b/Dockerfile index d462b09..902b2f7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,11 +18,11 @@ RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /sub-store . # ─── Stage 3: Runtime ─── FROM alpine:3.21 RUN apk add --no-cache ca-certificates tzdata && \ - wget -qO /tmp/sing-box.tar.gz "https://github.com/SagerNet/sing-box/releases/download/v1.13.14/sing-box-1.13.14-linux-amd64-musl.tar.gz" && \ - tar xzf /tmp/sing-box.tar.gz -C /tmp && \ - mv /tmp/sing-box-*/sing-box /usr/local/bin/sing-box && \ - chmod +x /usr/local/bin/sing-box && \ - rm -rf /tmp/sing-box* + wget -qO /tmp/mihomo.gz "https://github.com/MetaCubeX/mihomo/releases/download/v1.19.29/mihomo-linux-amd64-v1.19.29.gz" && \ + gunzip /tmp/mihomo.gz && \ + mv /tmp/mihomo /usr/local/bin/mihomo && \ + chmod +x /usr/local/bin/mihomo && \ + rm -rf /tmp/mihomo* WORKDIR /app COPY --from=backend-builder /sub-store /app/sub-store COPY --from=frontend-builder /frontend/dist /app/frontend/dist diff --git a/internal/handler/egress_info.go b/internal/handler/egress_info.go index 42aa46c..e3c5b29 100644 --- a/internal/handler/egress_info.go +++ b/internal/handler/egress_info.go @@ -17,9 +17,9 @@ import ( "time" "github.com/gofiber/fiber/v3" + "gopkg.in/yaml.v3" "github.com/peterqiu0516/sub-store/internal/model" - "github.com/peterqiu0516/sub-store/internal/render" "github.com/peterqiu0516/sub-store/internal/util" ) @@ -134,37 +134,35 @@ func buildEgressProbeConfig(node model.ProxyNode, port int) ([]byte, error) { } probeNode["name"] = "PROXY" - outbound := render.ToSingBoxOutbound(probeNode) - if outbound == nil { - return nil, fmt.Errorf("Unsupported proxy node for egress probe") + // Build a minimal mihomo (Clash Meta) config with the probe node as the + // only proxy. mihomo natively supports xhttp and other Clash transports. + proxyYaml, err := yaml.Marshal(probeNode) + if err != nil { + return nil, fmt.Errorf("Failed to marshal proxy node: %w", err) } - doc := map[string]any{ - "log": map[string]any{"level": "warn"}, - "inbounds": []any{ - map[string]any{ - "type": "mixed", - "tag": "mixed-in", - "listen": "127.0.0.1", - "listen_port": port, - }, - }, - "outbounds": []any{ - outbound, - map[string]any{"type": "direct", "tag": "DIRECT"}, - }, - "route": map[string]any{ - "auto_detect_interface": true, - "final": "PROXY", - "rules": []any{map[string]any{"action": "sniff"}}, - }, - } - return json.Marshal(doc) + + config := fmt.Sprintf(`mixed-port: %d +allow-lan: false +mode: direct +log-level: warning +proxies: +- %s +proxy-groups: +- name: PROXY + type: select + proxies: + - PROXY +rules: +- MATCH,PROXY +`, port, strings.ReplaceAll(strings.TrimSuffix(string(proxyYaml), "\n"), "\n", "\n ")) + + return []byte(config), nil } func runEgressProbe(configData []byte, port int) (fiber.Map, error) { - singBox, err := exec.LookPath("sing-box") + mihomo, err := exec.LookPath("mihomo") if err != nil { - return nil, fmt.Errorf("sing-box executable not found") + return nil, fmt.Errorf("mihomo executable not found") } dir, err := os.MkdirTemp("", "sub-store-egress-*") if err != nil { @@ -172,14 +170,14 @@ func runEgressProbe(configData []byte, port int) (fiber.Map, error) { } defer os.RemoveAll(dir) - configPath := filepath.Join(dir, "config.json") + configPath := filepath.Join(dir, "config.yaml") if err := os.WriteFile(configPath, configData, 0o600); err != nil { return nil, err } ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second) defer cancel() - cmd := exec.CommandContext(ctx, singBox, "run", "-c", configPath) + cmd := exec.CommandContext(ctx, mihomo, "-f", configPath) var stderr bytes.Buffer cmd.Stderr = &stderr if err := cmd.Start(); err != nil {