Skip to content

feat(SurgeMac): expose mihomoMerge / mihomoMergeName via URL query#588

Merged
xream merged 1 commit into
sub-store-org:masterfrom
hhh2210:feat-expose-mihomo-merge-url-param
May 15, 2026
Merged

feat(SurgeMac): expose mihomoMerge / mihomoMergeName via URL query#588
xream merged 1 commit into
sub-store-org:masterfrom
hhh2210:feat-expose-mihomo-merge-url-param

Conversation

@hhh2210
Copy link
Copy Markdown
Contributor

@hhh2210 hhh2210 commented May 15, 2026

Motivation

producers/surgemac.js already implements a merge mode (surgemac.js#L119-L160) that emits:

  • N × socks5,127.0.0.1:<port> Surge proxy entries (one per node), and
  • 1 × shared external,exec=mihomo,args=-config <merged-config> entry with N inbound SOCKS5 listeners and N internal proxies[]

This is exactly the right shape when a subscription contains many nodes whose protocol Surge does not natively support (e.g. VLESS-Reality with xtls-rprx-vision) — Surge needs only one mihomo subprocess, regardless of node count.

However, the only way to enable it today is to add a JS script operator that sets _merge = true on every proxy (documented as comment #24 in scripts/demo.js). Many users don't know this exists.

Real-world impact observed

On a CERNET / 校园网 with native IPv6, where most overseas VPS IPs are reachable only intermittently:

  • Subscription with ~53 VLESS-Reality nodes → Surge spawned 53 separate mihomo processes
  • Each process attempted its own outbound TCP to *.inetsnode.de:<port>
  • Surge's enhanced mode intercepted those outbound flows and routed them through the External Bypass rule → DIRECT
  • DIRECT TCP from CERNET to overseas VPS IPs timed out at ~8s each
  • 53 simultaneous failing connections (plus 53 separate DoH lookups against doh.pub) saturated the outbound SYN queue and Surge's flow-tracking path
  • Unrelated foreground traffic (qidian, xiaohongshu, etc.) became visibly laggy

Confirmed via surge-cli --raw dump active + dump recent: every slow connection had processPath=mihomo and rule=External Bypass. Killing all mihomo processes immediately restored normal performance.

The merge mode would have eliminated this entirely (1 process instead of 53), but requires the user to either:

  1. Write a custom script operator (high friction), or
  2. Manually edit per-proxy _merge fields after the producer runs (loses on next sync)

What this PR does

Adds two URL query parameters that flow through to produceOpts:

Param Type Behavior
mihomoMerge 'true' | '1' | '' Enables shared-mihomo merge mode (only when target=SurgeMac)
mihomoMergeName string Optional name override for the merged external entry (defaults to mihomo merged)

The producer code already reads opts.merge and opts.mergeName, so this PR is purely plumbing.

Diff scope

  • backend/src/restful/download.js — both downloadSubscription and downloadCollection: parse query params + pass into produceOpts. Two log lines when enabled. ~38 added lines, no removals.
  • No producer / core changes.
  • No behavior change when mihomoMerge is absent — default unchanged.

Usage

GET /download/<name>?target=SurgeMac&mihomoMerge=true
GET /download/<name>?target=SurgeMac&mihomoMerge=true&mihomoMergeName=Ients-merged

Tests / verification

  • node --check backend/src/restful/download.js passes
  • Manually traced the option from query → produceOptsProxyUtils.produce(...)surgemac.js produce(proxy, type, opts)const merge = opts?.merge || proxy._merge → existing merge branch
  • target other than SurgeMac short-circuits to false, so this can't affect Surge / Clash / sing-box / Loon / etc. outputs

Follow-ups (not in this PR)

  • Could also expose mihomoMerge on the Artifact entity for cron-sync-artifacts.js, but that's a UI/data-model change and worth a separate discussion.
  • Doc update to scripts/demo.js comment “发生错误,无法创建订阅” #24 mentioning the URL-param form — happy to add in this PR if preferred.

The producer (producers/surgemac.js) already supports a merge mode
that emits N SOCKS5 listeners + one shared mihomo external process,
avoiding the "one mihomo process per node" pattern. However the only
way to enable it today is to write a per-proxy script operator that
sets `_merge = true`.

For users on networks where many subscription nodes are unreachable
(e.g. CERNET / 校园网 to overseas VPS), the current default spawns
dozens of idle-but-stuck mihomo subprocesses, each accumulating
~8s GFW-timeout outbound attempts that can cause system-wide
slowdown.

This patch wires two new URL query parameters into `produceOpts`:

- `mihomoMerge=true|1` — enables the existing merge mode
- `mihomoMergeName=<name>` — optional name for the merged entry

Both only take effect when `target=SurgeMac` (i.e. when
`useMihomoExternal` is already on).

No behavior change for existing requests; default is unchanged.
Copilot AI review requested due to automatic review settings May 15, 2026 03:19
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds two new URL query parameters (mihomoMerge and mihomoMergeName) to the /download/... endpoints in download.js, plumbing them through produceOpts to the existing merge-mode branch in surgemac.js. The goal is to let users enable shared-mihomo-process output without writing a custom script operator.

Changes:

  • Parse mihomoMerge / mihomoMergeName from req.query in both downloadSubscription and downloadCollection, gated on target=SurgeMac.
  • Pass them through as merge / mergeName inside produceOpts.
  • Emit an info log line when merge mode is enabled.
Comments suppressed due to low confidence (1)

backend/src/restful/download.js:445

  • Same issue as in downloadSubscription: when mihomoMerge is not provided in the query string, req.query.mihomoMerge ?? '' yields '', which matches the ['true', '1', ''] list, so merge mode is silently enabled for every target=SurgeMac collection download. This should only be enabled when the user explicitly opts in (e.g., value is 'true' or '1').
    const mihomoMerge =
        useMihomoExternal &&
        ['true', '1', ''].includes(String(req.query.mihomoMerge ?? ''));

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

// 等价于在脚本里对每个节点设置 _merge=true,避免每个节点 spawn 一个 mihomo 进程
const mihomoMerge =
useMihomoExternal &&
['true', '1', ''].includes(String(req.query.mihomoMerge ?? ''));
@xream
Copy link
Copy Markdown
Member

xream commented May 15, 2026

Thank you for your PR. However, there are some issues with the code and it doesn't quite match the previous style. I will reimplement it based on your requirements and give you credit.

@xream xream merged commit b485234 into sub-store-org:master May 15, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants