chore(legacy): 移除 Legacy/ 旧版源码,归档到 legacy-v1 分支

旧版实现已由 shadowsocks/ShadowsocksX-NG 的 legacy-v1 分支跟踪,本仓库不再
保留同一份代码副本。删除 Legacy/ 下 603 个跟踪文件,被 gitignore 的
.DS_Store 一并清理。

删除前核对:Legacy/ShadowsocksX-NG/PACUtils.swift 的 blob hash 与
upstream/legacy-v1 的同名文件完全相同,且工作区在 Legacy/ 下无未提交改动。

配套改动:

- 根 README、AGENTS.md、ShadowsocksX-NG2/README.md:目录结构只留
  ShadowsocksX-NG2/;旧版说明改为指向 shadowsocks/ShadowsocksX-NG 的
  legacy-v1 分支(含其 README);pre-commit 说明去掉 Legacy/。
- codegraph.json 删除 "exclude": ["Legacy/"];.gitignore 与 .zcodeignore
  删除 Legacy 构建产物与依赖条目;.swiftlint.yml 删除 ../Legacy 排除项;
  .githooks/pre-commit 头部注释去掉冻结目录表述。
- docs/research 的 4 份历史研究报告(issue 7/11/12/17)中 129 处
  ../../Legacy/... 相对链接改写为 legacy-v1 的 GitHub blob 链接,正文未改;
  把新 URL 反替换回原相对路径后与 HEAD 版本逐字节相同,证明只动了链接。

验证:全仓库不再有指向 Legacy/ 的路径引用;剩余 Legacy 字样均为概念性用法
(Legacy 导入功能、ADR 与术语表、历史报告正文中的冻结陈述)。本次未改任何
NG2 源码或构建配置,暂存区无 NG2 .swift 文件,pre-commit 直接放行。
This commit is contained in:
Qiu Yuzhou
2026-10-05 21:39:06 +08:00
parent 9b6d73e91c
commit 7ab774fcfd
615 changed files with 68 additions and 74808 deletions
+12 -12
View File
@@ -19,21 +19,21 @@
Legacy 的 PAC 是“规则编译 + 本机 HTTP 托管 + 特权系统代理写入”的组合,不是一个可以直接复制到 2.0 的静态文件。
- PAC 状态目录是 ~/.ShadowsocksX-NG/;输出为 gfwlist.js,输入为 gfwlist.txt 和 user-rule.txt。首次运行从 bundle 复制规则资源,同时迁移旧的 ~/.ShadowsocksX-NE/ 目录。[PACUtils.swift](../../Legacy/ShadowsocksX-NG/PACUtils.swift#L12-L17) [PACUtils.swift](../../Legacy/ShadowsocksX-NG/PACUtils.swift#L51-L73)
- GeneratePACFile() 对 Base64 GFWList 解码,把用户规则置前并去重,过滤空行/注释,再把规则和 SOCKS 地址/端口替换进 abp.js;结果用临时文件加 rename 原子替换 gfwlist.js。[PACUtils.swift](../../Legacy/ShadowsocksX-NG/PACUtils.swift#L79-L150)
- abp.js 的 FindProxyForURL 命中规则时返回 SOCKS5/SOCKS/DIRECT,否则返回 DIRECT。[abp.js](../../Legacy/ShadowsocksX-NG/abp.js#L1-L6) [abp.js](../../Legacy/ShadowsocksX-NG/abp.js#L768-L777)
- 本地 PAC URL 固定为 http://localhost:<port>/proxy.pac;GCDWebServer 在启动时把文件读入 originalPACData,只注册 GET /proxy.pac,并返回 application/x-ns-proxy-autoconfig。[ProxyConfHelper.m](../../Legacy/ShadowsocksX-NG/ProxyConfHelper.m#L136-L153) [ProxyConfHelper.m](../../Legacy/ShadowsocksX-NG/ProxyConfHelper.m#L206-L253)
- 默认监听 localhost:1089,端口和绑定范围来自 UserDefaults;PAC 文件变更由 vnode 监视器触发 disableProxy 后 enablePACProxy。[AppDelegate.swift](../../Legacy/ShadowsocksX-NG/AppDelegate.swift#L91-L115) [ProxyConfHelper.m](../../Legacy/ShadowsocksX-NG/ProxyConfHelper.m#L262-L294)
- 系统代理由 /Library/Application Support/ShadowsocksX-NG/proxy_conf_helper 写入。安装脚本把 helper 设为 root:admin、a+rx 并加 setuid;helper 使用 AuthorizationCreate 和 SCPreferencesCreateWithAuthorization,处理 auto/global/off 以及网络服务、例外项。[install_helper.sh](../../Legacy/ShadowsocksX-NG/install_helper.sh#L10-L14) [proxy_conf_helper/main.m](../../Legacy/proxy_conf_helper/main.m#L105-L216)
- 外部 PAC 直接把 ExternalPACURL 传给相同的 helper;PACURLFormatter 接受 file/http/https,但没有看到内容、可达性或来源完整性校验。[PACURLFormatter.swift](../../Legacy/ShadowsocksX-NG/PACURLFormatter.swift#L26-L52) [ProxyConfHelper.m](../../Legacy/ShadowsocksX-NG/ProxyConfHelper.m#L195-L221)
- PAC 状态目录是 ~/.ShadowsocksX-NG/;输出为 gfwlist.js,输入为 gfwlist.txt 和 user-rule.txt。首次运行从 bundle 复制规则资源,同时迁移旧的 ~/.ShadowsocksX-NE/ 目录。[PACUtils.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/PACUtils.swift#L12-L17) [PACUtils.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/PACUtils.swift#L51-L73)
- GeneratePACFile() 对 Base64 GFWList 解码,把用户规则置前并去重,过滤空行/注释,再把规则和 SOCKS 地址/端口替换进 abp.js;结果用临时文件加 rename 原子替换 gfwlist.js。[PACUtils.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/PACUtils.swift#L79-L150)
- abp.js 的 FindProxyForURL 命中规则时返回 SOCKS5/SOCKS/DIRECT,否则返回 DIRECT。[abp.js](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/abp.js#L1-L6) [abp.js](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/abp.js#L768-L777)
- 本地 PAC URL 固定为 http://localhost:<port>/proxy.pac;GCDWebServer 在启动时把文件读入 originalPACData,只注册 GET /proxy.pac,并返回 application/x-ns-proxy-autoconfig。[ProxyConfHelper.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ProxyConfHelper.m#L136-L153) [ProxyConfHelper.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ProxyConfHelper.m#L206-L253)
- 默认监听 localhost:1089,端口和绑定范围来自 UserDefaults;PAC 文件变更由 vnode 监视器触发 disableProxy 后 enablePACProxy。[AppDelegate.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/AppDelegate.swift#L91-L115) [ProxyConfHelper.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ProxyConfHelper.m#L262-L294)
- 系统代理由 /Library/Application Support/ShadowsocksX-NG/proxy_conf_helper 写入。安装脚本把 helper 设为 root:admin、a+rx 并加 setuid;helper 使用 AuthorizationCreate 和 SCPreferencesCreateWithAuthorization,处理 auto/global/off 以及网络服务、例外项。[install_helper.sh](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/install_helper.sh#L10-L14) [proxy_conf_helper/main.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/proxy_conf_helper/main.m#L105-L216)
- 外部 PAC 直接把 ExternalPACURL 传给相同的 helper;PACURLFormatter 接受 file/http/https,但没有看到内容、可达性或来源完整性校验。[PACURLFormatter.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/PACURLFormatter.swift#L26-L52) [ProxyConfHelper.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ProxyConfHelper.m#L195-L221)
源码盘点发现的具体风险边界:
- PAC 服务失败的布尔结果未向上层传播,之后仍可能把不可用 URL 写入系统代理;用户端口允许 128–65535,但 URL/监听路径有 short 窄化,32768–65535 存在表示范围不一致。[ProxyConfHelper.m](../../Legacy/ShadowsocksX-NG/ProxyConfHelper.m#L206-L215) [ProxyConfHelper.m](../../Legacy/ShadowsocksX-NG/ProxyConfHelper.m#L244-L252)
- 原子替换会产生 vnode DELETE;监视器在 DELETE 事件中取消自身,而源码没有持续重装路径。后续规则更新并不保证仍会被观察到。[PACUtils.swift](../../Legacy/ShadowsocksX-NG/PACUtils.swift#L146-L150) [ProxyConfHelper.m](../../Legacy/ShadowsocksX-NG/ProxyConfHelper.m#L273-L293)
- helper 的 auto/global 路径从部分 key 新建 Proxies 字典,关闭时只在 PAC URL 或 SOCKS 值恰好匹配调用方参数时清除;它没有保存并恢复用户原有的完整代理字典。[proxy_conf_helper/main.m](../../Legacy/proxy_conf_helper/main.m#L126-L216)
- 外部 PAC 启用时传入外部 URL,但 disableProxy 始终传本地 getHttpPACUrl;外部 PAC 进入 manual/off 或退出的清理路径因此存在状态不对称。[ProxyConfHelper.m](../../Legacy/ShadowsocksX-NG/ProxyConfHelper.m#L178-L203)
- PAC 模板头部标注由 gfwlist2pac 生成并带 GPLv3 声明。2.0 若继续重用该模板,要把许可和生成器来源作为独立发布检查项;本票据不假定其上游维护状态。[abp.js](../../Legacy/ShadowsocksX-NG/abp.js#L1-L22)
- PAC 服务失败的布尔结果未向上层传播,之后仍可能把不可用 URL 写入系统代理;用户端口允许 128–65535,但 URL/监听路径有 short 窄化,32768–65535 存在表示范围不一致。[ProxyConfHelper.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ProxyConfHelper.m#L206-L215) [ProxyConfHelper.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ProxyConfHelper.m#L244-L252)
- 原子替换会产生 vnode DELETE;监视器在 DELETE 事件中取消自身,而源码没有持续重装路径。后续规则更新并不保证仍会被观察到。[PACUtils.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/PACUtils.swift#L146-L150) [ProxyConfHelper.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ProxyConfHelper.m#L273-L293)
- helper 的 auto/global 路径从部分 key 新建 Proxies 字典,关闭时只在 PAC URL 或 SOCKS 值恰好匹配调用方参数时清除;它没有保存并恢复用户原有的完整代理字典。[proxy_conf_helper/main.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/proxy_conf_helper/main.m#L126-L216)
- 外部 PAC 启用时传入外部 URL,但 disableProxy 始终传本地 getHttpPACUrl;外部 PAC 进入 manual/off 或退出的清理路径因此存在状态不对称。[ProxyConfHelper.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ProxyConfHelper.m#L178-L203)
- PAC 模板头部标注由 gfwlist2pac 生成并带 GPLv3 声明。2.0 若继续重用该模板,要把许可和生成器来源作为独立发布检查项;本票据不假定其上游维护状态。[abp.js](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/abp.js#L1-L22)
## 2. Apple 当前的 PAC 和系统代理边界
+4 -4
View File
@@ -21,10 +21,10 @@
Legacy 对 GCDWebServer 的使用确实只有一件事:在本地回环上交付 `/proxy.pac`。
- 依赖来自 CocoaPods:`pod "GCDWebServer", "~> 3.0"`,Podfile.lock 锁定 3.5.4(GCDWebServer/Core)。[Podfile](../../Legacy/Podfile#L11) [Podfile.lock](../../Legacy/Podfile.lock#L4-L6)
- 本地 PAC URL 固定拼为 `http://localhost:<port>/proxy.pac`,端口来自 `PacServer.ListenPort`,short 窄化。[ProxyConfHelper.m](../../Legacy/ShadowsocksX-NG/ProxyConfHelper.m#L206-L215)
- 启动服务时把 PAC 文件一次性读入内存 `originalPACData`,只注册 `GET /proxy.pac` 一个 handler,返回 `application/x-ns-proxy-autoconfig`;再以 `GCDWebServerOption_BindToLocalhost` + `GCDWebServerOption_Port` 启动——`PacServer.BindToLocalhost` 是 Legacy 已有的回环/主机两态开关,即“把 PAC URL 给局域网其他机器用”的场景在 Legacy 就被支持。[ProxyConfHelper.m](../../Legacy/ShadowsocksX-NG/ProxyConfHelper.m#L223-L253)
- 停止服务只是 `stop`,无错误传播;启动失败的 `error:nil` 也被丢弃——这正是 #11 已判定要重写的生命周期与失败语义。[ProxyConfHelper.m](../../Legacy/ShadowsocksX-NG/ProxyConfHelper.m#L255-L260)
- 依赖来自 CocoaPods:`pod "GCDWebServer", "~> 3.0"`,Podfile.lock 锁定 3.5.4(GCDWebServer/Core)。[Podfile](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/Podfile#L11) [Podfile.lock](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/Podfile.lock#L4-L6)
- 本地 PAC URL 固定拼为 `http://localhost:<port>/proxy.pac`,端口来自 `PacServer.ListenPort`,short 窄化。[ProxyConfHelper.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ProxyConfHelper.m#L206-L215)
- 启动服务时把 PAC 文件一次性读入内存 `originalPACData`,只注册 `GET /proxy.pac` 一个 handler,返回 `application/x-ns-proxy-autoconfig`;再以 `GCDWebServerOption_BindToLocalhost` + `GCDWebServerOption_Port` 启动——`PacServer.BindToLocalhost` 是 Legacy 已有的回环/主机两态开关,即“把 PAC URL 给局域网其他机器用”的场景在 Legacy 就被支持。[ProxyConfHelper.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ProxyConfHelper.m#L223-L253)
- 停止服务只是 `stop`,无错误传播;启动失败的 `error:nil` 也被丢弃——这正是 #11 已判定要重写的生命周期与失败语义。[ProxyConfHelper.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ProxyConfHelper.m#L255-L260)
结论:2.0 对这个组件的需求与 Legacy 完全同构(loopback + 单端点 + 固定 MIME),但生命周期、失败传播和版本化缓存(#11 §4.2)要求整体重写。GCDWebServer 在 Legacy 中的使用没有隐藏功能,替换它没有迁移负债。
@@ -18,22 +18,22 @@
## 1. Legacy 运行时产物的精确清单(正向识别的基底)
两个 launchd job 的全部标识字符串在冻结源码中硬编码,[LaunchAgentUtils.swift](../../Legacy/ShadowsocksX-NG/LaunchAgentUtils.swift#L11-L16):
两个 launchd job 的全部标识字符串在冻结源码中硬编码,[LaunchAgentUtils.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/LaunchAgentUtils.swift#L11-L16):
| 产物 | 精确值 | 源码位置 |
| --- | --- | --- |
| ss-local job label | `com.qiuyuzhou.shadowsocksX-NG.local` | [LaunchAgentUtils.swift L66](../../Legacy/ShadowsocksX-NG/LaunchAgentUtils.swift#L66) |
| ss-local plist 路径 | `~/Library/LaunchAgents/com.qiuyuzhou.shadowsocksX-NG.local.plist` | [LaunchAgentUtils.swift L14/L34-36](../../Legacy/ShadowsocksX-NG/LaunchAgentUtils.swift#L14) |
| Privoxy job label | `com.qiuyuzhou.shadowsocksX-NG.http` | [LaunchAgentUtils.swift L303](../../Legacy/ShadowsocksX-NG/LaunchAgentUtils.swift#L303) |
| Privoxy plist 路径 | `~/Library/LaunchAgents/com.qiuyuzhou.shadowsocksX-NG.http.plist` | [LaunchAgentUtils.swift L15/L287-289](../../Legacy/ShadowsocksX-NG/LaunchAgentUtils.swift#L15) |
| 登录项 helper bundle ID | `com.qiuyuzhou.ShadowsocksX-NG.LaunchHelper`(注意大小写与 job label 不同) | [LaunchAtLoginController.m L69](../../Legacy/ShadowsocksX-NG/LaunchAtLoginController.m#L67-L81) |
| 登录项 helper 安装位置 | `<Legacy app>/Contents/Library/LoginItems/LaunchHelper.app` | [project.pbxproj L131](../../Legacy/ShadowsocksX-NG.xcodeproj/project.pbxproj#L131)(`dstPath = Contents/Library/LoginItems`) |
| ss-local job label | `com.qiuyuzhou.shadowsocksX-NG.local` | [LaunchAgentUtils.swift L66](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/LaunchAgentUtils.swift#L66) |
| ss-local plist 路径 | `~/Library/LaunchAgents/com.qiuyuzhou.shadowsocksX-NG.local.plist` | [LaunchAgentUtils.swift L14/L34-36](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/LaunchAgentUtils.swift#L14) |
| Privoxy job label | `com.qiuyuzhou.shadowsocksX-NG.http` | [LaunchAgentUtils.swift L303](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/LaunchAgentUtils.swift#L303) |
| Privoxy plist 路径 | `~/Library/LaunchAgents/com.qiuyuzhou.shadowsocksX-NG.http.plist` | [LaunchAgentUtils.swift L15/L287-289](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/LaunchAgentUtils.swift#L15) |
| 登录项 helper bundle ID | `com.qiuyuzhou.ShadowsocksX-NG.LaunchHelper`(注意大小写与 job label 不同) | [LaunchAtLoginController.m L69](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/LaunchAtLoginController.m#L67-L81) |
| 登录项 helper 安装位置 | `<Legacy app>/Contents/Library/LoginItems/LaunchHelper.app` | [project.pbxproj L131](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG.xcodeproj/project.pbxproj#L131)(`dstPath = Contents/Library/LoginItems`) |
**跨版本稳定性(git 全历史查证)**:对全部 619 个提交做 `git grep` 扫描,出现过的 label 字符串只有 `.local`、`.http`、`.kcptun` 三种,无任何改名、变体或大小写漂移。2.0 可以安全地把这些字符串当作常量。
**历史第三 job(识别面要覆盖、处置面要区分)**:`com.qiuyuzhou.shadowsocksX-NG.kcptun` job 连同 `generateKcptunLauchAgentPlist()` 存活到 2018-09-16(提交 `db81543` "Remove feature over kcptune.")。用过 2018 年前 Legacy 版本的用户机器上可能残留该 plist 甚至已加载 job。#7 决议只授权停止/卸载 `.local` 与 `.http` 两个 job;kcptun 残留应走「检测 + 提示用户处理」路线(识别序列仍按精确 label 检查它),其处置是否扩入停止名单需要一个小裁决(遗留裁决项,见 §9)。
**两个 job 的 plist 关键形态**(现行冻结版):只有 `Label`、`WorkingDirectory`、`StandardOutPath/StandardErrorPath`、`ProgramArguments`、`EnvironmentVariables`(ss-local),无 `RunAtLoad`、无 `KeepAlive`、无 Socket 定义——即登录时 launchd 只注册 job 不启动实例;ss-local 由 Legacy app 经脚本 `launchctl load -wF` + `launchctl start` 显式拉起([start_ss_local.sh](../../Legacy/ShadowsocksX-NG/ss-local/start_ss_local.sh)、[stop_ss_local.sh](../../Legacy/ShadowsocksX-NG/ss-local/stop_ss_local.sh),用旧式 `load/unload`)。ss-local 的 `ProgramArguments` 含 `--reuse-port`([LaunchAgentUtils.swift L57](../../Legacy/ShadowsocksX-NG/LaunchAgentUtils.swift#L50-L58)),这对端口释放探测有直接约束(§2.3)。
**两个 job 的 plist 关键形态**(现行冻结版):只有 `Label`、`WorkingDirectory`、`StandardOutPath/StandardErrorPath`、`ProgramArguments`、`EnvironmentVariables`(ss-local),无 `RunAtLoad`、无 `KeepAlive`、无 Socket 定义——即登录时 launchd 只注册 job 不启动实例;ss-local 由 Legacy app 经脚本 `launchctl load -wF` + `launchctl start` 显式拉起([start_ss_local.sh](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ss-local/start_ss_local.sh)、[stop_ss_local.sh](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ss-local/stop_ss_local.sh),用旧式 `load/unload`)。ss-local 的 `ProgramArguments` 含 `--reuse-port`([LaunchAgentUtils.swift L57](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/LaunchAgentUtils.swift#L50-L58)),这对端口释放探测有直接约束(§2.3)。
**端口归属映射(交接检查的对象清单)**:
@@ -41,7 +41,7 @@
| --- | --- | --- | --- |
| 1086 SOCKS5 | ss-local job(`.local`) | plist `ProgramArguments` | bootout `.local` |
| 1087 HTTP | Privoxy job(`.http`) | plist `ProgramArguments` | bootout `.http` |
| 1089 PAC | **Legacy GUI 进程内 GCDWebServer**,非 launchd job | [ProxyConfHelper.m L16/L223-236](../../Legacy/ShadowsocksX-NG/ProxyConfHelper.m#L223-L236)(进程内全局对象) | **Legacy app 退出**;bootout 两个 job 不影响它 |
| 1089 PAC | **Legacy GUI 进程内 GCDWebServer**,非 launchd job | [ProxyConfHelper.m L16/L223-236](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ProxyConfHelper.m#L223-L236)(进程内全局对象) | **Legacy app 退出**;bootout 两个 job 不影响它 |
注意 Legacy 的 LaunchAgent plist 内**没有端口字段**:端口写在 `~/Library/Application Support/ShadowsocksX-NG/ss-local-config.json`(`local_port`/`local_address`)与 `privoxy.config` 中,源头是 Legacy defaults `LocalSocks5.ListenPort`/`LocalHTTP.ListenPort`/`PacServer.ListenPort`(inventory §1)。因此端口释放检查不能从 LaunchAgent plist 读端口,而应:从 defaults 域 `com.qiuyuzhou.ShadowsocksX-NG` 与上述生成文件读取配置端口,再对每个具体端口探测。
@@ -116,11 +116,11 @@
### 4.2 识别途径与诚实边界
- 可正向识别的静态事实:Legacy app bundle 是否存在于常见安装位置(`/Applications/ShadowsocksX-NG.app` 等,LaunchHelper 的启动逻辑本身也按这些位置找主 app,[AppDelegate.m](../../Legacy/LaunchHelper/LaunchHelper/AppDelegate.m));bundle 内 `Contents/Library/LoginItems/LaunchHelper.app` 是否存在;defaults 域 `com.qiuyuzhou.ShadowsocksX-NG` 的 `LaunchAtLogin` 布尔([LaunchAtLoginController.m L76](../../Legacy/ShadowsocksX-NG/LaunchAtLoginController.m#L75-L77) 在 SMLoginItemSetEnabled 成功后写入,可作意图信号,但不是系统登记的真值)。
- 可正向识别的静态事实:Legacy app bundle 是否存在于常见安装位置(`/Applications/ShadowsocksX-NG.app` 等,LaunchHelper 的启动逻辑本身也按这些位置找主 app,[AppDelegate.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/LaunchHelper/LaunchHelper/AppDelegate.m));bundle 内 `Contents/Library/LoginItems/LaunchHelper.app` 是否存在;defaults 域 `com.qiuyuzhou.ShadowsocksX-NG` 的 `LaunchAtLogin` 布尔([LaunchAtLoginController.m L76](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/LaunchAtLoginController.m#L75-L77) 在 SMLoginItemSetEnabled 成功后写入,可作意图信号,但不是系统登记的真值)。
- BTM 数据库(macOS 13+ 登录项统一存储):`sfltool dumpbtm` 可列出记录(记录含 `Identifier`、`URL`、`Type`、`Disposition` 字段,第三方登录项按 bundle ID 可辨)。但本机实测该命令**触发了面向用户的系统管理员授权弹窗**(协调者确认),且 `man sfltool` 只把它描述为「tool for testing and debugging SharedFileList」,`dumpbtm` 子命令本身未文档化。**结论:sfltool 不能作为 2.0 应用内的无提示检测手段**——它需要管理员授权、输出格式无 API 承诺、子命令未文档化。
- 因此 2.0 对旧登录项的编程能力边界是:只能做**静态正向识别**(Legacy bundle/helper 文件存在性 + defaults 意图信号),无法可靠地以编程方式读取或改变系统登记状态。停用动作走人工路径:
1. **首选(状态真值在系统设置)**:指引文案指向 系统设置 → 通用 → 登录项与扩展,在「登录时打开」/「允许在后台」中找到 ShadowsocksX-NG(或其 helper)条目并移除;可用 `openSystemSettingsLoginItems` 直达。
2. **替代(若 Legacy app 仍在)**:启动 Legacy app 并在其偏好中关闭「登录时启动」——它会以弃用 API 把自己登记为 false([LaunchAtLoginController.m](../../Legacy/ShadowsocksX-NG/LaunchAtLoginController.m#L67-L82))。语义上这是让「旧代码撤销旧登记」,最不容易误伤。
2. **替代(若 Legacy app 仍在)**:启动 Legacy app 并在其偏好中关闭「登录时启动」——它会以弃用 API 把自己登记为 false([LaunchAtLoginController.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/LaunchAtLoginController.m#L67-L82))。语义上这是让「旧代码撤销旧登记」,最不容易误伤。
- 不采用:从 2.0 调用 `SMLoginItemSetEnabled(helperID, false)` 的跨 app 摸底——弃用 API 对「非本 app LoginItems 目录中的 identifier」的行为未文档化,且无法在本机构造已登记环境验证(Legacy 已不在本机登记,见 §7)。若未来要试,必须先在装有 Legacy 且已开登录项的机器上验证(§9)。
## 5. 系统代理:确认、改写与失败路径
@@ -7,7 +7,7 @@
## 1. 应用拥有的 UserDefaults
`applicationDidFinishLaunching` 中唯一的 `register(defaults:)` 调用注册了下列 21 个默认值。[AppDelegate.swift](../../Legacy/ShadowsocksX-NG/AppDelegate.swift#L91-L114)
`applicationDidFinishLaunching` 中唯一的 `register(defaults:)` 调用注册了下列 21 个默认值。[AppDelegate.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/AppDelegate.swift#L91-L114)
| Key | 注册默认值 |
| --- | --- |
@@ -35,53 +35,53 @@
下列 key 被读写,但没有出现在这份注册默认值中:
- `ServerProfiles` 是 Profile 字典数组,`ActiveServerProfileId` 是选中 Profile 的字符串 ID。[ServerProfileManager.swift](../../Legacy/ShadowsocksX-NG/ServerProfileManager.swift#L18-L44)
- `LocalSocks5.ListenAddress.Old` 和 `LocalSocks5.ListenPort.Old` 是 PAC 重生成的上次监听地址/端口缓存。[PACUtils.swift](../../Legacy/ShadowsocksX-NG/PACUtils.swift#L20-L36)
- `Proxy4NetworkServices` 是手选 System Configuration 网络服务 ID 的数组;未保存时 UI 使用空集合。[ProxyInterfacesViewCtrl.swift](../../Legacy/ShadowsocksX-NG/ProxyInterfacesViewCtrl.swift#L21-L32) [ProxyInterfacesViewCtrl.swift](../../Legacy/ShadowsocksX-NG/ProxyInterfacesViewCtrl.swift#L60-L74)
- `LaunchAtLogin` 在 `SMLoginItemSetEnabled` 成功后写入;源中没有注册默认值。[LaunchAtLoginController.m](../../Legacy/ShadowsocksX-NG/LaunchAtLoginController.m#L43-L81)
- `ToggleRunning` 与 `SwitchProxyMode` 是 `MASShortcutView` 的关联 defaults key,并由 MASShortcut 绑定;本应用源码未注册快捷键默认组合。[PreferencesWinController.xib](../../Legacy/ShadowsocksX-NG/Base.lproj/PreferencesWinController.xib#L36-L63) [ShortcutsController.m](../../Legacy/ShadowsocksX-NG/ShortcutsController.m#L16-L28)
- `ServerProfiles` 是 Profile 字典数组,`ActiveServerProfileId` 是选中 Profile 的字符串 ID。[ServerProfileManager.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ServerProfileManager.swift#L18-L44)
- `LocalSocks5.ListenAddress.Old` 和 `LocalSocks5.ListenPort.Old` 是 PAC 重生成的上次监听地址/端口缓存。[PACUtils.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/PACUtils.swift#L20-L36)
- `Proxy4NetworkServices` 是手选 System Configuration 网络服务 ID 的数组;未保存时 UI 使用空集合。[ProxyInterfacesViewCtrl.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ProxyInterfacesViewCtrl.swift#L21-L32) [ProxyInterfacesViewCtrl.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ProxyInterfacesViewCtrl.swift#L60-L74)
- `LaunchAtLogin` 在 `SMLoginItemSetEnabled` 成功后写入;源中没有注册默认值。[LaunchAtLoginController.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/LaunchAtLoginController.m#L43-L81)
- `ToggleRunning` 与 `SwitchProxyMode` 是 `MASShortcutView` 的关联 defaults key,并由 MASShortcut 绑定;本应用源码未注册快捷键默认组合。[PreferencesWinController.xib](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/Base.lproj/PreferencesWinController.xib#L36-L63) [ShortcutsController.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ShortcutsController.m#L16-L28)
不确定性:这是一份应用源码层的 key 盘点;未实测某个用户的偏好域,也未审计 CocoaPods 内部可能持有的 framework 私有 key 或 MASShortcut 的序列化形状。
## 2. ServerProfile 模型、UUID 与选中行为
- 新建 `ServerProfile` 立即生成 `UUID().uuidString`;初始字段为 `serverHost = ""`、`serverPort = 8379`、`method = "aes-128-gcm"`,以及空的 `password`、`remark`、`plugin`、`pluginOptions`。空 `plugin` 表示禁用插件。[ServerProfile.swift](../../Legacy/ShadowsocksX-NG/ServerProfile.swift#L12-L32)
- 持久化字典字段精确为 `Id`、`ServerHost`、`ServerPort`、`Method`、`Password`、`Remark`、`Plugin`、`PluginOptions`。读取时前四个服务器字段和密码使用强制转换;缺少 `Id` 时会生成新 UUID。[ServerProfile.swift](../../Legacy/ShadowsocksX-NG/ServerProfile.swift#L159-L198)
- `ServerProfiles` 只写入 `isValid()` 的 Profile;有效性要求主机是 IPv4、IPv6 或匹配的域名,且密码非空。[ServerProfileManager.swift](../../Legacy/ShadowsocksX-NG/ServerProfileManager.swift#L35-L49) [ServerProfile.swift](../../Legacy/ShadowsocksX-NG/ServerProfile.swift#L233-L269)
- `ActiveServerProfileId` 与 Profile 的 `uuid` 做精确字符串匹配;找不到匹配项时 `getActiveProfile()` 返回 `nil`。`setActiveProfiledId` 同时更新内存和 defaults。[ServerProfileManager.swift](../../Legacy/ShadowsocksX-NG/ServerProfileManager.swift#L29-L33) [ServerProfileManager.swift](../../Legacy/ShadowsocksX-NG/ServerProfileManager.swift#L64-L75)
- `save()` 找不到活动 Profile 时只将内存 `activeProfileId` 置为 `nil`,该方法内没有移除或重写 `ActiveServerProfileId` 的 defaults 值。收到 `NOTIFY_SERVER_PROFILES_CHANGED` 后,应用仅在内存 ID 为 `nil` 且首个 Profile 有效时选择第一个 Profile 并写回 ID。[ServerProfileManager.swift](../../Legacy/ShadowsocksX-NG/ServerProfileManager.swift#L35-L49) [AppDelegate.swift](../../Legacy/ShadowsocksX-NG/AppDelegate.swift#L132-L145)
- URL 导入由 `ServerProfile(url:)` 先调用默认构造器,再填充 URL 字段;`ss://` URL 输出也不包含 `Id`。因此该导入路径本身不携带原有 UUID。[ServerProfile.swift](../../Legacy/ShadowsocksX-NG/ServerProfile.swift#L34-L144) [ServerProfile.swift](../../Legacy/ShadowsocksX-NG/ServerProfile.swift#L295-L324)
- 新建 `ServerProfile` 立即生成 `UUID().uuidString`;初始字段为 `serverHost = ""`、`serverPort = 8379`、`method = "aes-128-gcm"`,以及空的 `password`、`remark`、`plugin`、`pluginOptions`。空 `plugin` 表示禁用插件。[ServerProfile.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ServerProfile.swift#L12-L32)
- 持久化字典字段精确为 `Id`、`ServerHost`、`ServerPort`、`Method`、`Password`、`Remark`、`Plugin`、`PluginOptions`。读取时前四个服务器字段和密码使用强制转换;缺少 `Id` 时会生成新 UUID。[ServerProfile.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ServerProfile.swift#L159-L198)
- `ServerProfiles` 只写入 `isValid()` 的 Profile;有效性要求主机是 IPv4、IPv6 或匹配的域名,且密码非空。[ServerProfileManager.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ServerProfileManager.swift#L35-L49) [ServerProfile.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ServerProfile.swift#L233-L269)
- `ActiveServerProfileId` 与 Profile 的 `uuid` 做精确字符串匹配;找不到匹配项时 `getActiveProfile()` 返回 `nil`。`setActiveProfiledId` 同时更新内存和 defaults。[ServerProfileManager.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ServerProfileManager.swift#L29-L33) [ServerProfileManager.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ServerProfileManager.swift#L64-L75)
- `save()` 找不到活动 Profile 时只将内存 `activeProfileId` 置为 `nil`,该方法内没有移除或重写 `ActiveServerProfileId` 的 defaults 值。收到 `NOTIFY_SERVER_PROFILES_CHANGED` 后,应用仅在内存 ID 为 `nil` 且首个 Profile 有效时选择第一个 Profile 并写回 ID。[ServerProfileManager.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ServerProfileManager.swift#L35-L49) [AppDelegate.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/AppDelegate.swift#L132-L145)
- URL 导入由 `ServerProfile(url:)` 先调用默认构造器,再填充 URL 字段;`ss://` URL 输出也不包含 `Id`。因此该导入路径本身不携带原有 UUID。[ServerProfile.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ServerProfile.swift#L34-L144) [ServerProfile.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ServerProfile.swift#L295-L324)
## 3. 迁移相关文件索引
| 范围 | 主要历史证据 |
| --- | --- |
| 启动、默认值、运行模式、菜单动作 | [AppDelegate.swift](../../Legacy/ShadowsocksX-NG/AppDelegate.swift#L76-L251)、[MainMenu.xib](../../Legacy/ShadowsocksX-NG/Base.lproj/MainMenu.xib#L32-L160) |
| Profile schema、导入/导出、选中 ID | [ServerProfile.swift](../../Legacy/ShadowsocksX-NG/ServerProfile.swift#L12-L324)、[ServerProfileManager.swift](../../Legacy/ShadowsocksX-NG/ServerProfileManager.swift#L11-L104) |
| PAC 规则文件、编译和 GFW List 下载 | [PACUtils.swift](../../Legacy/ShadowsocksX-NG/PACUtils.swift#L12-L198)、[UserRulesController.swift](../../Legacy/ShadowsocksX-NG/UserRulesController.swift#L15-L52)、[abp.js](../../Legacy/ShadowsocksX-NG/abp.js#L1-L22) |
| 系统代理与本地 PAC 托管 | [ProxyConfHelper.m](../../Legacy/ShadowsocksX-NG/ProxyConfHelper.m#L103-L294)、[proxy_conf_helper/main.m](../../Legacy/proxy_conf_helper/main.m#L45-L223) |
| `ss-local`、Privoxy、插件安装和 launchd | [LaunchAgentUtils.swift](../../Legacy/ShadowsocksX-NG/LaunchAgentUtils.swift#L11-L202)、[LaunchAgentUtils.swift](../../Legacy/ShadowsocksX-NG/LaunchAgentUtils.swift#L281-L452)、各 `install_*.sh` 脚本 |
| QR、文本/剪贴板导入与 Profile 分享 | [ImportWindowController.swift](../../Legacy/ShadowsocksX-NG/ImportWindowController.swift#L11-L56)、[ShareServerProfilesWindowController.swift](../../Legacy/ShadowsocksX-NG/ShareServerProfilesWindowController.swift#L47-L189)、[Utils.m](../../Legacy/ShadowsocksX-NG/Utils.m#L13-L199) |
| 快捷键、登录启动、诊断 | [ShortcutsController.m](../../Legacy/ShadowsocksX-NG/ShortcutsController.m#L16-L28)、[LaunchAtLoginController.m](../../Legacy/ShadowsocksX-NG/LaunchAtLoginController.m#L43-L87)、[Diagnose.swift](../../Legacy/ShadowsocksX-NG/Diagnose.swift#L28-L96) |
| 启动、默认值、运行模式、菜单动作 | [AppDelegate.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/AppDelegate.swift#L76-L251)、[MainMenu.xib](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/Base.lproj/MainMenu.xib#L32-L160) |
| Profile schema、导入/导出、选中 ID | [ServerProfile.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ServerProfile.swift#L12-L324)、[ServerProfileManager.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ServerProfileManager.swift#L11-L104) |
| PAC 规则文件、编译和 GFW List 下载 | [PACUtils.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/PACUtils.swift#L12-L198)、[UserRulesController.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/UserRulesController.swift#L15-L52)、[abp.js](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/abp.js#L1-L22) |
| 系统代理与本地 PAC 托管 | [ProxyConfHelper.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ProxyConfHelper.m#L103-L294)、[proxy_conf_helper/main.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/proxy_conf_helper/main.m#L45-L223) |
| `ss-local`、Privoxy、插件安装和 launchd | [LaunchAgentUtils.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/LaunchAgentUtils.swift#L11-L202)、[LaunchAgentUtils.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/LaunchAgentUtils.swift#L281-L452)、各 `install_*.sh` 脚本 |
| QR、文本/剪贴板导入与 Profile 分享 | [ImportWindowController.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ImportWindowController.swift#L11-L56)、[ShareServerProfilesWindowController.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ShareServerProfilesWindowController.swift#L47-L189)、[Utils.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/Utils.m#L13-L199) |
| 快捷键、登录启动、诊断 | [ShortcutsController.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ShortcutsController.m#L16-L28)、[LaunchAtLoginController.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/LaunchAtLoginController.m#L43-L87)、[Diagnose.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/Diagnose.swift#L28-L96) |
## 4. 用户可见能力面(事实,不是保留范围)
| 能力面 | Legacy 行为 | 证据 |
| --- | --- | --- |
| 内置 PAC 与 GFW List | 规则目录为 `~/.ShadowsocksX-NG/`,输入为 `gfwlist.txt` 和 `user-rule.txt`,输出为 `gfwlist.js`;首次可从 `~/.ShadowsocksX-NE/` 移动目录。编译时 Base64 解码 GFW List、将用户规则置前并去重、替换 JavaScript 模板的 SOCKS 地址/端口;更新动作通过 `GFWListURL` 下载并重编译。 | [PACUtils.swift](../../Legacy/ShadowsocksX-NG/PACUtils.swift#L12-L17) [PACUtils.swift](../../Legacy/ShadowsocksX-NG/PACUtils.swift#L51-L150) [PACUtils.swift](../../Legacy/ShadowsocksX-NG/PACUtils.swift#L162-L198) |
| PAC 用户规则 | 菜单有 “Edit User Rules For PAC...”;窗口读取/原子写入 `user-rule.txt`,随后调用 `GeneratePACFile()` 并发出成功或失败通知。 | [MainMenu.xib](../../Legacy/ShadowsocksX-NG/Base.lproj/MainMenu.xib#L111-L121) [UserRulesController.swift](../../Legacy/ShadowsocksX-NG/UserRulesController.swift#L15-L52) |
| 自动 PAC 模式 | `ShadowsocksRunningMode == "auto"` 时启动本机 PAC server,并以 helper 的 `auto` 模式把 `http://localhost:<PacServer.ListenPort>/proxy.pac` 写入系统代理。server 只提供 `GET /proxy.pac`,MIME 为 `application/x-ns-proxy-autoconfig`,端口和是否绑定 localhost 来自 defaults。 | [AppDelegate.swift](../../Legacy/ShadowsocksX-NG/AppDelegate.swift#L231-L250) [ProxyConfHelper.m](../../Legacy/ShadowsocksX-NG/ProxyConfHelper.m#L136-L153) [ProxyConfHelper.m](../../Legacy/ShadowsocksX-NG/ProxyConfHelper.m#L206-L253) |
| 全局与手动模式 | `global` 调 helper 设置 SOCKS;启用 `LocalHTTPOn && LocalHTTP.FollowGlobal` 时一并传入 Privoxy 的 HTTP/HTTPS 地址和端口。`manual` 调 `disableProxy()`;README 描述它为不配置系统代理、由用户自行给应用配置 SOCKS5 的模式。 | [ProxyConfHelper.m](../../Legacy/ShadowsocksX-NG/ProxyConfHelper.m#L155-L193) [proxy_conf_helper/main.m](../../Legacy/proxy_conf_helper/main.m#L154-L210) [README.md](../../Legacy/README.md#L46-L49) |
| 外部 PAC | `externalPAC` 将 `ExternalPACURL` 直接传给同一 helper 的 `auto` 模式;菜单仅在该值非空时启用。输入 formatter 允许空值或 `file`、`http`、`https` scheme。 | [AppDelegate.swift](../../Legacy/ShadowsocksX-NG/AppDelegate.swift#L477-L503) [ProxyConfHelper.m](../../Legacy/ShadowsocksX-NG/ProxyConfHelper.m#L195-L221) [PACURLFormatter.swift](../../Legacy/ShadowsocksX-NG/PACURLFormatter.swift#L26-L52) |
| 网络服务与例外项 | `AutoConfigureNetworkServices == false` 时,helper 接收 `Proxy4NetworkServices` 中的服务 ID;`ProxyExceptions` 按逗号、顿号、空白分割为多个 `-x` 参数。未手选服务时 helper 按 Wi-Fi/AirPort/Ethernet 判断。 | [ProxyConfHelper.m](../../Legacy/ShadowsocksX-NG/ProxyConfHelper.m#L103-L134) [ProxyInterfacesViewCtrl.swift](../../Legacy/ShadowsocksX-NG/ProxyInterfacesViewCtrl.swift#L21-L74) [proxy_conf_helper/main.m](../../Legacy/proxy_conf_helper/main.m#L133-L170) |
| 本地 HTTP/Privoxy | 安装/生成 `com.qiuyuzhou.shadowsocksX-NG.http` LaunchAgent;模板将 `{http}` 监听地址/端口和 `{socks5}` 替换后写入 `privoxy.config`,追加 `~/.ShadowsocksX-NG/user-privoxy.config`。仅在有活动 Profile 且 `LocalHTTPOn` 为真时启动。菜单可复制 `http_proxy` 和 `https_proxy` export 命令。 | [LaunchAgentUtils.swift](../../Legacy/ShadowsocksX-NG/LaunchAgentUtils.swift#L284-L317) [LaunchAgentUtils.swift](../../Legacy/ShadowsocksX-NG/LaunchAgentUtils.swift#L343-L452) [AppDelegate.swift](../../Legacy/ShadowsocksX-NG/AppDelegate.swift#L411-L425) |
| SIP003 插件 | Profile 含 `plugin` 与 `pluginOptions`;非空插件在 `ss-local` JSON 中写成 `plugins/<plugin>` 和 `plugin_opts`。安装脚本安装/链接 `simple-obfs`、`kcptun` adapter 和 `v2ray-plugin` 到应用支持目录的 `plugins/`。 | [ServerProfile.swift](../../Legacy/ShadowsocksX-NG/ServerProfile.swift#L22-L24) [ServerProfile.swift](../../Legacy/ShadowsocksX-NG/ServerProfile.swift#L201-L219) [install_simple_obfs.sh](../../Legacy/ShadowsocksX-NG/simple-obfs/install_simple_obfs.sh#L7-L19) [install_kcptun.sh](../../Legacy/ShadowsocksX-NG/kcptun/install_kcptun.sh#L9-L23) [install_v2ray_plugin.sh](../../Legacy/ShadowsocksX-NG/v2ray-plugin/install_v2ray_plugin.sh#L17-L23) |
| QR、URL 导入和分享 | 分享窗可复制单个 URL、二维码、全部 URL,保存二维码 GIF 或全部 URL 文本;二维码由 Profile 的 `ss://` URL 生成。导入窗口从剪贴板/文本行中接受 scheme 为 `ss` 的 URL。屏幕扫描先检查/请求屏幕录制权限,再扫描所有活动显示器的 QR,并只收集以 `ss://` 开头的消息。 | [ShareServerProfilesWindowController.swift](../../Legacy/ShadowsocksX-NG/ShareServerProfilesWindowController.swift#L47-L132) [ShareServerProfilesWindowController.swift](../../Legacy/ShadowsocksX-NG/ShareServerProfilesWindowController.swift#L171-L189) [ImportWindowController.swift](../../Legacy/ShadowsocksX-NG/ImportWindowController.swift#L19-L55) [Utils.m](../../Legacy/ShadowsocksX-NG/Utils.m#L13-L47) [Utils.m](../../Legacy/ShadowsocksX-NG/Utils.m#L95-L159) [Info.plist](../../Legacy/ShadowsocksX-NG/Info.plist#L52-L53) |
| 快捷键 | 两个 MASShortcut defaults key:`ToggleRunning` 发布运行开关通知,`SwitchProxyMode` 发布模式切换通知。模式切换按启用的 `EnableSwitchMode.*` 顺序循环,外部 PAC 还要求菜单当前可用。状态菜单另有运行开关和三种模式的 key equivalents,前十个动态 Profile 菜单项使用 `1` 至 `0`。 | [ShortcutsController.m](../../Legacy/ShadowsocksX-NG/ShortcutsController.m#L16-L28) [MainMenu.xib](../../Legacy/ShadowsocksX-NG/Base.lproj/MainMenu.xib#L37-L66) [AppDelegate.swift](../../Legacy/ShadowsocksX-NG/AppDelegate.swift#L147-L199) [AppDelegate.swift](../../Legacy/ShadowsocksX-NG/AppDelegate.swift#L591-L610) |
| 登录时启动 | `LaunchAtLogin` 绑定到 `SMLoginItemSetEnabled`,helper bundle ID 为 `com.qiuyuzhou.ShadowsocksX-NG.LaunchHelper`;LaunchHelper 尝试在 `/Applications`、按名称、再按相对 bundle 路径启动主 app 后退出。 | [LaunchAtLoginController.m](../../Legacy/ShadowsocksX-NG/LaunchAtLoginController.m#L67-L87) [LaunchHelper AppDelegate.m](../../Legacy/LaunchHelper/LaunchHelper/AppDelegate.m#L18-L35) |
| 诊断和日志 | 菜单可打开 `~/Library/Logs/ss-local.log` 或将 `diagnose()` 输出写入用户选择的文本文件。诊断包含 Info.plist、列出的偏好、活动 Profile 的 `debugString()`、若干目录列表、监听端口、网卡和 launchctl 输出。 | [AppDelegate.swift](../../Legacy/ShadowsocksX-NG/AppDelegate.swift#L428-L465) [Diagnose.swift](../../Legacy/ShadowsocksX-NG/Diagnose.swift#L28-L96) |
| 内置 PAC 与 GFW List | 规则目录为 `~/.ShadowsocksX-NG/`,输入为 `gfwlist.txt` 和 `user-rule.txt`,输出为 `gfwlist.js`;首次可从 `~/.ShadowsocksX-NE/` 移动目录。编译时 Base64 解码 GFW List、将用户规则置前并去重、替换 JavaScript 模板的 SOCKS 地址/端口;更新动作通过 `GFWListURL` 下载并重编译。 | [PACUtils.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/PACUtils.swift#L12-L17) [PACUtils.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/PACUtils.swift#L51-L150) [PACUtils.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/PACUtils.swift#L162-L198) |
| PAC 用户规则 | 菜单有 “Edit User Rules For PAC...”;窗口读取/原子写入 `user-rule.txt`,随后调用 `GeneratePACFile()` 并发出成功或失败通知。 | [MainMenu.xib](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/Base.lproj/MainMenu.xib#L111-L121) [UserRulesController.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/UserRulesController.swift#L15-L52) |
| 自动 PAC 模式 | `ShadowsocksRunningMode == "auto"` 时启动本机 PAC server,并以 helper 的 `auto` 模式把 `http://localhost:<PacServer.ListenPort>/proxy.pac` 写入系统代理。server 只提供 `GET /proxy.pac`,MIME 为 `application/x-ns-proxy-autoconfig`,端口和是否绑定 localhost 来自 defaults。 | [AppDelegate.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/AppDelegate.swift#L231-L250) [ProxyConfHelper.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ProxyConfHelper.m#L136-L153) [ProxyConfHelper.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ProxyConfHelper.m#L206-L253) |
| 全局与手动模式 | `global` 调 helper 设置 SOCKS;启用 `LocalHTTPOn && LocalHTTP.FollowGlobal` 时一并传入 Privoxy 的 HTTP/HTTPS 地址和端口。`manual` 调 `disableProxy()`;README 描述它为不配置系统代理、由用户自行给应用配置 SOCKS5 的模式。 | [ProxyConfHelper.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ProxyConfHelper.m#L155-L193) [proxy_conf_helper/main.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/proxy_conf_helper/main.m#L154-L210) [README.md](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/README.md#L46-L49) |
| 外部 PAC | `externalPAC` 将 `ExternalPACURL` 直接传给同一 helper 的 `auto` 模式;菜单仅在该值非空时启用。输入 formatter 允许空值或 `file`、`http`、`https` scheme。 | [AppDelegate.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/AppDelegate.swift#L477-L503) [ProxyConfHelper.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ProxyConfHelper.m#L195-L221) [PACURLFormatter.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/PACURLFormatter.swift#L26-L52) |
| 网络服务与例外项 | `AutoConfigureNetworkServices == false` 时,helper 接收 `Proxy4NetworkServices` 中的服务 ID;`ProxyExceptions` 按逗号、顿号、空白分割为多个 `-x` 参数。未手选服务时 helper 按 Wi-Fi/AirPort/Ethernet 判断。 | [ProxyConfHelper.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ProxyConfHelper.m#L103-L134) [ProxyInterfacesViewCtrl.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ProxyInterfacesViewCtrl.swift#L21-L74) [proxy_conf_helper/main.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/proxy_conf_helper/main.m#L133-L170) |
| 本地 HTTP/Privoxy | 安装/生成 `com.qiuyuzhou.shadowsocksX-NG.http` LaunchAgent;模板将 `{http}` 监听地址/端口和 `{socks5}` 替换后写入 `privoxy.config`,追加 `~/.ShadowsocksX-NG/user-privoxy.config`。仅在有活动 Profile 且 `LocalHTTPOn` 为真时启动。菜单可复制 `http_proxy` 和 `https_proxy` export 命令。 | [LaunchAgentUtils.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/LaunchAgentUtils.swift#L284-L317) [LaunchAgentUtils.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/LaunchAgentUtils.swift#L343-L452) [AppDelegate.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/AppDelegate.swift#L411-L425) |
| SIP003 插件 | Profile 含 `plugin` 与 `pluginOptions`;非空插件在 `ss-local` JSON 中写成 `plugins/<plugin>` 和 `plugin_opts`。安装脚本安装/链接 `simple-obfs`、`kcptun` adapter 和 `v2ray-plugin` 到应用支持目录的 `plugins/`。 | [ServerProfile.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ServerProfile.swift#L22-L24) [ServerProfile.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ServerProfile.swift#L201-L219) [install_simple_obfs.sh](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/simple-obfs/install_simple_obfs.sh#L7-L19) [install_kcptun.sh](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/kcptun/install_kcptun.sh#L9-L23) [install_v2ray_plugin.sh](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/v2ray-plugin/install_v2ray_plugin.sh#L17-L23) |
| QR、URL 导入和分享 | 分享窗可复制单个 URL、二维码、全部 URL,保存二维码 GIF 或全部 URL 文本;二维码由 Profile 的 `ss://` URL 生成。导入窗口从剪贴板/文本行中接受 scheme 为 `ss` 的 URL。屏幕扫描先检查/请求屏幕录制权限,再扫描所有活动显示器的 QR,并只收集以 `ss://` 开头的消息。 | [ShareServerProfilesWindowController.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ShareServerProfilesWindowController.swift#L47-L132) [ShareServerProfilesWindowController.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ShareServerProfilesWindowController.swift#L171-L189) [ImportWindowController.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ImportWindowController.swift#L19-L55) [Utils.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/Utils.m#L13-L47) [Utils.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/Utils.m#L95-L159) [Info.plist](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/Info.plist#L52-L53) |
| 快捷键 | 两个 MASShortcut defaults key:`ToggleRunning` 发布运行开关通知,`SwitchProxyMode` 发布模式切换通知。模式切换按启用的 `EnableSwitchMode.*` 顺序循环,外部 PAC 还要求菜单当前可用。状态菜单另有运行开关和三种模式的 key equivalents,前十个动态 Profile 菜单项使用 `1` 至 `0`。 | [ShortcutsController.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ShortcutsController.m#L16-L28) [MainMenu.xib](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/Base.lproj/MainMenu.xib#L37-L66) [AppDelegate.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/AppDelegate.swift#L147-L199) [AppDelegate.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/AppDelegate.swift#L591-L610) |
| 登录时启动 | `LaunchAtLogin` 绑定到 `SMLoginItemSetEnabled`,helper bundle ID 为 `com.qiuyuzhou.ShadowsocksX-NG.LaunchHelper`;LaunchHelper 尝试在 `/Applications`、按名称、再按相对 bundle 路径启动主 app 后退出。 | [LaunchAtLoginController.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/LaunchAtLoginController.m#L67-L87) [LaunchHelper AppDelegate.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/LaunchHelper/LaunchHelper/AppDelegate.m#L18-L35) |
| 诊断和日志 | 菜单可打开 `~/Library/Logs/ss-local.log` 或将 `diagnose()` 输出写入用户选择的文本文件。诊断包含 Info.plist、列出的偏好、活动 Profile 的 `debugString()`、若干目录列表、监听端口、网卡和 launchctl 输出。 | [AppDelegate.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/AppDelegate.swift#L428-L465) [Diagnose.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/Diagnose.swift#L28-L96) |
README 也列出 SIP003 插件、GFW List PAC 更新、二维码/URL 分享、剪贴板导入、屏幕 QR 扫描、PAC 自定义规则和 Privoxy HTTP proxy;它同时说明 `ss-local` 通过 launchd 运行,退出 app 后仍可能继续运行。[README.md](../../Legacy/README.md#L35-L49)
README 也列出 SIP003 插件、GFW List PAC 更新、二维码/URL 分享、剪贴板导入、屏幕 QR 扫描、PAC 自定义规则和 Privoxy HTTP proxy;它同时说明 `ss-local` 通过 launchd 运行,退出 app 后仍可能继续运行。[README.md](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/README.md#L35-L49)
## 5. 敏感字段与暴露位置
@@ -92,9 +92,9 @@ README 也列出 SIP003 插件、GFW List PAC 更新、二维码/URL 分享、
| 屏幕扫描结果 | 每个发现的 QR `messageString` 会被 `NSLog`,并且以 `ss://` 开头的 URL 会进入导入通知。 | 日志是否被导出、保留多久或谁可访问,源码未说明。 |
| `PluginOptions`、`ExternalPACURL`、`GFWListURL` | Plugin options 被写入 `ss-local` JSON;外部 PAC URL 传给 helper;GFW List URL 用于下载。 | 这些字段不一定含凭据,但其值可能含连接、路径或查询信息;是否敏感取决于具体值。 |
对应一手证据:[ServerProfile.swift](../../Legacy/ShadowsocksX-NG/ServerProfile.swift#L188-L219) [ServerProfile.swift](../../Legacy/ShadowsocksX-NG/ServerProfile.swift#L295-L324) [LaunchAgentUtils.swift](../../Legacy/ShadowsocksX-NG/LaunchAgentUtils.swift#L133-L160) [ShareServerProfilesWindowController.swift](../../Legacy/ShadowsocksX-NG/ShareServerProfilesWindowController.swift#L47-L132) [Utils.m](../../Legacy/ShadowsocksX-NG/Utils.m#L109-L118) [ProxyConfHelper.m](../../Legacy/ShadowsocksX-NG/ProxyConfHelper.m#L195-L221) [PACUtils.swift](../../Legacy/ShadowsocksX-NG/PACUtils.swift#L172-L198)
对应一手证据:[ServerProfile.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ServerProfile.swift#L188-L219) [ServerProfile.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ServerProfile.swift#L295-L324) [LaunchAgentUtils.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/LaunchAgentUtils.swift#L133-L160) [ShareServerProfilesWindowController.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ShareServerProfilesWindowController.swift#L47-L132) [Utils.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/Utils.m#L109-L118) [ProxyConfHelper.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ProxyConfHelper.m#L195-L221) [PACUtils.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/PACUtils.swift#L172-L198)
诊断对活动 Profile 的 `ServerHost` 与 `Password` 以同长度星号输出,但仍输出端口、method、plugin 和 `PluginOptions`;诊断偏好列表包含 `GFWListURL` 和代理例外项。[ServerProfile.swift](../../Legacy/ShadowsocksX-NG/ServerProfile.swift#L222-L230) [Diagnose.swift](../../Legacy/ShadowsocksX-NG/Diagnose.swift#L38-L70)
诊断对活动 Profile 的 `ServerHost` 与 `Password` 以同长度星号输出,但仍输出端口、method、plugin 和 `PluginOptions`;诊断偏好列表包含 `GFWListURL` 和代理例外项。[ServerProfile.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/ServerProfile.swift#L222-L230) [Diagnose.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/Diagnose.swift#L38-L70)
## 6. 仅在 Legacy 中观察到的依赖/机制
@@ -105,7 +105,7 @@ README 也列出 SIP003 插件、GFW List PAC 更新、二维码/URL 分享、
| 特权 `proxy_conf_helper` | 安装到 `/Library/Application Support/ShadowsocksX-NG/`,脚本设置 `root:admin`、可执行和 setuid;helper 使用 Authorization 与 SystemConfiguration 写代理偏好。 |
| `SMLoginItem` + `LaunchHelper.app` | 登录时启动依赖辅助 app 和 `SMLoginItemSetEnabled`。 |
对应证据:[Podfile](../../Legacy/Podfile#L3-L27) [LaunchAgentUtils.swift](../../Legacy/ShadowsocksX-NG/LaunchAgentUtils.swift#L31-L83) [LaunchAgentUtils.swift](../../Legacy/ShadowsocksX-NG/LaunchAgentUtils.swift#L284-L378) [install_helper.sh](../../Legacy/ShadowsocksX-NG/install_helper.sh#L10-L14) [proxy_conf_helper/main.m](../../Legacy/proxy_conf_helper/main.m#L105-L218) [README.md](../../Legacy/README.md#L35-L49)
对应证据:[Podfile](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/Podfile#L3-L27) [LaunchAgentUtils.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/LaunchAgentUtils.swift#L31-L83) [LaunchAgentUtils.swift](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/LaunchAgentUtils.swift#L284-L378) [install_helper.sh](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/ShadowsocksX-NG/install_helper.sh#L10-L14) [proxy_conf_helper/main.m](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/proxy_conf_helper/main.m#L105-L218) [README.md](https://github.com/shadowsocks/ShadowsocksX-NG/blob/legacy-v1/README.md#L35-L49)
“仅在 Legacy 中观察到”只描述本次审计范围内的实现构成;本报告没有审计或断言新实现是否使用、替换或保留其中任一依赖。