# NG2 进程架构:GUI、Agent、Helper 与 sslocal [English](ng2-process-architecture.md) | 简体中文 实现快照:2026-10-24。后续实现以源码为准,已接受的设计见 [ADR](../adr/),术语见 [GLOSSARY.md](../../GLOSSARY.md)。 GUI 保存用户意图、生成运行配置并协调系统代理。用户会话中的 Agent 启动和监管 sslocal;sslocal 接收代理请求,执行路由与 Shadowsocks 转发。具有 root 权限的 SystemProxyHelper 按 GUI 请求修改 macOS 系统代理设置。 ## 进程与职责 | 组件 | 运行身份与生命周期 | 职责 | | --- | --- | --- | | GUI:`ShadowsocksX-NG2` | 当前用户的图形应用 | 管理服务器库、活动目标、规则、插件与监听设置;派生和发布运行配置;决定何时应用、暂停或清理系统代理 | | Agent:`ShadowsocksX-NG2Agent` | 当前用户会话的 LaunchAgent,由 launchd 管理 | 读取非秘密监管资料,切换稳定链接,启动、重载和停止 sslocal,记录部署回执与失败 | | Helper:`ShadowsocksX-NG2SystemProxyHelper` | 系统域 root LaunchDaemon,由 MachService 请求按需激活 | 接受 GUI 的 XPC 请求,通过 SystemConfiguration 写入或清除系统代理设置 | | `sslocal` | Agent 启动的用户态子进程 | 提供 SOCKS5 与 HTTP 入站,读取服务器配置与 ACL,处理直连和代理流量 | | SIP003 插件 | 按服务器配置由 sslocal 启动 | 为对应服务器提供插件传输路径;程序可来自随应用发布的托管插件或用户插件映射 | `Domain/` 是编入相应 target 的共享源码,没有独立进程。GUI 的应用层、工作流和 SwiftUI 视图也都位于同一个 GUI 进程。 ### 控制关系 ```mermaid flowchart TB GUI["GUI:用户意图与配置派生"] LD["launchd"] Files["运行目录:配置、ACL、监管资料"] Agent["Agent:用户态监管进程"] SL["sslocal:代理流量处理"] Plugin["SIP003 插件"] Helper["SystemProxyHelper:root"] SC["macOS 系统代理设置"] GUI -->|"bootstrap / bootout"| LD LD -->|"用户会话服务"| Agent GUI -->|"准备文件并发布监管资料"| Files GUI -->|"SIGUSR1:通知新部署"| Agent Agent -->|"读取监管资料、切换链接、写回执"| Files Agent -->|"启动 / 信号重载 / 停止"| SL Files -->|"配置、ACL、认证文件"| SL SL -->|"启动与使用"| Plugin GUI -->|"SMAppService.daemon 注册"| LD LD -->|"MachService 按需激活"| Helper GUI -->|"XPC:apply / clear"| Helper Helper -->|"SystemConfiguration 写入"| SC SC -->|"GUI 只读观察"| GUI ``` GUI 与 Agent 通过文件和信号协作。GUI 与 Helper 使用 XPC。Agent 和 Helper 之间没有直接调用关系,Helper 也不启动或监管 sslocal。 Agent 的生产注册实现会在 `~/Library/LaunchAgents/` 写入带可执行文件绝对路径的清单,再执行 `launchctl bootstrap gui/`;停止使用 `bootout`。`SMAppService.agent` 用于读取后台项目批准状态。Helper 则通过 `SMAppService.daemon` 注册应用内的 LaunchDaemon 清单。 ## GUI 内部的分工 `App/Composition/` 装配依赖和生命周期对象。SwiftUI 视图通过 `CatalogWorkflow`、`ProxyControlWorkflow`、`SettingsWorkflow`、`RulesWorkflow` 等工作流读取事实、提交用户操作。 `ProxyRuntimeController` 将最新保存的配置目录、活动目标、代理模式、规则、监听配置和有效插件目录派生为运行意图,再执行配置发布和 Agent 启停。`SystemProxyObserver` 负责系统代理的批准门禁、差异观察、应用、修复与暂停。两者的决策都发生在 GUI 进程。 共享领域代码负责目录不变量、存储、规则计算、运行计划和跨进程契约。Agent 和 Helper 只编入各自需要的共享文件;具体编译边界由 [`project.yml`](../../ShadowsocksX-NG2/project.yml) 定义。 ## 配置从保存到生效 用户持久保存的服务器库与供 sslocal 使用的运行配置具有不同生命周期。前者保留完整配置目录和凭据;后者由活动目标派生,只包含本次运行所需的信息。 ### 持久配置与秘密边界 默认应用支持目录是 `~/Library/Application Support/ShadowsocksX-NG2/`: | 位置 | 内容与读写方 | | --- | --- | | `configuration/catalog.age` | GUI 读写的加密服务器库 | | `configuration/proxy-listening.age` | GUI 读写的加密监听配置,包含 LAN 认证设置 | | `configuration/` 下的规则、活动目标和插件资料 | GUI 管理的独立配置文件 | | `runtime/` | GUI 发布的运行文件,以及 Agent 写入的进程身份、回执和失败记录 | | `logs/agent.log` | Agent 事件及经过输出 intake 处理的 sslocal 日志 | 服务器库与监听配置分别使用独立的 age identity,私钥保存在登录钥匙串。GUI 在派生运行配置时取得所需明文。当前 sslocal 配置和 LAN 认证文件仍以明文保存于受保护的运行目录,目录权限为 `0700`,普通文件为 `0600`。 Agent 只解码 `agent-supervision.json`,其中包含部署身份、代次、监听端点、随机配置身份与认证身份、文件引用和 ACL 元数据。Agent 不解析服务器密码、插件参数、LAN 凭据或 GUI 私有意图元数据。这个边界限制的是 Agent 的处理职责;Agent 与 sslocal 仍以同一用户身份运行。 ### 发布协议 1. GUI 从已保存的资料生成 `SslocalRuntimeDocument`,输出 sslocal JSON、ACL 和必要的 LAN 认证文件。 2. `RuntimeFileStore` 在 `runtime.lock` 内分配单调递增的代次,写入带代次的不可变文件。内容未变的认证文件与 ACL 可以复用。 3. 所有引用文件准备并校验完成后,GUI 原子替换 `agent-supervision.json`。这是部署意图的发布点。 4. 首次运行时,GUI 注册 Agent;Agent 已运行时,GUI 向其发送 `SIGUSR1`。 5. Agent 校验监管资料,切换 `sslocal-active.json` 与 `acl-active.ini` 稳定链接,再启动或重载 sslocal。 6. Agent 写入 `agent-runtime-status.json` 回执,GUI 在部署确认阶段据此检查本地就绪状态,再协调系统代理。 GUI 不会解码已输出的 sslocal JSON 来恢复用户意图。它使用同代 `intent.json` 中的私有意图摘要、输出字节摘要与脱敏汇总完成比较、完整性检查和诊断。Agent 只保留该文件的引用。 稳定链接由 Agent 独占切换。GUI 准备和发布、Agent 回收旧文件共用 `runtime.lock`;Agent 另持有进程期的 `agent.lock`,防止两个监管实例同时启动监听进程。回收会保留最新发布与当前子进程仍使用的文件,停止服务也保留代次高水位。 ### 更新与重载 GUI 发给 Agent 的 `SIGUSR1` 统一表示“读取新部署”。Agent 再按变化决定给 sslocal 的操作: | 变化 | Agent 对 sslocal 的操作 | | --- | --- | | 服务器等配置字节变化,配置身份改变 | 切换配置链接,发送 `SIGUSR1` | | ACL 变化,包括代理模式与规则变化 | 切换 ACL 链接,发送 `SIGUSR2` | | 配置与 ACL 同时变化 | 分别发送上述两个信号 | | 监听端点、IPv6 监听设置或认证身份变化 | 停止旧子进程并确认退出,再启动新进程 | 两个链接分别原子切换,服务器配置与 ACL 可以先后生效。Agent 在初次监听尚未就绪时合并待发信号,待就绪后发送。 部署回执关联 `deploymentID` 和 sslocal PID,记录 Agent 操作及监听证据。它不能证明 sslocal 已成功解析和应用热重载内容;例如 ACL 解析失败时,上游可能继续使用旧规则。当前没有上游应用确认协议。 ## 请求流量经过哪里 ```mermaid flowchart LR App["使用系统代理的本机应用"] -->|"读取系统代理设置"| Local["sslocal 本机 SOCKS5 / HTTP 入站"] Manual["显式配置代理的应用或终端"] --> Local LAN["局域网客户端"] --> Network["sslocal LAN 入站,可选认证"] Local --> ACL["sslocal ACL 路由"] Network --> ACL ACL -->|"直连"| Target["目标服务"] ACL -->|"代理,无插件"| SS["Shadowsocks 服务器"] ACL -->|"代理,配置了插件"| Plugin["SIP003 插件传输"] Plugin --> SS SS --> Target ``` 本机入站绑定 `127.0.0.1`,默认 SOCKS5 端口为 `11086`,HTTP 端口为 `11087`。LAN 入站独立启用,默认端口分别为 `11088` 和 `11089`,可选择地址族、认证与 SOCKS5 UDP 中继设置。本机 SOCKS5 支持 TCP 与 UDP;HTTP 入站由 sslocal 直接提供。 系统 SOCKS 代理指向本机 SOCKS5 入站,HTTP 和 HTTPS 系统代理共用本机 HTTP 入站。HTTPS 请求使用该 HTTP 代理的 CONNECT 能力。系统代理设置影响遵循这些设置的应用;显式代理配置也可以直接使用本地入站。 规则、全局和直连模式通过 sslocal ACL 表达。全局模式仍包含固定本地绕过策略;直连模式仍保留 sslocal 入站,由 sslocal 直接连接目标。GUI、Agent 和 Helper 均不转发正常代理请求。 代理服务开关与系统代理开关分别保存意图。服务开启而系统代理关闭时,本地代理入口仍可供显式配置代理的客户端使用。 ## Helper 的权限与系统代理范围 GUI 决定是否应用系统代理,并显式提供 SOCKS、HTTP、HTTPS 端点、PAC 与自动发现启停、简单主机名排除及完整例外列表。Helper 解码类型化请求、串行执行,再返回类型化结果。 - `apply` 处理当前网络位置的所有网络服务,只修改受管代理字段,保留未建模的字段,并跳过值已等价的服务。 - `clear` 移除所有网络位置、所有网络服务的完整 `Proxies` 字典,不判断设置来源,也不恢复其他软件的旧值。 GUI 通过只读 SystemConfiguration 访问观察当前设置。正常启动和被动网络变化以检查差异为主;差异修复由用户明确请求。Helper 不读取服务器库、不获取 Shadowsocks 密码,也不自行观察运行故障或决定清理时机。 Helper 接受代码签名 identifier 为 `com.qiuyuzhou.ShadowsocksX-NG2` 的客户端,当前不限制 Team ID 或 UID。各用户拥有独立代理意图,但系统代理设置是设备级共享状态;多个实例的请求经 Helper 串行执行,后执行的写入决定最终值。 ## 生命周期与失败处理 Agent 启动 sslocal 后检查本地端点可连接,并在正式带 ACL 的部署中确认 TCP 监听属于该子进程。已健康子进程意外退出时,Agent 重放当前发布。首次启动未就绪、链接切换或信号投递失败等可检测故障,会形成与部署身份关联的持久失败记录,停止该部署,不自动切回历史配置。 Agent 非零退出时由 launchd 的 `KeepAlive.SuccessfulExit = false` 策略重启,清单配置了 2 秒节流。Agent 崩溃后若记录中的旧 sslocal PID 仍存在,下一实例拒绝启动第二个监听实例,并记录失败。 GUI 在启动、重启和显式部署时执行就绪确认。稳定运行期间,系统代理意图开启时,GUI 每 5 秒检查当前部署的明确失败记录;发现失败后暂停系统代理并保留开启意图,等待显式重试或新运行意图。自动重启中的短暂回执空窗、PID 变化或远端波动不会单独触发暂停。没有失败记录也不能证明服务健康,GUI 不再周期性连接本地端口来判定故障。 关闭代理服务时,GUI 关闭系统代理意图并请求清理,再停止后台服务。Agent 收到停止信号后终止 sslocal,必要时升级为强制终止;GUI 确认 Agent 退出后才清理运行文件。Helper 的注册可以继续保留,供后续按需调用。 退出 GUI 时,退出流程等待连通性测试结束与清理,后台 Agent 和正常代理服务继续运行,系统代理设置保留。此时 GUI 中的故障检查和系统代理暂停逻辑也随之停止;Helper 不接管这些职责。GUI 登录项只控制是否在登录时打开图形应用,与代理服务开关独立。 ### 连通性测试的独立进程 服务器连通性测试由 GUI 的 `ConnectivityRuntime` 直接启动临时 sslocal,使用独立临时目录和动态分配的回环 HTTP 端口。测试配置只含待测服务器,不带正式运行 ACL,请求通过该服务器访问测试 URL。测试完成或取消后清理进程组与临时目录。 因此,测试进程不经过正式 Agent,也不修改系统代理或占用正式监听端口。测试成功描述指定服务器到测试 URL 的请求结果,与正式部署的就绪回执具有不同含义。 ## 源码入口与相关决定 | 关注点 | 源码入口 | | --- | --- | | GUI 装配与工作流 | [`App/Composition/MainApp.swift`](../../ShadowsocksX-NG2/App/Composition/MainApp.swift)、[`App/Application/`](../../ShadowsocksX-NG2/App/Application/) | | Agent 注册与运行计划 | [`LaunchAgentService.swift`](../../ShadowsocksX-NG2/App/Application/Services/LaunchAgentService.swift)、[`ProxyRuntimePlan.swift`](../../ShadowsocksX-NG2/Domain/Runtime/ProxyRuntimePlan.swift) | | 配置发布与监管契约 | [`RuntimeFileStore.swift`](../../ShadowsocksX-NG2/Domain/Runtime/RuntimeFileStore.swift)、[`RuntimeSupervision.swift`](../../ShadowsocksX-NG2/Domain/Runtime/RuntimeSupervision.swift) | | Agent 主循环与监听确认 | [`Agent/main.swift`](../../ShadowsocksX-NG2/Agent/main.swift)、[`AgentListening.swift`](../../ShadowsocksX-NG2/Agent/AgentListening.swift) | | 系统代理策略与 XPC 客户端 | [`SystemProxyObserver.swift`](../../ShadowsocksX-NG2/App/Application/ProxyRuntime/SystemProxyObserver.swift)、[`SystemProxyHelperService.swift`](../../ShadowsocksX-NG2/App/Application/Services/SystemProxyHelperService.swift) | | Helper 契约、校验与写入 | [`Domain/SystemProxy/`](../../ShadowsocksX-NG2/Domain/SystemProxy/)、[`SystemProxyWriter.swift`](../../ShadowsocksX-NG2/SystemProxyHelper/SystemProxyWriter.swift) | | 稳定期失败观察 | [`ProxyRuntimeController+Health.swift`](../../ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController+Health.swift) | | 独立连通性测试 | [`ConnectivityRuntime.swift`](../../ShadowsocksX-NG2/App/Application/Connectivity/ConnectivityRuntime.swift) | 相关设计依据:[特权 Helper](../adr/0020-privileged-system-proxy-writer.md)、[系统代理差异与修复](../adr/0022-observe-system-proxy-differences-and-repair.md)、[监管与配置分离](../adr/0038-separate-agent-supervision-and-sslocal-input.md)、[配置单向输出](../adr/0043-one-way-sslocal-configuration-output.md)、[ACL 信号重载](../adr/0048-reload-sslocal-acl-by-signal.md)、[Agent 故障观察职责](../adr/0049-agent-owned-runtime-failure-observation.md)、[age 持久配置](../adr/0050-age-encrypted-configuration-storage.md)。