v2.2.5
Synced upstream docmirror/dev-sidecar master (29 commits). Upstream introduced a CLI rewrite (native ds-cli binary with SEA packaging), single-instance mutex, and Linux/macOS environment-variable proxy support. All fork-specific changes (CA cert passthrough, Xray plugin, keep-alive socket fix, log.debug hot-path demotion, conditional linuxTargets, native module rebuild, CSS variable theme) were preserved. The fork's desktop-detection logic in set-system-proxy was merged with upstream's env-var support so that headless servers now write proxy env vars (previously the early return true skipped them).
Added
- CLI rewritten as native
ds-clibinary (upstream). The CLI (packages/cli/) was rewritten from a plain Node script into a native command-line toolds-cliwith Sea-of-Nodejs (SEA) packaging support. Newpackages/cli/scripts/build.jsperforms incremental builds with SHA256 checksums and parallel downloads, auto-cleans stale build products while preserving thenode-bincache, dynamically fetches the Node.js supported platform list, and displays OS type/version/arch. New.github/workflows/build-cli.ymlCI workflow builds the CLI binary. Newpackages/cli/README.mddocuments SEA packaging and cross-compilation.packages/cli/src/sea-entry.jsis the SEA entry point;packages/cli/.gitignoreignores build products exceptsea-config.json. - CLI new commands (upstream):
proxy on/off(fork worker sets system proxy immediately),plugin start/stop(persists toconfig.json, takes effect on restart),service install/uninstall(boot autostart),help(command formds-cli helponly,--help/-hremoved),status(shows running instance and plugin state),start/stop/restart. Workers split intoproxy-worker.js,plugin-worker.js,free-eye-worker.js.packages/cli/src/user_config.json5removed (no longer shipped as a static template). - CLI/GUI single-instance mutex via
proper-lockfilelong-lock (packages/core/src/modules/instance/index.js, 140 lines).packages/core/src/expose.jsexposesapi.instance. GUIbackground.jsacquires the lock onapp.whenReady()and writes instance info (type: 'gui', pid, command, startTime) torunning.json; if another instance holds the lock, the GUI quits with an error. CLI does the same onstart/proxy on/plugin start.packages/core/test/instanceTest.js(153 lines) covers acquire/release/write/cleanup. - Linux/macOS environment-variable proxy support (upstream).
packages/core/src/shell/scripts/set-system-proxy/index.jsgainedwriteProxyEnvFile/addProxyEnvToShellProfile/removeProxyEnvFromShellProfilehelpers and asetEnvparam. WhensetEnvis true, the proxy env vars (http_proxy,https_proxy,no_proxy) are written to a file and appended to the detected shell profile (~/.bashrc/~/.zshrc), independent ofgsettings— so CLI tools (curl, git, npm) on headless servers pick up the proxy even without a desktop. macOS uses the same env-var file mechanism alongsidenetworksetup. - Plugin status events (upstream).
overwallandpipplugins now fireevent.fire('status', ...)on start/close and log开启/关闭【X】代理成功.overwallgained astatus: { enabled: false }block.packages/core/src/modules/server/index.jsemitsstatus.server.enabledevents.packages/gui/src/view/pages/proxy.vuereads the new status. - CLI test suite (upstream). 60+ new Mocha test cases:
packages/cli/test/{gui,index,plugin,proxy,service,start,status}.test.jscovering proxy on/off, config persistence, shell detection, service/help/version/unknown commands. - Xray Stage3 零中断热刷新(Phase 2) (fork). Stage3 后台热刷新改为基于 Xray HandlerService gRPC API 的动态增删 outbound,不再重写 config.json + 重启 xray 进程,现有连接完全不中断。新增
packages/core/src/modules/plugin/xray/xray_api.js模块,封装xray api ado(AddOutbound,stdin 传 JSON)/xray api rmo(RemoveOutbound,按 tag)/xray api lso(ListOutbounds)三个 CLI 子进程调用(execFile+ 5s 超时)。packages/core/src/modules/plugin/xray/gen_config.js新增apiPort参数和api块生成(listen: 127.0.0.1:<apiPort>,services: [HandlerService, ObservatoryService, RoutingService]),并新增 7 个单元测试(packages/core/test/xrayGenConfig.test.js)。balancer.selector和observatory.subjectSelector从显式 tag 列表["proxy_0",...]改为前缀["proxy_"],通过 Xraystrings.HasPrefix自动包含运行时动态新增的proxy_Ntag。RemoveHandler只从 manager 的 map 删除 tag 引用、不调handler.Close(),已建立连接继续完成、新连接不再路由到该 tag;Observatorybackground()每个探测周期自动发现新 outbound 并探测。packages/core/src/modules/plugin/xray/index.js路径 B 新增 API 热刷新逻辑:维护currentLiveNodeTags(Map: fingerprint→tag)和nextProxyTagIndex计数器,先addOutbounds新节点(observatory 下个周期探测后才可选,正好支持"先 Add 后 Remove"策略),再removeOutbounds坏节点;API 调用失败时回滚 in-memory 状态并 fallback 到重启路径(Phase 1 行为)。兼容 Xray-core v26.3.27+(v26.3.27 observatory 不清理已移除 outbound 状态但无害)。73 个 core 测试全部通过无回归。 - Xray 主进程启用 metrics 端口供运行时调试 (fork). 主进程(live xray)的 3 个
genConfig调用点(冷启动 regen / fallback 重启 / 初始启动)新增metricsPort参数,生成metrics块(expvar/debug/vars)。运维人员可通过curl -s http://127.0.0.1:<metricsPort>/debug/vars | jq '.observatory'查看主进程各节点的 alive/delay/lastErrorReason,无需依赖上游未合并的xray api obs命令。metricsPort和apiPort通过event.fire('status', ...)写入running.json的app.status.plugin.xray,方便随时查看。packages/core/src/modules/instance/index.js的watchStatusEvents过滤逻辑从仅同步*.enabled扩展为同时同步plugin.xray.port/apiPort/metricsPort三个端口字段。复用已有 config 时从metrics.listen提取端口(与api.listen同逻辑)。 - 新增
doc/xray-devops.md(fork). Xray 插件开发与运维文档,面向调试运行时状态或排查 Stage3 热刷新行为的开发者/运维人员。包含:Stage3 热刷新机制(工作原理 + 日志关键词解读)、运行时调试方式(curl/debug/vars查看 observatory 节点延时 [推荐] /xray api lso/obs/bi子命令)、Xray-core v26.3.27 vs v26.7.28 版本兼容性对比表、部署后首次重启注意事项。
Changed
- Xray Stage3 热刷新路径 B 改为 API 动态增删 (fork).
packages/core/src/modules/plugin/xray/index.js的maybeRegenerateLiveConfigFromCache路径 B(热刷新,config.json 已有节点)不再调processApi.restart(stop SIGTERM + 200ms + start,中断流量),改为通过 HandlerService gRPC API 动态增删 outbound。仅在冷启动(路径 A,config.json 无节点)或 API 调用失败 fallback 时才重启 xray 进程。运行时增删纯走 API,config.json 定位保持"上次启动时的快照"语义,不同步写回(避免写文件并发问题,Stage1 冷启动筛选本身可靠不依赖 config.json 实时性)。 - 简化 Stage3 重启判断条件(Phase 1) (fork).
packages/core/src/modules/plugin/xray/index.js的短路条件从keptNodes.length === currentConfigNodes.length && keptNodes.length >= startupNodeLimit改为keptNodes.length >= startupNodeLimit。旧条件要求"可用节点数达标且节点集合未变化"才跳过重启,导致 5 个节点全可用但startupNodeLimit=10时因数量变化触发无谓重启。新条件只要可用节点数达标即跳过,节点集合的变化由 Phase 2 的 API 动态增删处理,无需重启。 - Stage1 候选节点 SQL 层提前过滤 country/owner (fork).
packages/core/src/modules/plugin/xray/index.js的 5 处buildCacheEntryQueryOptions调用(启动主路径/冷启动 regen/热刷新)新增allowedCountries和allowedOwners参数,让 SQLWHERE子句直接过滤country IN (...)和owner NOT LIKE '%cloudflare%',而不是在 JS 层collectBootstrapCandidateEntries事后过滤。之前 LIMIT 100 取出的节点可能大部分不符合 country/owner 条件(如前 100 个低延迟节点大多是 CN/JP),导致 probe 后筛出的可用节点不足startupNodeLimit。修复后 LIMIT 100 取的全是符合 country/owner 条件的节点,probe 后能筛出更多符合maxDelayMs的节点。bootstrapCandidateLimit默认值从 31 提高到 100。 - Stage1 快速复检改用 observatoryProbeUrl 探测 (fork).
packages/core/src/modules/plugin/xray/index.js的probeNodesBatch新增probeUrl参数,Stage1 bootstrap 传cfg.observatoryProbeUrl(严格,如 chatgpt.com),Stage3 周期探测传cfg.probeUrl(宽松,gstatic 204)。之前 Stage1 用probeUrl(宽松)探测,导致通过 gstatic 筛选的节点在主进程 observatory 用observatoryProbeUrl(严格)重新探测时大量 dead(delay=99999999)。修复后 Stage1 和主进程 observatory 用同一探测目标,确保进 config.json 的节点都能通过 observatory 探测。 - Stage1 不再用未 probe 的 stable 节点凑数 (fork).
packages/core/src/modules/plugin/xray/index.js启动节点选择不再将supportedFallbackEntries(只检查格式、未 probe 的 stable 节点)与bootstrapSelectedEntries(probe 验证过的节点)合并凑满startupNodeLimit。现在只用 probe 验证过的节点,仅在 probe 完全失败(0 个节点)时才 fallback 到 stable 节点兜底,避免未验证的 delay=0 节点进入 config.json 导致主进程 observatory 标记为 dead。 - 移除 Stage3/cache 和 bootstrap 探测的人为超时 (fork).
packages/core/src/modules/plugin/xray/index.js的两处probeNodesBatch调用(Stage1 bootstrap + Stage3 cache 周期探测)从传递cacheBatchTimeout/bootstrapBatchTimeout秒数改为直接传timeoutMs: 0。packages/core/src/modules/plugin/xray/probe.js的waitForObservatoryMetrics在timeoutMs <= 0时使用deadline = Infinity,即不设超时上限,让 observatory 自然收集完所有 sample 再返回;探测进程崩溃通过child.exitCode检查捕获。之前cacheBatchTimeout: 120(2 分钟)和 bootstrap 超时会在 v26.3.27 observatory 慢速收集时提前中断探测,导致 "Observatory metrics have not collected N samples yet" 错误和节点遗漏。同时删除了不再需要的getCacheBatchTimeoutSeconds/getBootstrapBatchTimeoutSeconds辅助函数,并从packages/core/src/modules/plugin/xray/config.js移除cacheBatchTimeout配置项。 - 探测从 burst observatory 切换到 regular observatory + 并发探测 (fork).
packages/core/src/modules/plugin/xray/index.js的runSingleProbePass将probeMode从'burst'改为'observatory'。v26.3.27 的 burst observatory 是串行探测的——每个 alive 节点需要timeout × sampling秒(15s × 2 = 30s),128 个 alive 节点需要 64 分钟;之前之所以快是因为大部分节点 dead(连接拒绝毫秒级完成),SQL 过滤后节点质量提升、alive 比例增加后速度暴跌。regular observatory 配合enableConcurrency: true并发探测所有节点,128 节点仅需 5-6 秒。实测 Stage3 第一轮 14332 节点(112 批)从预估 93+ 小时降至 ~11 分钟完成。同时修复了buildCacheEntriesFromObservatory(packages/core/src/modules/plugin/xray/cache.js)不处理 regular observatory 格式的问题——regular observatory 的节点状态是{alive, delay, outbound_tag}(无HealthPing字段),旧代码只处理HealthPing路径、跳过无HealthPing的状态,导致 regular observatory 的 alive 节点全部被丢弃;新增 fallback 路径直接从alive/delay字段构建 cache entry。probe.js的isObservationReady也同步更新:当检测到无HealthPing字段时(regular observatory),改用delay > 0判断所有节点是否已被探测(dead 节点的 delay=99999999 也满足此条件),而非等待healthPing.all >= expectedSamples。 set-system-proxycomplementary merge (fork + upstream). The fork's desktop-detection logic (skipgsettingswhen/usr/bin/gsettingsis absent or the X server socket/tmp/.X11-unix/X*does not exist, to avoid spawning dbus-launch + dbus-daemon + dconf-service on headless servers) was refactored from an earlyreturn trueinto ahasDesktopflag. Upstream's env-var support (writeProxyEnvFile+addProxyEnvToShellProfile) now runs unconditionally;gsettingscalls are guarded byif (!hasDesktop) return trueafter env vars are written. This fixes a regression where the fork's early return skipped env-var setup on headless servers, leaving CLI tools unable to use the proxy.- Duplicate
versionkeys in package.json (packages/cli,packages/core,packages/mitmproxy) cleaned up after conflict resolution (theours/theirsmerge left duplicate"version": "2.2.4"lines). Versions kept at2.2.4(will bump to2.2.5on release). packages/core/src/expose.js:startup()now checks!status.server.enabledbefore callingserver.start(), preventing a duplicate-start when the server is already running.packages/core/package.json: addedproper-lockfile: ^4.1.2dependency.- Root
package.json: merged fork'selectron-builder25.1.8 /app-builder-lib/dmg-builder/electron-builder-squirrel-windowspins with upstream's security overrides (axios,brace-expansion,cross-spawn,dns-packet,form-data,hoek,ip,minimist,qs,tough-cookie). packages/gui/src/view/composables/theme.js: adopted upstream'sdocument.body.setAttribute('data-theme', theme)sync (complements the fork'sclassList.add('theme-dark')inApp.vue, which was deduplicated from 13 accidental copies to 1).README.md: upstream revised the mirror website section.
Fixed
- GUI process detection false positives (upstream).
packages/cli/src/commands/gui.jsandstatus.jsno longer misreport a running GUI when matching unrelated processes. status.jsonperiodic writes removed (upstream). Replaced with event-driven status updates merged intorunning.json, eliminating the separatestatus.jsonfile and its timer.- Instance PID reuse false positive (fork fix for upstream regression).
service-entry.cjsandinstance.isLocked()only checked if a PID was alive (process.kill(pid, 0)), without verifying the process was actually dev-sidecar. When a staleservice.pidwas reused by an unrelated process (e.g.kiro-goat PID 234), the instance check always passed, causing systemd to fail restarting dev-sidecar in an infinite loop (81 restarts observed on a corporate network). Fix:isDevSidecarProcess()inservice-entry.cjsandisDevSidecarPid()ininstance/index.jsnow read/proc/<pid>/cmdlineand check fordev-sidecar/service-entry/@docmirrordev-sidecarkeywords; non-Linux falls back toprocess.kill(pid, 0). Added 2 test cases toinstanceTest.js. - mitmproxy OOM + respawn deadlock under stage3 cache thrash (fork fix). Two coupled bugs caused mitmproxy to crash and the auto-respawn to silently fail on a corporate network after ~5h of operation:
- mitmproxy
--max-old-space-size=96too small for sub2api traffic + corporate SASE: the 96 MB cap was sized for omniroute traffic (steady-state ~15 MB, 6× headroom) in v2.2.4. With sub2api's high-concurrencyrouter.huggingface.coSNI bursts stacked on the*.huggingface.cofakeServer (shared by all huggingface subdomains viagetDnsName) plus the corporate SASE 141-cert PEMcaarray spread into everyHttpsAgent, live V8 objects reached 94.5 MB (pooled: 0.0 MB, average mu = 0.214, mark-compact ineffective) and SIGABRT'd. Raised--max-old-space-sizefrom 96 to 192 inpackages/core/src/modules/server/index.js(12× headroom). Also demoted the one remaining hot-pathlog.infoinFakeServersCenter.jsSNICallback tolog.debug(v2.2.4's 57-call demotion missed this single line, which fired on every TLS SNI handshake). - Stage3 overrun → cleanup while-loop blocks main thread → SIGCHLD cannot dispatch:
cacheRefreshIntervalwas configured to 10800 s (3 h) but the Stage3 round took 5 h 40 min, soresolveNextCacheRefreshDelayreturned 0, immediately scheduling the next round. The next round's Stage2 cache-only path hitcacheSizeBeforeStage2 >= CACHE_SIZE_LIMIT_BYTES(1 GB) and enteredcleanupOutdatedToSizeLimit'swhile (getSqliteDatabaseSizeBytes(db) > target) { delete batch; incremental_vacuum(2048) }— a fully synchronousbetter-sqlite3transaction on a 1 GB database underMemoryHigh=280Mcgroup thrash (PSI memory/io both ~100%) that blocked the main thread's libuv event loop for 25+ minutes. When mitmproxy SIGABRT'd during this window, the kernel delivered SIGCHLD but libuv could not dispatch it (no event-loop tick) →serverProcess.on('exit')never fired →respawnState30-second/3-attempt sliding window was skipped entirely → the child became a zombie (<defunct>) and systemd showedactive (running)while the proxy port was dead. Fix: (1)cleanupOutdatedToSizeLimitinpackages/core/src/modules/plugin/xray/cache.jsis nowasyncand yields to the event loop (await new Promise(r => setImmediate(r))) every 4 batches by closing and reopening the SQLite handle, letting libuv dispatch pending SIGCHLD/IPC; (2)resolveNextCacheRefreshDelayinpackages/core/src/modules/plugin/xray/index.jsnow enforces a minimum 30-minute cooldown (STAGE3_OVERRUN_COOLDOWN_MS) when the previous round overrancacheRefreshInterval, preventing immediate re-entry into the cleanup loop. The 2cleanupOutdatedToSizeLimittest cases inxrayCacheOrdering.test.jswere updated toasync/await. Verified by 66 passing core tests.
- mitmproxy