mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-01 15:43:45 +00:00
docs: add English documentation versions
This commit is contained in:
@@ -2,14 +2,14 @@
|
||||
|
||||
> 🚧 重新实现进行中 —— 本仓库正在使用现代技术栈重写 ShadowsocksX-NG macOS 客户端。
|
||||
|
||||
新实现位于 [`ShadowsocksX-NG2/`](ShadowsocksX-NG2/README.md),面向 macOS 15+ / Apple Silicon(arm64)。外部二进制使用上游发布产物,不再通过 git 子模块在仓库内构建。
|
||||
新实现位于 [`ShadowsocksX-NG2/`](ShadowsocksX-NG2/README.zh-CN.md),面向 macOS 15+ / Apple Silicon(arm64)。外部二进制使用上游发布产物,不再通过 git 子模块在仓库内构建。
|
||||
|
||||
## 开发入口
|
||||
|
||||
- [首次构建与常用命令](ShadowsocksX-NG2/README.md#开发构建与测试)
|
||||
- [开发指南](docs/development.md):统一验证、测试证据、代码风格工具与 Git hooks。
|
||||
- [工程与专题文档](ShadowsocksX-NG2/README.md#文档导航):源码布局、供应链与签名、规则快照、配置说明。
|
||||
- [术语表](GLOSSARY.md)与[架构决策记录(ADR)](docs/adr/);文档语言约定见[开发指南](docs/development.md#领域文档语言)。
|
||||
- [首次构建与常用命令](ShadowsocksX-NG2/README.zh-CN.md#开发构建与测试)
|
||||
- [开发指南](docs/development.zh-CN.md):统一验证、测试证据、代码风格工具与 Git hooks。
|
||||
- [工程与专题文档](ShadowsocksX-NG2/README.zh-CN.md#文档导航):源码布局、供应链与签名、规则快照、配置说明。
|
||||
- [术语表](GLOSSARY.md)与[架构决策记录(ADR)](docs/adr/);文档语言约定见[开发指南](docs/development.zh-CN.md#领域文档语言)。
|
||||
|
||||
## 旧版归档
|
||||
|
||||
|
||||
+20
-18
@@ -1,14 +1,16 @@
|
||||
# ShadowsocksX-NG2
|
||||
|
||||
ShadowsocksX-NG 的现代化重写版本,面向 macOS 15+ / Apple Silicon(arm64)。新工程的源码与构建配置位于本目录,旧版归档见[仓库首页](../README.md#旧版归档)。
|
||||
English | [简体中文](README.zh-CN.md)
|
||||
|
||||
依赖的 `sslocal`、`v2ray-plugin` 与 Cloak `ck-client` 使用上游发布的可执行文件。HTTP 入站由 shadowsocks-rust 直接提供,不再携带 Legacy Privoxy。
|
||||
ShadowsocksX-NG2 is a modern rewrite of ShadowsocksX-NG for macOS 15+ and Apple Silicon (arm64). Its source code and build configuration are in this directory. The archived legacy version is described on the [repository home page](../README.md#旧版归档).
|
||||
|
||||
## 开发、构建与测试
|
||||
The app uses upstream releases of `sslocal`, `v2ray-plugin`, and Cloak `ck-client`. HTTP inbound traffic is handled directly by shadowsocks-rust; Legacy Privoxy is no longer bundled.
|
||||
|
||||
准备 Xcode/Swift 工具链、[Task](https://taskfile.dev/)、[XcodeGen](https://github.com/yonaskolb/XcodeGen) 和 SwiftLint;工具基线与安装说明见[开发指南](../docs/development.md#代码风格工具)。
|
||||
## Development, build, and test
|
||||
|
||||
从本目录执行首次构建,先获取外部二进制,再生成工程:
|
||||
Install the Xcode/Swift toolchain, [Task](https://taskfile.dev/), [XcodeGen](https://github.com/yonaskolb/XcodeGen), and SwiftLint. See the [development guide](../docs/development.md#code-style-tools) for tool versions and installation instructions.
|
||||
|
||||
From this directory, fetch the external binaries before generating the Xcode project for the first build:
|
||||
|
||||
```bash
|
||||
Scripts/fetch-external-binaries.sh
|
||||
@@ -16,27 +18,27 @@ task gen:prj
|
||||
task build
|
||||
```
|
||||
|
||||
Debug 默认使用 ad-hoc 签名,无需证书;Developer ID 配置与发布门槛见[签名与分发](../docs/distribution.md)。工程配置以 `project.yml` 为准,生成的 `.xcodeproj` 不入库。
|
||||
Debug builds use ad-hoc signing by default and do not require a certificate. See [Signing and distribution](../docs/distribution.md) for Developer ID configuration and release requirements. `project.yml` is the source of truth for project settings; the generated `.xcodeproj` is not checked in.
|
||||
|
||||
从仓库根目录运行统一验证:
|
||||
Run the unified validation workflow from the repository root:
|
||||
|
||||
```bash
|
||||
task --exit-code --taskfile ShadowsocksX-NG2/Taskfile.dist.yml validate
|
||||
# 按指定顺序只运行所需关卡:
|
||||
# Run only the requested stages, in the specified order:
|
||||
task --exit-code --taskfile ShadowsocksX-NG2/Taskfile.dist.yml validate -- lint test
|
||||
```
|
||||
|
||||
默认顺序为 `lint → test → test:smoke → build`,失败即停止,日志与结果保存在 `ShadowsocksX-NG2/.build/validation/`。在本目录内可直接运行 `task --exit-code validate`。单项任务、证据读取与测试边界见[开发指南](../docs/development.md)。
|
||||
The default order is `lint → test → test:smoke → build`; execution stops on the first failure. Logs and results are saved in `ShadowsocksX-NG2/.build/validation/`. From this directory, you can run `task --exit-code validate`. See the [development guide](../docs/development.md) for individual tasks, evidence, and test boundaries.
|
||||
|
||||
## 文档导航
|
||||
## Documentation
|
||||
|
||||
| 文档 | 内容 |
|
||||
| Document | Contents |
|
||||
| --- | --- |
|
||||
| [开发、构建与测试](../docs/development.md) | 验证入口与产物、单项任务、代码风格工具、Git hooks、文档语言 |
|
||||
| [工程结构与源码搜索](../docs/project-structure.md) | 源码分层、测试目录、跨 target 文件约束、搜索方法 |
|
||||
| [外部二进制、签名与分发](../docs/distribution.md) | 供应链校验、Debug/Release 签名、发布门槛、依赖升级 |
|
||||
| [内置规则快照](../docs/rule-snapshots.md) | 来源、转换语义、维护脚本与离线检查 |
|
||||
| [订阅、插件与监听配置](../docs/configuration.md) | 订阅资料、用户插件、Cloak、本地与局域网监听、配置恢复 |
|
||||
| [服务器库加密格式](../docs/server-library-encryption-format.zh-CN.md) | 加密文件格式与外部解密约定([English](../docs/server-library-encryption-format.md)) |
|
||||
| [Development, build, and test](../docs/development.md) | Validation entry points and artifacts, individual tasks, code style tools, Git hooks, and document language |
|
||||
| [Project structure and source search](../docs/project-structure.md) | Source layers, test directories, cross-target file constraints, and search methods |
|
||||
| [External binaries, signing, and distribution](../docs/distribution.md) | Supply-chain checks, Debug/Release signing, release requirements, and dependency updates |
|
||||
| [Bundled rule snapshots](../docs/rule-snapshots.md) | Sources, conversion semantics, maintenance scripts, and offline checks |
|
||||
| [Subscriptions, plugins, and listener configuration](../docs/configuration.md) | Subscription data, user plugins, Cloak, local and LAN listeners, and configuration recovery |
|
||||
| [Server library encryption format](../docs/server-library-encryption-format.md) | Encrypted file format and external decryption contract |
|
||||
|
||||
领域术语与已定设计见[术语表](../GLOSSARY.md)和[架构决策记录](../docs/adr/)。`docs/design/` 与 `docs/research/` 为历史设计和研究记录,判断当前实现时以代码为准。
|
||||
The [glossary](../GLOSSARY.md) and [architecture decision records](../docs/adr/) are maintained in Simplified Chinese and describe domain terminology and accepted designs. `docs/design/` and `docs/research/` are historical design and research records. Use the current code as the source of truth for implementation behavior.
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# ShadowsocksX-NG2
|
||||
|
||||
[English](README.md) | 简体中文
|
||||
|
||||
ShadowsocksX-NG 的现代化重写版本,面向 macOS 15+ / Apple Silicon(arm64)。新工程的源码与构建配置位于本目录,旧版归档见[仓库首页](../README.md#旧版归档)。
|
||||
|
||||
依赖的 `sslocal`、`v2ray-plugin` 与 Cloak `ck-client` 使用上游发布的可执行文件。HTTP 入站由 shadowsocks-rust 直接提供,不再携带 Legacy Privoxy。
|
||||
|
||||
## 开发、构建与测试
|
||||
|
||||
准备 Xcode/Swift 工具链、[Task](https://taskfile.dev/)、[XcodeGen](https://github.com/yonaskolb/XcodeGen) 和 SwiftLint;工具基线与安装说明见[开发指南](../docs/development.zh-CN.md#代码风格工具)。
|
||||
|
||||
从本目录执行首次构建,先获取外部二进制,再生成工程:
|
||||
|
||||
```bash
|
||||
Scripts/fetch-external-binaries.sh
|
||||
task gen:prj
|
||||
task build
|
||||
```
|
||||
|
||||
Debug 默认使用 ad-hoc 签名,无需证书;Developer ID 配置与发布门槛见[签名与分发](../docs/distribution.zh-CN.md)。工程配置以 `project.yml` 为准,生成的 `.xcodeproj` 不入库。
|
||||
|
||||
从仓库根目录运行统一验证:
|
||||
|
||||
```bash
|
||||
task --exit-code --taskfile ShadowsocksX-NG2/Taskfile.dist.yml validate
|
||||
# 按指定顺序只运行所需关卡:
|
||||
task --exit-code --taskfile ShadowsocksX-NG2/Taskfile.dist.yml validate -- lint test
|
||||
```
|
||||
|
||||
默认顺序为 `lint → test → test:smoke → build`,失败即停止,日志与结果保存在 `ShadowsocksX-NG2/.build/validation/`。在本目录内可直接运行 `task --exit-code validate`。单项任务、证据读取与测试边界见[开发指南](../docs/development.zh-CN.md)。
|
||||
|
||||
## 文档导航
|
||||
|
||||
| 文档 | 内容 |
|
||||
| --- | --- |
|
||||
| [开发、构建与测试](../docs/development.zh-CN.md) | 验证入口与产物、单项任务、代码风格工具、Git hooks、文档语言 |
|
||||
| [工程结构与源码搜索](../docs/project-structure.zh-CN.md) | 源码分层、测试目录、跨 target 文件约束、搜索方法 |
|
||||
| [外部二进制、签名与分发](../docs/distribution.zh-CN.md) | 供应链校验、Debug/Release 签名、发布门槛、依赖升级 |
|
||||
| [内置规则快照](../docs/rule-snapshots.zh-CN.md) | 来源、转换语义、维护脚本与离线检查 |
|
||||
| [订阅、插件与监听配置](../docs/configuration.zh-CN.md) | 订阅资料、用户插件、Cloak、本地与局域网监听、配置恢复 |
|
||||
| [服务器库加密格式](../docs/server-library-encryption-format.zh-CN.md) | 加密文件格式与外部解密约定([English](../docs/server-library-encryption-format.md)) |
|
||||
|
||||
领域术语与已定设计见[术语表](../GLOSSARY.md)和[架构决策记录](../docs/adr/)。`docs/design/` 与 `docs/research/` 为历史设计和研究记录,判断当前实现时以代码为准。
|
||||
@@ -6,7 +6,7 @@ Status: Accepted (2026-10-05)
|
||||
|
||||
边界由 `Tests/SkeletonTests.swift` 的守卫断言,且一律**正面圈定**而非禁令加豁免登记:视图层的守卫只扫 `App/UI/`(不得引用运行时控制器、工作流实现协作者、原始目录/凭据存储类型),`Domain/` 的守卫只断言根下无平铺 `.swift`、树下只有 `.swift`。把文件放进 `Application/`、`PlatformEffects/` 或某个主题目录,即自动落在扫描范围之外,无需登记豁免;新增主题目录也不需要改断言。
|
||||
|
||||
各目录的划分与各自职责由 [工程结构文档](../project-structure.md)维护——本 ADR 只固定组织原则,不维护清单。
|
||||
各目录的划分与各自职责由 [工程结构文档](../project-structure.zh-CN.md)维护——本 ADR 只固定组织原则,不维护清单。
|
||||
|
||||
## Considered options
|
||||
|
||||
@@ -24,4 +24,4 @@ XcodeGen 带来两条**在代码里不可见**的约束:
|
||||
|
||||
跨进程契约按**文件**引用而非按目录:wrapper 与 system proxy helper 两个 target 各自显式列出所需的 `Domain/` 文件,因为它们只该拿到跨进程契约,而不是整个领域层。代价是目录内移动文件必须同步 `project.yml`;漏改时 xcodegen 以 `missing source directory` 直接失败,不静默。
|
||||
|
||||
在目录内移动文件因此是低代价操作:模块名、类型名与 import 均不变,`task format` 与 lint 基线不变,版本历史可继续追溯。这条约定分散记录在 `project.yml` 的分层注释、[工程结构文档](../project-structure.md)与本 ADR;新增主题目录时只需同步前两处(`DomainLayoutTests` 失败信息里列举的目录名会过期,断言本身不会失败)。
|
||||
在目录内移动文件因此是低代价操作:模块名、类型名与 import 均不变,`task format` 与 lint 基线不变,版本历史可继续追溯。这条约定分散记录在 `project.yml` 的分层注释、[工程结构文档](../project-structure.zh-CN.md)与本 ADR;新增主题目录时只需同步前两处(`DomainLayoutTests` 失败信息里列举的目录名会过期,断言本身不会失败)。
|
||||
|
||||
+32
-30
@@ -1,57 +1,59 @@
|
||||
# 订阅、插件与监听配置
|
||||
# Subscriptions, Plugins, and Listener Configuration
|
||||
|
||||
[返回工程首页](../ShadowsocksX-NG2/README.md)
|
||||
[Project home](../ShadowsocksX-NG2/README.md) | [简体中文](configuration.zh-CN.md)
|
||||
|
||||
## 订阅资料
|
||||
## Subscription information
|
||||
|
||||
面向订阅服务提供方的字段格式、嵌套分组扩展和响应要求见 [SIP-008 订阅扩展说明](subscription-provider-extension.zh-CN.md)。
|
||||
See the [SIP-008 subscription extension guide](subscription-provider-extension.md) for field formats, the nested-group extension, and response requirements for subscription providers.
|
||||
|
||||
订阅可在 SIP-008 JSON 根级提供 `bytes_used`、`bytes_remaining`(非负整数,最大为 UInt64),以及自定义的 `expires_at`(服务到期时间)、`traffic_reset_at`(流量重置时间)。日期值使用 RFC 3339 字符串,省略或 `null` 表示未提供;无效资料仅忽略该项,不影响有效服务器或分组资料。
|
||||
A SIP-008 JSON root may contain `bytes_used` and `bytes_remaining` (non-negative integers up to UInt64), plus the custom fields `expires_at` (service expiration time) and `traffic_reset_at` (traffic reset time). Date values use RFC 3339 strings; an omitted field or `null` means no value was provided. An invalid item is ignored without affecting valid server or group information.
|
||||
|
||||
成功刷新完整替换白名单订阅资料,省略或无效的字段清除旧值;失败或取消保留最后成功资料与独立成功时间。资料和目录记录同文档原子保存,不另存原始响应或含密码的服务器明文快照;密码和插件参数沿用 Keychain 存储。旧目录文件缺少成功时间时不猜测。
|
||||
A successful refresh replaces all allowlisted subscription information. Omitted or invalid fields clear their previous values; a failure or cancellation preserves the last successful information and its separate success timestamp. Information and the catalog record are saved atomically in the same document. The app does not separately save the raw response or a plaintext snapshot of server data containing passwords. Passwords and plugin parameters continue to use Keychain storage. If an older catalog file has no success timestamp, the app does not infer one.
|
||||
|
||||
订阅卡片显示返回的有效资料;仅在两项流量都有效且总量大于零时显示消耗进度。日期按用户当前语言与时区显示,到期和重置资料不改变代理启停或激活行为。
|
||||
Subscription cards show valid information returned by the provider. A usage progress indicator appears only when both traffic values are valid and the total is greater than zero. Dates use the user's current language and time zone. Expiration and reset information do not change proxy start, stop, or activation behavior.
|
||||
|
||||
## 用户插件目录
|
||||
## User plugin catalog
|
||||
|
||||
`PluginCatalog` 合成当前托管清单与本机 `plugins.json` 用户记录,名称精确区分大小写。用户映射优先,失效 override 不退回托管程序;删除 override 恢复当前 app 默认值。文件读取错误保留阻断事实,不按空映射处理。用户路径、本地启动参数与插件传输模式不进入服务器资料、订阅或分享文件。用户路径也不进入诊断报告。
|
||||
`PluginCatalog` combines the current managed manifest with user records from the local `plugins.json`; names are case-sensitive. A user mapping takes precedence. If an override is invalid, the app does not fall back to the managed program; deleting the override restores the app's current default. A file-read error remains a blocking fact and is not treated as an empty mapping. User paths, local launch arguments, and plugin transport modes are not included in server information, subscriptions, or share files. User paths are also excluded from diagnostic reports.
|
||||
|
||||
设置页底部的“插件”区域统一展示内置与用户插件,按记录展示全部名称,支持新增、编辑名称集合与路径、逐名称覆盖内置、删除记录与恢复同名内置。用户与内置记录可以设置插件传输模式 `tcp_only` 或 `tcp_and_udp`;默认 `tcp_only`,生成 sslocal 配置时省略 `plugin_mode`。应用不探测插件的 SIP003u 支持。内置编辑器中的“清空参数”只清空草稿启动参数并保留模式,保存后生效。名称与启动参数使用列表编辑,启动参数保留顺序和完整字符串;路径支持手动输入与文件选择。读取失败时隐藏条目并提供只读重试,管理 UI 不提供整体重置。映射提交与显式修复 interface 在组合根统一接入表单、激活与诊断。保存成功后刷新激活候选并异步收敛,运行失败不撤销保存。没有文件替换监控。`plugins.json` v1 每项独立迁移为带持久身份的用户记录,下一次保存写入 v2,不按相同路径合并。现有内置程序同时提供 `v2ray-plugin` 和 `v2ray-plugin-v1`。详见 [ADR-0033](adr/0033-plugin-records-own-aliases-and-local-arguments.md)。
|
||||
The **Plugins** section at the bottom of Settings shows built-in and user plugins together. It displays all names for each record and supports adding records, editing their name sets and paths, overriding built-ins by name, deleting records, and restoring a same-named built-in. User and built-in records can use the `tcp_only` or `tcp_and_udp` plugin transport mode. The default is `tcp_only`; generated sslocal configuration omits `plugin_mode` in that case. The app does not probe whether a plugin supports SIP003u.
|
||||
|
||||
`MacOSPluginInspection` 只读取下载隔离、签名与系统评估事实,不启动用户插件、不修改系统策略。检查失败或不适用为未知;策略评估拒绝不等于实际执行禁令,也不阻止映射保存或激活。真实 macOS 拦截提示不由 hostless 替身测试证明。
|
||||
In the built-in editor, **Clear parameters** clears only the draft launch arguments and preserves the transport mode; the change takes effect when saved. Names and launch arguments are edited as lists. Launch arguments preserve their order and complete strings. Paths can be entered manually or selected from a file chooser. When reading fails, the UI hides the entries and offers a read-only retry; plugin management has no global reset. At the composition root, mapping commits and the explicit repair interface are wired into the form, activation, and diagnostics. After a successful save, the app refreshes activation candidates and converges asynchronously; a runtime failure does not undo the save. The app does not monitor files for replacement. Each `plugins.json` v1 entry migrates independently into a user record with persistent identity; the next save writes v2, with no merging by path. The existing built-in program is available as both `v2ray-plugin` and `v2ray-plugin-v1`. See [ADR-0033](adr/0033-plugin-records-own-aliases-and-local-arguments.md).
|
||||
|
||||
## Cloak 托管插件
|
||||
`MacOSPluginInspection` reads only download-quarantine, signature, and system-assessment facts. It does not launch user plugins or change system policy. A failed or inapplicable check is unknown; a policy assessment that denies execution does not prove that execution is actually prohibited and does not block saving a mapping or activating it. Hostless test doubles do not prove that macOS will block a plugin in practice.
|
||||
|
||||
随应用提供官方 [Cloak v2.12.0](https://github.com/cbeuw/Cloak/releases/tag/v2.12.0) 的 darwin-arm64 `ck-client`,服务器引用名称为 `cloak` 或 `cloak-v2`,两者共享一条内置记录。`ck-client` 是打包文件名,不是可引用名称。同名用户插件仍优先;失效覆盖不会自动回退到内置程序。
|
||||
## Managed Cloak plugin
|
||||
|
||||
服务器插件选项原样传给上游,不进行 Cloak 字段语义校验。可以使用分号分隔的 `Key=value` 参数,或使用外部 JSON 配置文件的绝对路径。外部文件由用户维护,应用不复制文件、不管理文件凭据或生命周期;分享路径不包含文件内容,接收方需要自行提供对应文件。本地启动参数仍独立保存,不随服务器资料分享。
|
||||
The app includes the official darwin-arm64 `ck-client` from [Cloak v2.12.0](https://github.com/cbeuw/Cloak/releases/tag/v2.12.0). Servers refer to it by the name `cloak` or `cloak-v2`; both names share one built-in record. `ck-client` is the packaged filename, not a reference name. A same-named user plugin still takes precedence, and an invalid override does not fall back automatically to the built-in program.
|
||||
|
||||
[固定版本配置解析](https://github.com/cbeuw/Cloak/blob/v2.12.0/internal/client/state.go)存在以下限制:只有同时含 `;` 和 `=` 的字符串才被识别为参数列表,单个 `Key=value` 会被当作文件路径;转义分号仍会被切分,引号和反斜杠也不能按通用 SIP003 转义方式可靠传递;空段会终止后续解析。需要复杂值时可自行维护 JSON 文件。提供 `UID`、`PublicKey`、`ServerName`、`EncryptionMethod`,并按需显式设置 `NumConn=4`,不能依赖 README 所述的默认 4;省略时固定版本实现关闭连接复用。显式端点字段会覆盖 SIP003 环境变量,因此通常应省略 `LocalHost`、`LocalPort`、`RemoteHost`、`RemotePort`。
|
||||
Server plugin options are passed upstream unchanged; the app does not validate Cloak option semantics. Options may be semicolon-separated `Key=value` arguments or an absolute path to an external JSON configuration file. The user maintains an external file; the app does not copy it or manage its credentials or lifecycle. Shared paths do not include file contents, so recipients must provide the corresponding file themselves. Local launch arguments are stored separately and are not shared with server information.
|
||||
|
||||
默认插件传输模式为 `tcp_only`。上游 `UDP=true` 切换为 UDP 监听,不代表同时承载 TCP 与 UDP 的 SIP003u 支持;应用仍允许用户设置现有传输模式,本次只验收 TCP 配置接入。托管程序的 GPL-3.0 许可证、来源与第三方声明随应用装入 `Contents/Resources/ThirdParty/Cloak/`,源码与构建入口见其中 NOTICE。
|
||||
The [configuration parser at the pinned version](https://github.com/cbeuw/Cloak/blob/v2.12.0/internal/client/state.go) has these limitations: a string is recognized as an argument list only if it contains both `;` and `=`, so a single `Key=value` is treated as a file path; escaped semicolons are still split; quotes and backslashes cannot be reliably passed using generic SIP003 escaping; and an empty segment stops further parsing. For complex values, maintain a JSON file yourself. Provide `UID`, `PublicKey`, `ServerName`, and `EncryptionMethod`; set `NumConn=4` explicitly if needed. Do not rely on the README's stated default of 4: the pinned implementation disables connection reuse when this value is omitted. Explicit endpoint fields override SIP003 environment variables, so usually omit `LocalHost`, `LocalPort`, `RemoteHost`, and `RemotePort`.
|
||||
|
||||
## 本地与局域网监听
|
||||
The default plugin transport mode is `tcp_only`. Upstream `UDP=true` switches to a UDP listener; it does not indicate SIP003u support for carrying TCP and UDP at the same time. The app still lets users set the existing transport mode; this integration is verified for TCP configuration only. Cloak's GPL-3.0 license, source, and third-party notices are bundled at `Contents/Resources/ThirdParty/Cloak/`; see the NOTICE there for source and build entry points.
|
||||
|
||||
后台代理客户端运行时,本地 SOCKS/HTTP 始终监听 `127.0.0.1`,默认端口为 `11086`/`11087`;本地 SOCKS 固定开启 UDP 中继。局域网入口默认关闭,使用独立端口 `11088`/`11089`,支持 IPv4、双栈和仅 IPv6 三种方式。四端口必须在 `1025...65535` 内且全部不同。
|
||||
## Local and LAN listeners
|
||||
|
||||
局域网 SOCKS 的 UDP 中继和 SOCKS/HTTP 共用的身份验证独立设置。用户名与密码随监听配置整体加密保存,Keychain 仅保存监听专用主密钥;运行时派生当前用户专用的认证文件;上游认证失败日志中的密码会隐藏。设置只检查 TCP 占用,不设置独立 UDP 端口。首页命令按选择地址使用本地或局域网端口,局域网身份验证开启时加入编码后的凭据;tooltip 显示完整命令。
|
||||
While the background proxy client is running, local SOCKS and HTTP always listen on `127.0.0.1`, using ports `11086` and `11087` by default. Local SOCKS always enables UDP relay. The LAN entry point is disabled by default and uses separate ports `11088` and `11089`. It supports IPv4, dual-stack, and IPv6-only modes. All four ports must be in `1025...65535` and must be distinct.
|
||||
|
||||
监听配置使用标准 age 文件格式与独立 X25519 identity,单独加密保存在 `proxy-listening.age`,只包含本地与局域网监听相关值。代理模式、规则默认动作、代理服务开关、系统代理开关和附加系统代理例外以 `proxyControl.` 前缀的独立键保存在 UserDefaults;运行计算的内存快照不代表统一持久化文档。监听仍采用联合校验与原子文件保存;UserDefaults 偏好不提供同步落盘失败的报错与回滚。
|
||||
UDP relay for LAN SOCKS and authentication shared by SOCKS/HTTP have independent settings. The username and password are stored together in the encrypted listener configuration; Keychain stores only the listener-specific master key. At runtime, the app derives an authentication file specific to the current user. Passwords in upstream authentication-failure logs are redacted. Settings check TCP port use only and do not configure a separate UDP port. The home-page command uses a local or LAN port according to the selected address. When LAN authentication is enabled, it includes encoded credentials; the tooltip shows the full command.
|
||||
|
||||
不迁移旧 `settings.json`:新监听文件与新增偏好缺值时使用默认值,需要重新设置现有开发配置;旧文件不读取、不删除,遗留 Keychain 条目不访问、不清理。已有语言、静默启动、测试 URL 等独立偏好及服务器资料保持原状。监听读取失败会阻断正常启动,包括静默启动,并显示只含“重置监听配置”的说明性 alert。用户确认后创建新监听主密钥并原子保存默认配置,默认关闭局域网监听。重置成功前保留原文件及已有后台代理和系统代理;成功后按默认配置与原有开关意图收敛。不迁移旧 `proxy-listening.enc` 或 `proxy-listening.json`,不读取或清理旧逐项 Keychain 凭据;发现旧文件时同样提示确认重置。设计见 [ADR-0034](adr/0034-separate-local-and-lan-listeners.md)、[ADR-0040](adr/0040-keep-settings-recovery-simple.md)、[ADR-0041](adr/0041-separate-preferences-from-proxy-listening-configuration.md)、[ADR-0042](adr/0042-encrypt-proxy-listening-configuration.md) 和 [ADR-0050](adr/0050-age-encrypted-configuration-storage.md)。
|
||||
Listener settings use the standard age file format and a separate X25519 identity. They are encrypted in `proxy-listening.age` and contain only local and LAN listener values. Proxy mode, the default rule action, proxy-service enablement, system-proxy enablement, and additional system-proxy exceptions are stored separately in UserDefaults under keys prefixed with `proxyControl.`. An in-memory snapshot of runtime calculations is not a unified persistent document. Listener settings are still jointly validated and saved atomically; UserDefaults preferences do not report or roll back a failed synchronized disk write.
|
||||
|
||||
## 应用支持目录
|
||||
The app does not migrate the old `settings.json`. Missing values in the new listener file and added preferences use defaults, so existing development configuration must be set again. The old file is not read or deleted, and legacy Keychain entries are not accessed or cleaned up. Existing independent preferences such as language, silent launch, and test URL, as well as server information, remain unchanged. A listener-read failure blocks normal startup, including silent launch, and shows an explanatory alert with only the **Reset listener configuration** action. After the user confirms, the app creates a new listener master key and atomically saves default settings, with LAN listening disabled. Until reset succeeds, the old file and any existing background proxy or system proxy are preserved. After success, the app converges using the defaults and the original enablement intent. The app does not migrate old `proxy-listening.enc` or `proxy-listening.json` files and does not read or clean up old per-item Keychain credentials; if an old file is found, the user is likewise asked to confirm a reset. See [ADR-0034](adr/0034-separate-local-and-lan-listeners.md), [ADR-0040](adr/0040-keep-settings-recovery-simple.md), [ADR-0041](adr/0041-separate-preferences-from-proxy-listening-configuration.md), [ADR-0042](adr/0042-encrypt-proxy-listening-configuration.md), and [ADR-0050](adr/0050-age-encrypted-configuration-storage.md).
|
||||
|
||||
文件根目录为 `~/Library/Application Support/ShadowsocksX-NG2/`,按生命周期分类:
|
||||
## Application Support directory
|
||||
|
||||
| 子目录 | 文件与用途 |
|
||||
The root directory is `~/Library/Application Support/ShadowsocksX-NG2/`, organized by lifecycle:
|
||||
|
||||
| Subdirectory | Files and purpose |
|
||||
| --- | --- |
|
||||
| `configuration/` | `catalog.age`、`proxy-listening.age`、`activation.json`、`custom-rules.json`、`plugins.json`,由 GUI 管理的持久资料 |
|
||||
| `runtime/` | 监管资料、运行回执、失败记录、PID、稳定链接,以及代次命名的配置、GUI 私有意图摘要、认证与 ACL 文件 |
|
||||
| `logs/` | `agent.log`,停止后台代理后保留 |
|
||||
| `configuration/` | `catalog.age`, `proxy-listening.age`, `activation.json`, `custom-rules.json`, and `plugins.json`: persistent information managed by the GUI |
|
||||
| `runtime/` | Supervision data, runtime receipts, failure records, PID, stable links, and generation-named configuration, GUI-private intent summaries, authentication, and ACL files |
|
||||
| `logs/` | `agent.log`, retained after the background proxy stops |
|
||||
|
||||
`runtime-generation`、`runtime.lock`、`agent.lock` 位于 `runtime/`,停止服务仍保留。不能把停止操作实现为删除整个运行目录。目录权限为 `0700`,普通文件权限为 `0600`;运行配置和认证文件仍可能包含明文秘密。UserDefaults、Keychain、LaunchAgent plist 及 bundle 内规则与插件继续使用各自的位置。
|
||||
`runtime-generation`, `runtime.lock`, and `agent.lock` are in `runtime/` and remain after service shutdown. Do not implement stopping the service by deleting the whole runtime directory. Directory permissions are `0700`; regular-file permissions are `0600`. Runtime configuration and authentication files may still contain plaintext secrets. UserDefaults, Keychain, LaunchAgent plists, and bundled rules and plugins keep their respective locations.
|
||||
|
||||
此次布局调整不提供自动迁移。旧根目录文件不读取、不移动、不删除,包括初版 `silent-launch.json`;已保存的 UserDefaults 偏好不变。开发环境切换前,退出 GUI 并停止旧 Agent 与 sslocal,确认进程退出后,将需要保留的上述五份 GUI 文件原样搬到 `configuration/`,不要搬移运行产物。当前 age 文件可沿用同一用户对应 service 中的 Keychain identity;历史自研 `.enc` 文件不迁移,不能只改扩展名继续使用。不应只复制密文到另一台机器并假定能够解密。设置目录权限为 `0700`、文件权限为 `0600`,再启动新构建,由新路径重新派生运行产物。未搬移配置时,新目录按首次使用初始化;不会从旧根目录补读。新目录内旧格式文件的拒绝规则仍保留。
|
||||
This layout change does not provide automatic migration. Files under the old root are not read, moved, or deleted, including the initial `silent-launch.json`; saved UserDefaults preferences are unchanged. Before switching a development environment, quit the GUI and stop the old Agent and sslocal. After confirming that the processes have exited, move the five GUI files listed above to `configuration/` unchanged if you need to keep them; do not move runtime artifacts. Current age files can reuse the Keychain identity stored under the service for the same user. Historical custom `.enc` files are not migrated and cannot be reused by only changing their extension. Do not assume that copying ciphertext to another machine will make it decryptable. Set directory permissions to `0700` and file permissions to `0600`, then launch the new build so it can derive runtime artifacts at the new paths. If the files are not moved, the new directory initializes on first use and does not read anything from the old root. The rejection rules for old formats inside the new directory still apply.
|
||||
|
||||
路径规则集中在 `ApplicationSupportPaths` 与 `RuntimePaths`。Agent 的测试覆盖中,`SSXNG_CONTRACT_PATH` 优先于 `SSXNG_RUNTIME_DIR`,同一部署全部路径由该契约所在目录派生;仅设置运行目录时使用标准配置链接名。`SSXNG_LOG_PATH` 可独立指定日志位置。指定测试契约或运行目录且未指定日志时,日志留在被覆盖的运行目录,避免测试写入生产日志。
|
||||
Path rules are centralized in `ApplicationSupportPaths` and `RuntimePaths`. In Agent tests, `SSXNG_CONTRACT_PATH` takes precedence over `SSXNG_RUNTIME_DIR`, and all paths for one deployment are derived from the directory containing that contract. If only the runtime directory is set, the standard configuration-link name is used. `SSXNG_LOG_PATH` can independently select the log location. If a test contract or runtime directory is set without a log path, logs remain in the overridden runtime directory so tests do not write to production logs.
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# 订阅、插件与监听配置
|
||||
|
||||
[English](configuration.md) | [返回工程首页](../ShadowsocksX-NG2/README.zh-CN.md)
|
||||
|
||||
## 订阅资料
|
||||
|
||||
面向订阅服务提供方的字段格式、嵌套分组扩展和响应要求见 [SIP-008 订阅扩展说明](subscription-provider-extension.zh-CN.md)。
|
||||
|
||||
订阅可在 SIP-008 JSON 根级提供 `bytes_used`、`bytes_remaining`(非负整数,最大为 UInt64),以及自定义的 `expires_at`(服务到期时间)、`traffic_reset_at`(流量重置时间)。日期值使用 RFC 3339 字符串,省略或 `null` 表示未提供;无效资料仅忽略该项,不影响有效服务器或分组资料。
|
||||
|
||||
成功刷新完整替换白名单订阅资料,省略或无效的字段清除旧值;失败或取消保留最后成功资料与独立成功时间。资料和目录记录同文档原子保存,不另存原始响应或含密码的服务器明文快照;密码和插件参数沿用 Keychain 存储。旧目录文件缺少成功时间时不猜测。
|
||||
|
||||
订阅卡片显示返回的有效资料;仅在两项流量都有效且总量大于零时显示消耗进度。日期按用户当前语言与时区显示,到期和重置资料不改变代理启停或激活行为。
|
||||
|
||||
## 用户插件目录
|
||||
|
||||
`PluginCatalog` 合成当前托管清单与本机 `plugins.json` 用户记录,名称精确区分大小写。用户映射优先,失效 override 不退回托管程序;删除 override 恢复当前 app 默认值。文件读取错误保留阻断事实,不按空映射处理。用户路径、本地启动参数与插件传输模式不进入服务器资料、订阅或分享文件。用户路径也不进入诊断报告。
|
||||
|
||||
设置页底部的“插件”区域统一展示内置与用户插件,按记录展示全部名称,支持新增、编辑名称集合与路径、逐名称覆盖内置、删除记录与恢复同名内置。用户与内置记录可以设置插件传输模式 `tcp_only` 或 `tcp_and_udp`;默认 `tcp_only`,生成 sslocal 配置时省略 `plugin_mode`。应用不探测插件的 SIP003u 支持。内置编辑器中的“清空参数”只清空草稿启动参数并保留模式,保存后生效。名称与启动参数使用列表编辑,启动参数保留顺序和完整字符串;路径支持手动输入与文件选择。读取失败时隐藏条目并提供只读重试,管理 UI 不提供整体重置。映射提交与显式修复 interface 在组合根统一接入表单、激活与诊断。保存成功后刷新激活候选并异步收敛,运行失败不撤销保存。没有文件替换监控。`plugins.json` v1 每项独立迁移为带持久身份的用户记录,下一次保存写入 v2,不按相同路径合并。现有内置程序同时提供 `v2ray-plugin` 和 `v2ray-plugin-v1`。详见 [ADR-0033](adr/0033-plugin-records-own-aliases-and-local-arguments.md)。
|
||||
|
||||
`MacOSPluginInspection` 只读取下载隔离、签名与系统评估事实,不启动用户插件、不修改系统策略。检查失败或不适用为未知;策略评估拒绝不等于实际执行禁令,也不阻止映射保存或激活。真实 macOS 拦截提示不由 hostless 替身测试证明。
|
||||
|
||||
## Cloak 托管插件
|
||||
|
||||
随应用提供官方 [Cloak v2.12.0](https://github.com/cbeuw/Cloak/releases/tag/v2.12.0) 的 darwin-arm64 `ck-client`,服务器引用名称为 `cloak` 或 `cloak-v2`,两者共享一条内置记录。`ck-client` 是打包文件名,不是可引用名称。同名用户插件仍优先;失效覆盖不会自动回退到内置程序。
|
||||
|
||||
服务器插件选项原样传给上游,不进行 Cloak 字段语义校验。可以使用分号分隔的 `Key=value` 参数,或使用外部 JSON 配置文件的绝对路径。外部文件由用户维护,应用不复制文件、不管理文件凭据或生命周期;分享路径不包含文件内容,接收方需要自行提供对应文件。本地启动参数仍独立保存,不随服务器资料分享。
|
||||
|
||||
[固定版本配置解析](https://github.com/cbeuw/Cloak/blob/v2.12.0/internal/client/state.go)存在以下限制:只有同时含 `;` 和 `=` 的字符串才被识别为参数列表,单个 `Key=value` 会被当作文件路径;转义分号仍会被切分,引号和反斜杠也不能按通用 SIP003 转义方式可靠传递;空段会终止后续解析。需要复杂值时可自行维护 JSON 文件。提供 `UID`、`PublicKey`、`ServerName`、`EncryptionMethod`,并按需显式设置 `NumConn=4`,不能依赖 README 所述的默认 4;省略时固定版本实现关闭连接复用。显式端点字段会覆盖 SIP003 环境变量,因此通常应省略 `LocalHost`、`LocalPort`、`RemoteHost`、`RemotePort`。
|
||||
|
||||
默认插件传输模式为 `tcp_only`。上游 `UDP=true` 切换为 UDP 监听,不代表同时承载 TCP 与 UDP 的 SIP003u 支持;应用仍允许用户设置现有传输模式,本次只验收 TCP 配置接入。托管程序的 GPL-3.0 许可证、来源与第三方声明随应用装入 `Contents/Resources/ThirdParty/Cloak/`,源码与构建入口见其中 NOTICE。
|
||||
|
||||
## 本地与局域网监听
|
||||
|
||||
后台代理客户端运行时,本地 SOCKS/HTTP 始终监听 `127.0.0.1`,默认端口为 `11086`/`11087`;本地 SOCKS 固定开启 UDP 中继。局域网入口默认关闭,使用独立端口 `11088`/`11089`,支持 IPv4、双栈和仅 IPv6 三种方式。四端口必须在 `1025...65535` 内且全部不同。
|
||||
|
||||
局域网 SOCKS 的 UDP 中继和 SOCKS/HTTP 共用的身份验证独立设置。用户名与密码随监听配置整体加密保存,Keychain 仅保存监听专用主密钥;运行时派生当前用户专用的认证文件;上游认证失败日志中的密码会隐藏。设置只检查 TCP 占用,不设置独立 UDP 端口。首页命令按选择地址使用本地或局域网端口,局域网身份验证开启时加入编码后的凭据;tooltip 显示完整命令。
|
||||
|
||||
监听配置使用标准 age 文件格式与独立 X25519 identity,单独加密保存在 `proxy-listening.age`,只包含本地与局域网监听相关值。代理模式、规则默认动作、代理服务开关、系统代理开关和附加系统代理例外以 `proxyControl.` 前缀的独立键保存在 UserDefaults;运行计算的内存快照不代表统一持久化文档。监听仍采用联合校验与原子文件保存;UserDefaults 偏好不提供同步落盘失败的报错与回滚。
|
||||
|
||||
不迁移旧 `settings.json`:新监听文件与新增偏好缺值时使用默认值,需要重新设置现有开发配置;旧文件不读取、不删除,遗留 Keychain 条目不访问、不清理。已有语言、静默启动、测试 URL 等独立偏好及服务器资料保持原状。监听读取失败会阻断正常启动,包括静默启动,并显示只含“重置监听配置”的说明性 alert。用户确认后创建新监听主密钥并原子保存默认配置,默认关闭局域网监听。重置成功前保留原文件及已有后台代理和系统代理;成功后按默认配置与原有开关意图收敛。不迁移旧 `proxy-listening.enc` 或 `proxy-listening.json`,不读取或清理旧逐项 Keychain 凭据;发现旧文件时同样提示确认重置。设计见 [ADR-0034](adr/0034-separate-local-and-lan-listeners.md)、[ADR-0040](adr/0040-keep-settings-recovery-simple.md)、[ADR-0041](adr/0041-separate-preferences-from-proxy-listening-configuration.md)、[ADR-0042](adr/0042-encrypt-proxy-listening-configuration.md) 和 [ADR-0050](adr/0050-age-encrypted-configuration-storage.md)。
|
||||
|
||||
## 应用支持目录
|
||||
|
||||
文件根目录为 `~/Library/Application Support/ShadowsocksX-NG2/`,按生命周期分类:
|
||||
|
||||
| 子目录 | 文件与用途 |
|
||||
| --- | --- |
|
||||
| `configuration/` | `catalog.age`、`proxy-listening.age`、`activation.json`、`custom-rules.json`、`plugins.json`,由 GUI 管理的持久资料 |
|
||||
| `runtime/` | 监管资料、运行回执、失败记录、PID、稳定链接,以及代次命名的配置、GUI 私有意图摘要、认证与 ACL 文件 |
|
||||
| `logs/` | `agent.log`,停止后台代理后保留 |
|
||||
|
||||
`runtime-generation`、`runtime.lock`、`agent.lock` 位于 `runtime/`,停止服务仍保留。不能把停止操作实现为删除整个运行目录。目录权限为 `0700`,普通文件权限为 `0600`;运行配置和认证文件仍可能包含明文秘密。UserDefaults、Keychain、LaunchAgent plist 及 bundle 内规则与插件继续使用各自的位置。
|
||||
|
||||
此次布局调整不提供自动迁移。旧根目录文件不读取、不移动、不删除,包括初版 `silent-launch.json`;已保存的 UserDefaults 偏好不变。开发环境切换前,退出 GUI 并停止旧 Agent 与 sslocal,确认进程退出后,将需要保留的上述五份 GUI 文件原样搬到 `configuration/`,不要搬移运行产物。当前 age 文件可沿用同一用户对应 service 中的 Keychain identity;历史自研 `.enc` 文件不迁移,不能只改扩展名继续使用。不应只复制密文到另一台机器并假定能够解密。设置目录权限为 `0700`、文件权限为 `0600`,再启动新构建,由新路径重新派生运行产物。未搬移配置时,新目录按首次使用初始化;不会从旧根目录补读。新目录内旧格式文件的拒绝规则仍保留。
|
||||
|
||||
路径规则集中在 `ApplicationSupportPaths` 与 `RuntimePaths`。Agent 的测试覆盖中,`SSXNG_CONTRACT_PATH` 优先于 `SSXNG_RUNTIME_DIR`,同一部署全部路径由该契约所在目录派生;仅设置运行目录时使用标准配置链接名。`SSXNG_LOG_PATH` 可独立指定日志位置。指定测试契约或运行目录且未指定日志时,日志留在被覆盖的运行目录,避免测试写入生产日志。
|
||||
+37
-38
@@ -1,84 +1,83 @@
|
||||
# 开发、构建与测试
|
||||
# Development, Build, and Testing
|
||||
|
||||
[返回工程首页](../ShadowsocksX-NG2/README.md)
|
||||
[Project home](../ShadowsocksX-NG2/README.md) | [简体中文](development.zh-CN.md)
|
||||
|
||||
除特别说明外,命令从 `ShadowsocksX-NG2/` 执行,文中工程路径也相对于该目录。
|
||||
Unless noted otherwise, run commands from `ShadowsocksX-NG2/`; paths in this document are relative to that directory.
|
||||
|
||||
## 统一验证
|
||||
## Unified validation
|
||||
|
||||
统一验证入口(从仓库根目录调用):
|
||||
Run the unified validation workflow from the repository root:
|
||||
|
||||
```bash
|
||||
task --exit-code --taskfile ShadowsocksX-NG2/Taskfile.dist.yml validate
|
||||
# 按指定顺序只运行所需关卡:
|
||||
# Run only the requested stages, in the specified order:
|
||||
task --exit-code --taskfile ShadowsocksX-NG2/Taskfile.dist.yml validate -- lint test
|
||||
```
|
||||
|
||||
在工程目录内可用 `task --exit-code validate`。从任意目录可用 `task --exit-code --taskfile /path/to/ShadowsocksX-NG/ShadowsocksX-NG2/Taskfile.dist.yml validate`。入口按 Taskfile 所在位置定位工程目录,内部调用发行 Taskfile 中的单项任务。
|
||||
From the project directory, use `task --exit-code validate`. From any directory, use `task --exit-code --taskfile /path/to/ShadowsocksX-NG/ShadowsocksX-NG2/Taskfile.dist.yml validate`. The entry point locates the project directory relative to the Taskfile and invokes individual tasks from the distribution Taskfile.
|
||||
|
||||
默认按 `lint → test → test:smoke → build` 执行,也可按指定顺序选择子集,每关最多一次;任一关失败立即停止,后续关标记 `not_run`,进程返回非零。`--exit-code` 让 Task 透传关卡退出码;省略时 Task 仍返回非零,但可能使用自己的错误码。入口不自动重试,不修改测试并行设置,也不执行会修改源码的格式化。
|
||||
By default, stages run in this order: `lint → test → test:smoke → build`. You can select a subset in a specified order; each stage can appear at most once. Execution stops on the first failure, marks later stages `not_run`, and exits nonzero. `--exit-code` makes Task pass through the stage's exit code. Without it, Task still exits nonzero but may use its own error code. The entry point does not retry automatically, change test parallelism, or run formatters that modify source files.
|
||||
|
||||
### 验证产物与证据
|
||||
### Validation artifacts and evidence
|
||||
|
||||
每次运行创建独立的 `.build/validation/<UTC时间>-<随机后缀>/`,开始与结束均输出结果绝对路径。`summary.txt` 保存各关状态、退出码与证据状态;每个已执行关卡在同名子目录下保留合并 stdout/stderr 的 `raw.log` 和 `status.txt`(`test:smoke` 对应 `test-smoke/`),Xcode 关卡显式保存 `result.xcresult`。控制台只显示进度和摘要,需要完整错误时直接读取对应日志。所有产物均在现有忽略目录内;`task clean` 会删除它们,需要长期留存时先复制出去。
|
||||
Each run creates a separate `.build/validation/<UTC time>-<random suffix>/` directory and prints its absolute path at the start and end. `summary.txt` records each stage's status, exit code, and evidence status. For every executed stage, the corresponding subdirectory contains a combined stdout/stderr `raw.log` and a `status.txt` (`test:smoke` uses `test-smoke/`); Xcode stages also save `result.xcresult`. The console shows progress and a summary. Read the corresponding logs for complete errors. All artifacts are inside existing ignored directories. `task clean` deletes them, so copy anything you need to keep.
|
||||
|
||||
测试关通过本机 `xcrun xcresulttool get test-results summary` 和系统 `plutil` 尽力读取计数,无需新增脚本或工具依赖。`test-summary.json` 保存原始摘要,`test-summary.stderr.log` 保存 xcresulttool 错误,`count-reader.log` 保存计数读取错误。汇总展示通过、失败、跳过、预期失败及总数(Xcode 汇总口径,参数化测试的实际执行次数可能不同)。关卡与总体退出码由原命令决定,命令失败始终保留失败。结果包缺失或摘要不可读时显著标注 `evidence incomplete`(证据不完整)、计数未知,不改变命令结果,也不把缺失计数写成零。构建关不读取测试计数;未执行的关卡不等同于测试跳过。
|
||||
For test stages, the workflow uses the local `xcrun xcresulttool get test-results summary` and system `plutil` to read test counts on a best-effort basis; it adds no scripts or tool dependencies. `test-summary.json` stores the raw summary, `test-summary.stderr.log` stores xcresulttool errors, and `count-reader.log` stores count-reading errors. The summary shows passed, failed, skipped, expected-failure, and total counts using Xcode's summary semantics; parameterized tests may have a different actual execution count. Stage and overall exit codes come from the original command, and a failed command always remains a failure. A missing result bundle or unreadable summary is marked `evidence incomplete`, with counts shown as unknown. This does not change the command result, and missing counts are never reported as zero. Build stages do not read test counts; a stage that did not run is not the same as a skipped test.
|
||||
|
||||
统一入口成功表示所选关卡的命令通过,仍需核对证据是否完整;hostless、真实进程 smoke、原生 UI 和真实系统代理的验收边界仍分别成立,发布打包门槛见[签名与分发](distribution.md#发布门槛)。
|
||||
A successful unified run means the selected stage commands passed; still check that the evidence is complete. Acceptance boundaries remain separate for hostless tests, real-process smoke tests, native UI, and the real system proxy. See [Signing and distribution](distribution.md#release-gate) for release packaging requirements.
|
||||
|
||||
`task test` 与 `task test:smoke` 显式启用 Xcode 单测试超时:默认 120 秒,上限 300 秒。这是 runner 兜底,不保证测试任务正常清理,也不是性能门槛。规则阶段等待失败会抛错退出测试体;受保护的测试任务先释放夹具,再取消并等待结束。规则延迟 gate 和同步阻塞夹具的默认等待上限为 10 秒,超时必须报告失败。共享 `AsyncGate` 保留由用例控制的取消语义。Swift Testing 的 detached worker 超时报告保留创建用例名称和源码位置;应同时核对命令退出码与测试计数,不能仅凭单例通过数判断成功。
|
||||
`task test` and `task test:smoke` explicitly enable the Xcode per-test timeout: 120 seconds by default, with a 300-second maximum. This is a runner fallback. It does not guarantee normal test-task cleanup and is not a performance threshold. If a rule-stage wait fails, the test body throws and exits. Protected test tasks release their fixtures before cancellation and wait for task completion. Rule-delay gates and synchronous blocking fixtures have a default wait limit of 10 seconds and must report a failure on timeout. The shared `AsyncGate` preserves cancellation semantics controlled by each test. Swift Testing detached-worker timeout reports retain the creating test's name and source location. Check both the command exit code and test counts; a passing count for one test alone does not establish success.
|
||||
|
||||
## 常用任务
|
||||
## Common tasks
|
||||
|
||||
在工程目录内,日常格式化、静态检查、工程生成、构建和测试优先使用 `Taskfile.dist.yml` 定义的任务:
|
||||
For routine formatting, static checks, project generation, builds, and tests, prefer the tasks defined in `Taskfile.dist.yml` and run them from the project directory:
|
||||
|
||||
```bash
|
||||
task format
|
||||
task lint
|
||||
task lint:fix # 需要自动修复 SwiftLint 违规时使用;执行后再运行 task lint
|
||||
task gen:prj # 生成 Xcode 工程
|
||||
task build # 构建 Debug 版本 app
|
||||
task test # 运行单元测试(不含真实进程冒烟)
|
||||
task test:smoke # 运行真实进程冒烟测试(拉起真实 sslocal,见签名与分发文档的发布门槛)
|
||||
task lint:fix # Use for automatic SwiftLint fixes; run task lint afterward.
|
||||
task gen:prj # Generate the Xcode project.
|
||||
task build # Build the Debug app.
|
||||
task test # Run unit tests (without real-process smoke tests).
|
||||
task test:smoke # Run real-process smoke tests (launches real sslocal; see the release gate in the signing and distribution guide).
|
||||
```
|
||||
|
||||
`task format` 会格式化 `App/`、`Tests/`、`Domain/` 和 `Agent/` 下的 Swift 文件;`task lint:fix` 会修改源文件,执行后检查并复核 diff。
|
||||
`task format` formats Swift files under `App/`, `Tests/`, `Domain/`, and `Agent/`. `task lint:fix` modifies source files; inspect and review the diff afterward.
|
||||
|
||||
## 首次准备与测试范围
|
||||
## First-time setup and test scope
|
||||
|
||||
新 clone 或清单变更后,在工程目录内先取外部二进制,再生成工程(顺序不可颠倒):
|
||||
After a fresh clone or a manifest change, fetch the external binaries before generating the project. Run these commands from the project directory, in this order:
|
||||
|
||||
```bash
|
||||
Scripts/fetch-external-binaries.sh
|
||||
task gen:prj
|
||||
```
|
||||
|
||||
Swift Package Manager 从 `project.yml` 固定的正式版本解析 AgeSwift,并校验其发布 XCFramework。首次解析需要网络,消费者无需安装 Rust。`Vendor/AgeSwift/LICENSE-MIT` 来自固定版本,随应用打包到 `Resources/ThirdParty/AgeSwift/`。加密存储互操作测试还需要 `brew install age` 和可访问的登录钥匙串;测试使用 UUID 隔离的 service 并清理,不修改真实应用配置。命令行调试方法见[加密格式文档](server-library-encryption-format.zh-CN.md)。
|
||||
Swift Package Manager resolves the pinned release of AgeSwift from `project.yml` and verifies its release XCFramework. Network access is needed for the first resolution; consumers do not need to install Rust. `Vendor/AgeSwift/LICENSE-MIT` comes from the pinned release and is bundled in the app at `Resources/ThirdParty/AgeSwift/`. Encryption-storage interoperability tests also require `brew install age` and an accessible login keychain. Tests use a UUID-isolated service and clean it up without changing real app configuration. See the [encryption format guide](server-library-encryption-format.md) for command-line debugging.
|
||||
|
||||
无证书也能构建与跑单测:Debug 基线是 ad-hoc,不需要 `Configs/Signing.local.xcconfig`。只有要做 Developer ID 签名(发布,或让本机 Debug 产物过打包门槛)时才写该文件,内容见[签名与运行形态基线](distribution.md#签名与运行形态基线)。
|
||||
You can build and run unit tests without a certificate: the Debug baseline uses ad-hoc signing and does not require `Configs/Signing.local.xcconfig`. Create that file only when signing with Developer ID, either for release or to make a local Debug build pass the packaging gate. See [Signing and runtime baseline](distribution.md#signing-and-runtime-baseline) for its contents.
|
||||
|
||||
`Tests/Smoke/` 是真实进程冒烟(经 wrapper 拉起真实 sslocal 验证启动、握手、
|
||||
ACL 路由与插件监管),不在默认 `task test` 内,由独立 scheme 按需运行。
|
||||
`Tests/Smoke/` contains real-process smoke tests. A wrapper launches real `sslocal` to verify startup, handshake, ACL routing, and plugin supervision. These tests are not part of the default `task test`; run them on demand with their separate scheme.
|
||||
|
||||
## 代码风格工具
|
||||
## Code style tools
|
||||
|
||||
- `swift format` 随 Xcode/Swift 工具链提供,无需单独安装(基线:Apple Swift 6.4);SwiftLint 需 `brew install swiftlint`(基线 0.65.0)。工具缺失时,含工程目录 Swift 文件的提交会被 pre-commit 钩子拒绝而不是放行。
|
||||
- 配置文件:`.swift-format` 是 Swift 6.4 工具链默认规则的快照(固定成文件,工具链升级不漂移);`.swiftlint.yml` 默认规则起步,只声明排除项与个别和 format 基线冲突的关闭项(文件内有触发案例注释)。
|
||||
- 仓库级 `pre-commit` 钩子的启用方式和检查范围见下节「开发钩子」。
|
||||
- 调整规则只针对实际痛点:修改 `.swift-format` / `.swiftlint.yml` 的 PR 须给出触发案例,并同步更新本节中的基线版本。
|
||||
- `swift format` is included with the Xcode/Swift toolchain and does not need a separate installation (baseline: Apple Swift 6.4). Install SwiftLint with `brew install swiftlint` (baseline: 0.65.0). If either tool is missing, the pre-commit hook rejects a commit that contains Swift files under the project directory instead of allowing it.
|
||||
- `.swift-format` is a snapshot of the Swift 6.4 toolchain's default rules, checked in so the rules do not drift on toolchain upgrades. `.swiftlint.yml` starts with the default rules and declares only exclusions and individual opt-outs that conflict with the format baseline; comments in the file show triggering examples.
|
||||
- See [Development hooks](#development-hooks) for how to enable the repository-level `pre-commit` hook and what it checks.
|
||||
- Adjust rules only to address concrete problems. A PR that changes `.swift-format` or `.swiftlint.yml` must include a triggering example and update the baseline versions in this section.
|
||||
|
||||
## 开发钩子
|
||||
## Development hooks
|
||||
|
||||
新 clone 后,在仓库根目录启用 Git hooks:
|
||||
After cloning, enable Git hooks from the repository root:
|
||||
|
||||
```bash
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
|
||||
启用后,`pre-commit` 只检查暂存区中 `ShadowsocksX-NG2/` 下的 `.swift` 文件:`swift format` 的结果写回暂存区、不修改工作区;SwiftLint 的 error 级违规拒绝提交,warning 仅提示。其他文件跳过;没有 NG2 Swift 文件时直接放行。含有 NG2 Swift 文件但缺少工具时,提交检查失败。两关都把「工具没能执行」与「查出问题」分开报告:看到「执行失败」应查工具与配置环境,而不是去改代码。工具安装与版本见上节「代码风格工具」。
|
||||
Once enabled, `pre-commit` checks only staged `.swift` files under `ShadowsocksX-NG2/`. `swift format` writes its result back to the index without changing the working tree. SwiftLint errors reject the commit; warnings are informational. Other files are skipped. If there are no NG2 Swift files, the hook passes. If NG2 Swift files are present and a required tool is missing, the check fails. Both checks distinguish “tool execution failed” from “issues found”: when you see “execution failed,” investigate the tool or configuration environment instead of changing code. See [Code style tools](#code-style-tools) for installation and versions.
|
||||
|
||||
## 领域文档语言
|
||||
## Language of domain documents
|
||||
|
||||
本项目的 [术语表](../GLOSSARY.md) 和新建 [架构决策记录(ADR)](adr/) 使用简体中文,代码标识符与必要的英文术语保留原文;已有 ADR 保留原文,不作语言迁移。
|
||||
The project's [glossary](../GLOSSARY.md) and new [architecture decision records (ADRs)](adr/) are written in Simplified Chinese, while code identifiers and necessary English technical terms remain in English. Existing ADRs retain their original language and are not translated.
|
||||
|
||||
AI 生成大量文档后,人与机器之间的沟通成为协作瓶颈:人的阅读、理解和审阅速度决定了文档能否有效推动开发。使用简体中文可以降低维护者的认知负荷,提高沟通与审阅效率。
|
||||
As AI generates more documents, communication between people and machines can become a collaboration bottleneck: people's reading, comprehension, and review speed determines whether documentation can support development effectively. Simplified Chinese reduces the cognitive load for maintainers and makes communication and review more efficient.
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
# 开发、构建与测试
|
||||
|
||||
[English](development.md) | [返回工程首页](../ShadowsocksX-NG2/README.zh-CN.md)
|
||||
|
||||
除特别说明外,命令从 `ShadowsocksX-NG2/` 执行,文中工程路径也相对于该目录。
|
||||
|
||||
## 统一验证
|
||||
|
||||
统一验证入口(从仓库根目录调用):
|
||||
|
||||
```bash
|
||||
task --exit-code --taskfile ShadowsocksX-NG2/Taskfile.dist.yml validate
|
||||
# 按指定顺序只运行所需关卡:
|
||||
task --exit-code --taskfile ShadowsocksX-NG2/Taskfile.dist.yml validate -- lint test
|
||||
```
|
||||
|
||||
在工程目录内可用 `task --exit-code validate`。从任意目录可用 `task --exit-code --taskfile /path/to/ShadowsocksX-NG/ShadowsocksX-NG2/Taskfile.dist.yml validate`。入口按 Taskfile 所在位置定位工程目录,内部调用发行 Taskfile 中的单项任务。
|
||||
|
||||
默认按 `lint → test → test:smoke → build` 执行,也可按指定顺序选择子集,每关最多一次;任一关失败立即停止,后续关标记 `not_run`,进程返回非零。`--exit-code` 让 Task 透传关卡退出码;省略时 Task 仍返回非零,但可能使用自己的错误码。入口不自动重试,不修改测试并行设置,也不执行会修改源码的格式化。
|
||||
|
||||
### 验证产物与证据
|
||||
|
||||
每次运行创建独立的 `.build/validation/<UTC时间>-<随机后缀>/`,开始与结束均输出结果绝对路径。`summary.txt` 保存各关状态、退出码与证据状态;每个已执行关卡在同名子目录下保留合并 stdout/stderr 的 `raw.log` 和 `status.txt`(`test:smoke` 对应 `test-smoke/`),Xcode 关卡显式保存 `result.xcresult`。控制台只显示进度和摘要,需要完整错误时直接读取对应日志。所有产物均在现有忽略目录内;`task clean` 会删除它们,需要长期留存时先复制出去。
|
||||
|
||||
测试关通过本机 `xcrun xcresulttool get test-results summary` 和系统 `plutil` 尽力读取计数,无需新增脚本或工具依赖。`test-summary.json` 保存原始摘要,`test-summary.stderr.log` 保存 xcresulttool 错误,`count-reader.log` 保存计数读取错误。汇总展示通过、失败、跳过、预期失败及总数(Xcode 汇总口径,参数化测试的实际执行次数可能不同)。关卡与总体退出码由原命令决定,命令失败始终保留失败。结果包缺失或摘要不可读时显著标注 `evidence incomplete`(证据不完整)、计数未知,不改变命令结果,也不把缺失计数写成零。构建关不读取测试计数;未执行的关卡不等同于测试跳过。
|
||||
|
||||
统一入口成功表示所选关卡的命令通过,仍需核对证据是否完整;hostless、真实进程 smoke、原生 UI 和真实系统代理的验收边界仍分别成立,发布打包门槛见[签名与分发](distribution.zh-CN.md#发布门槛)。
|
||||
|
||||
`task test` 与 `task test:smoke` 显式启用 Xcode 单测试超时:默认 120 秒,上限 300 秒。这是 runner 兜底,不保证测试任务正常清理,也不是性能门槛。规则阶段等待失败会抛错退出测试体;受保护的测试任务先释放夹具,再取消并等待结束。规则延迟 gate 和同步阻塞夹具的默认等待上限为 10 秒,超时必须报告失败。共享 `AsyncGate` 保留由用例控制的取消语义。Swift Testing 的 detached worker 超时报告保留创建用例名称和源码位置;应同时核对命令退出码与测试计数,不能仅凭单例通过数判断成功。
|
||||
|
||||
## 常用任务
|
||||
|
||||
在工程目录内,日常格式化、静态检查、工程生成、构建和测试优先使用 `Taskfile.dist.yml` 定义的任务:
|
||||
|
||||
```bash
|
||||
task format
|
||||
task lint
|
||||
task lint:fix # 需要自动修复 SwiftLint 违规时使用;执行后再运行 task lint
|
||||
task gen:prj # 生成 Xcode 工程
|
||||
task build # 构建 Debug 版本 app
|
||||
task test # 运行单元测试(不含真实进程冒烟)
|
||||
task test:smoke # 运行真实进程冒烟测试(拉起真实 sslocal,见签名与分发文档的发布门槛)
|
||||
```
|
||||
|
||||
`task format` 会格式化 `App/`、`Tests/`、`Domain/` 和 `Agent/` 下的 Swift 文件;`task lint:fix` 会修改源文件,执行后检查并复核 diff。
|
||||
|
||||
## 首次准备与测试范围
|
||||
|
||||
新 clone 或清单变更后,在工程目录内先取外部二进制,再生成工程(顺序不可颠倒):
|
||||
|
||||
```bash
|
||||
Scripts/fetch-external-binaries.sh
|
||||
task gen:prj
|
||||
```
|
||||
|
||||
Swift Package Manager 从 `project.yml` 固定的正式版本解析 AgeSwift,并校验其发布 XCFramework。首次解析需要网络,消费者无需安装 Rust。`Vendor/AgeSwift/LICENSE-MIT` 来自固定版本,随应用打包到 `Resources/ThirdParty/AgeSwift/`。加密存储互操作测试还需要 `brew install age` 和可访问的登录钥匙串;测试使用 UUID 隔离的 service 并清理,不修改真实应用配置。命令行调试方法见[加密格式文档](server-library-encryption-format.zh-CN.md)。
|
||||
|
||||
无证书也能构建与跑单测:Debug 基线是 ad-hoc,不需要 `Configs/Signing.local.xcconfig`。只有要做 Developer ID 签名(发布,或让本机 Debug 产物过打包门槛)时才写该文件,内容见[签名与运行形态基线](distribution.zh-CN.md#签名与运行形态基线)。
|
||||
|
||||
`Tests/Smoke/` 是真实进程冒烟(经 wrapper 拉起真实 sslocal 验证启动、握手、
|
||||
ACL 路由与插件监管),不在默认 `task test` 内,由独立 scheme 按需运行。
|
||||
|
||||
## 代码风格工具
|
||||
|
||||
- `swift format` 随 Xcode/Swift 工具链提供,无需单独安装(基线:Apple Swift 6.4);SwiftLint 需 `brew install swiftlint`(基线 0.65.0)。工具缺失时,含工程目录 Swift 文件的提交会被 pre-commit 钩子拒绝而不是放行。
|
||||
- 配置文件:`.swift-format` 是 Swift 6.4 工具链默认规则的快照(固定成文件,工具链升级不漂移);`.swiftlint.yml` 默认规则起步,只声明排除项与个别和 format 基线冲突的关闭项(文件内有触发案例注释)。
|
||||
- 仓库级 `pre-commit` 钩子的启用方式和检查范围见下节「开发钩子」。
|
||||
- 调整规则只针对实际痛点:修改 `.swift-format` / `.swiftlint.yml` 的 PR 须给出触发案例,并同步更新本节中的基线版本。
|
||||
|
||||
## 开发钩子
|
||||
|
||||
新 clone 后,在仓库根目录启用 Git hooks:
|
||||
|
||||
```bash
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
|
||||
启用后,`pre-commit` 只检查暂存区中 `ShadowsocksX-NG2/` 下的 `.swift` 文件:`swift format` 的结果写回暂存区、不修改工作区;SwiftLint 的 error 级违规拒绝提交,warning 仅提示。其他文件跳过;没有 NG2 Swift 文件时直接放行。含有 NG2 Swift 文件但缺少工具时,提交检查失败。两关都把「工具没能执行」与「查出问题」分开报告:看到「执行失败」应查工具与配置环境,而不是去改代码。工具安装与版本见上节「代码风格工具」。
|
||||
|
||||
## 领域文档语言
|
||||
|
||||
本项目的 [术语表](../GLOSSARY.md) 和新建 [架构决策记录(ADR)](adr/) 使用简体中文,代码标识符与必要的英文术语保留原文;已有 ADR 保留原文,不作语言迁移。
|
||||
|
||||
AI 生成大量文档后,人与机器之间的沟通成为协作瓶颈:人的阅读、理解和审阅速度决定了文档能否有效推动开发。使用简体中文可以降低维护者的认知负荷,提高沟通与审阅效率。
|
||||
+45
-45
@@ -1,27 +1,27 @@
|
||||
# 外部二进制、签名与分发
|
||||
# External Binaries, Signing, and Distribution
|
||||
|
||||
[返回工程首页](../ShadowsocksX-NG2/README.md)
|
||||
[Project home](../ShadowsocksX-NG2/README.md) | [简体中文](distribution.zh-CN.md)
|
||||
|
||||
除特别说明外,命令从 `ShadowsocksX-NG2/` 执行,文中工程路径也相对于该目录。
|
||||
Unless noted otherwise, run commands from `ShadowsocksX-NG2/`; paths in this document are relative to that directory.
|
||||
|
||||
## 外部二进制供应链
|
||||
## External binary supply chain
|
||||
|
||||
外部二进制只用固定版本的上游 release 构建产物:[`sslocal` 清单](../ShadowsocksX-NG2/Vendor/sslocal/manifest.json)(装于 `Contents/Helpers/`)、[`v2ray-plugin` 清单](../ShadowsocksX-NG2/Vendor/v2ray-plugin/manifest.json) 与 [`ck-client` 清单](../ShadowsocksX-NG2/Vendor/ck-client/manifest.json)(两个插件均装于 `Contents/Helpers/Plugins/`)。清单记录各 release 与哈希;构建链如下(spec #21 D6/D10):
|
||||
External binaries come only from fixed-version upstream releases: the [`sslocal` manifest](../ShadowsocksX-NG2/Vendor/sslocal/manifest.json) (installed in `Contents/Helpers/`), the [`v2ray-plugin` manifest](../ShadowsocksX-NG2/Vendor/v2ray-plugin/manifest.json), and the [`ck-client` manifest](../ShadowsocksX-NG2/Vendor/ck-client/manifest.json) (both plugins are installed in `Contents/Helpers/Plugins/`). The manifests record each release and hash. The build pipeline is as follows (spec #21 D6/D10):
|
||||
|
||||
1. **fetch** — `Scripts/fetch-external-binaries.sh` 下载清单固定 URL 的资产、逐字节校验 `archiveSHA256`,按 `assetFormat` 处理 tar 归档(默认,提取 `archiveMember`)或直接二进制(`binary`),并确认 arm64,落盘 `Vendor/<name>/` 并记录戳。资产哈希不符 → 立即失败;本地缓存漂移或清单变更 → 自动重新下载复验。
|
||||
2. **generate** — `xcodegen generate` 把清单对应二进制作为 Copy Files 项装入 `Contents/Helpers[/Plugins]`。注意顺序:**先 fetch 再 generate**,文件在生成时必须已存在,否则 Copy Files 阶段会被静默丢弃(generate 也会直接报缺文件)。
|
||||
3. **verify on every build** — 预构建脚本阶段重跑 fetch 脚本(缓存命中时只对本地戳快速复验),哈希或清单漂移使构建即失败。
|
||||
4. **sign** — `Scripts/sign-embedded-helpers.sh`(post-build 阶段)对每个二进制 Developer ID 重签:`--options runtime --timestamp --identifier <清单 signIdentifier>`(插件形如 `<bundle-id>.plugin.<name>`);随后 Xcode 完成 app 外层签名,封存重签后的嵌套代码。
|
||||
5. **gate** — 构建产物必须过 `Scripts/packaging-gate.sh <App.app>`:嵌套代码签名严格校验、主应用 DevID + Hardened Runtime 且无 App Sandbox entitlement、逐二进制清单覆盖(Helpers 下每个 Mach-O 都在清单内且 DevID 重签通过)。篡改任一二进制会因签名失效被断言发现。
|
||||
1. **fetch** — `Scripts/fetch-external-binaries.sh` downloads the asset from the URL pinned in its manifest, verifies `archiveSHA256` byte for byte, processes tar archives according to `assetFormat` (the default; extracts `archiveMember`) or copies a raw binary (`binary`), verifies arm64, places the result in `Vendor/<name>/`, and writes a stamp. A hash mismatch fails immediately. A changed local cache or manifest triggers an automatic download and re-verification.
|
||||
2. **generate** — `xcodegen generate` adds each manifest binary as a Copy Files item under `Contents/Helpers[/Plugins]`. Order matters: **fetch before generate**. The files must exist when the project is generated; otherwise, the Copy Files phase may be silently omitted (or generation may report the missing file).
|
||||
3. **verify on every build** — a pre-build script runs the fetch script again. On a cache hit, it quickly rechecks the local stamp. A hash or manifest change fails the build.
|
||||
4. **sign** — in the post-build phase, `Scripts/sign-embedded-helpers.sh` re-signs every binary with Developer ID using `--options runtime --timestamp --identifier <manifest signIdentifier>` (plugin identifiers use the form `<bundle-id>.plugin.<name>`). Xcode then signs the outer app and seals the re-signed nested code.
|
||||
5. **gate** — the built app must pass `Scripts/packaging-gate.sh <App.app>`. It strictly verifies nested-code signatures, requires Developer ID and Hardened Runtime on the main app with no App Sandbox entitlement, and checks manifest coverage for each binary (every Mach-O under Helpers must be in a manifest and pass Developer ID re-signing). Tampering with any binary is detected because its signature is invalid.
|
||||
|
||||
## 签名与运行形态基线
|
||||
## Signing and runtime baseline
|
||||
|
||||
- bundle id `com.qiuyuzhou.ShadowsocksX-NG2`,与 Legacy(`com.qiuyuzhou.ShadowsocksX-NG`)区分;运行时数据落 `~/Library/Application Support/ShadowsocksX-NG2/`。macOS 15+ / arm64。
|
||||
- Hardened Runtime(DevID 发布路径;免证书的 ad-hoc Debug 例外,详见下面的配置说明);不启用 App Sandbox,entitlements 为空 dict;签名样式固定 `Manual`。
|
||||
- **证书与 Team 都不入库**(开源仓库):`project.yml` 不含 `CODE_SIGN_IDENTITY` / `DEVELOPMENT_TEAM`,两个键由 project xcconfig 按 config 在构建期注入:
|
||||
- `Configs/Signing-Debug.xcconfig`(入库)基线是 **ad-hoc(`-`)**:新 clone 与 CI 不需要任何证书就能 `task build` / `task test`。arm64 要求代码至少有 ad-hoc 签名才会被加载,所以「免证书」不是「不签名」。ad-hoc 产物不带 Hardened Runtime(Xcode 对 ad-hoc 签名不传 `-o runtime`,实测;`Scripts/sign-embedded-helpers.sh` 的 ad-hoc 分支同样不加),Hardened Runtime 由 DevID 发布路径与打包门槛断言。
|
||||
- `Configs/Signing-Release.xcconfig`(入库)基线是 **Developer ID Application**:发布产物必须真签名,这里不给免证书路径。
|
||||
- 两者都可选包含 `Configs/Signing.local.xcconfig`(gitignore)。有证书的人(含维护者)用它升级本机身份,例如让 Debug 也走 Developer ID:
|
||||
- Bundle ID: `com.qiuyuzhou.ShadowsocksX-NG2`, distinct from Legacy (`com.qiuyuzhou.ShadowsocksX-NG`). Runtime data is stored in `~/Library/Application Support/ShadowsocksX-NG2/`. The app targets macOS 15+ / arm64.
|
||||
- Hardened Runtime is required for the Developer ID release path; ad-hoc Debug builds are exempt because they need no certificate (see the configuration below). App Sandbox is disabled, entitlements are an empty dictionary, and the signing style is fixed to `Manual`.
|
||||
- **Certificates and Team IDs are not checked in** (this is an open-source repository). `project.yml` does not set `CODE_SIGN_IDENTITY` or `DEVELOPMENT_TEAM`; the project xcconfig injects both settings at build time according to the configuration:
|
||||
- The checked-in `Configs/Signing-Debug.xcconfig` baseline is **ad-hoc (`-`)**. A fresh clone and CI need no certificate to run `task build` or `task test`. On arm64, code must have at least an ad-hoc signature to load, so “no certificate” does not mean “unsigned.” Ad-hoc builds do not use Hardened Runtime (Xcode does not pass `-o runtime` for ad-hoc signing, as verified; the ad-hoc branch of `Scripts/sign-embedded-helpers.sh` does the same). Hardened Runtime is enforced by the Developer ID release path and packaging gate.
|
||||
- The checked-in `Configs/Signing-Release.xcconfig` baseline is **Developer ID Application**. Release artifacts must be signed; there is no certificate-free path.
|
||||
- Both configurations may optionally include `Configs/Signing.local.xcconfig` (gitignored). Developers with a certificate, including maintainers, can use it to set a local identity, for example to use Developer ID for Debug builds:
|
||||
|
||||
```bash
|
||||
cat > Configs/Signing.local.xcconfig <<'EOF'
|
||||
@@ -30,52 +30,52 @@
|
||||
EOF
|
||||
```
|
||||
|
||||
- 该文件缺失时 Debug 构建为 ad-hoc(可跑单测),Release 构建以 Xcode 的「Signing for … requires selecting either a development team or a provisioning profile」明确失败,不静默降级。
|
||||
- 测试 target(单测与冒烟)身份随同一基线:去宿主化(ADR-0021)后 xctest 包不再注入宿主 app,ad-hoc 也能加载执行(实测),因此测试无需证书。
|
||||
- 注意 `project.yml` 一旦重新设置这两个键,pbxproj 构建设置会盖掉 xcconfig(`Tests/Architecture/SigningConfigurationTests.swift` 守卫该约束)。
|
||||
- Release 配置关闭 `CODE_SIGN_INJECT_BASE_ENTITLEMENTS`,保证发布产物 entitlements 只来自项目文件。
|
||||
- Release 配置通过 `OTHER_CODE_SIGN_FLAGS = $(inherited) --timestamp` 为 Xcode 签名的主程序和两个原生工具请求安全时间戳。打包门槛在上传公证前检查这些时间戳;DMG 的时间戳不能替代包内可执行文件的时间戳。
|
||||
- If this file is absent, Debug builds use ad-hoc signing and can run unit tests. Release builds fail explicitly in Xcode with “Signing for … requires selecting either a development team or a provisioning profile”; they do not silently fall back.
|
||||
- Test targets (unit and smoke tests) use the same baseline. After tests became hostless (ADR-0021), the xctest bundle no longer injects the host app and can load and run with ad-hoc signing (verified), so tests need no certificate.
|
||||
- If these two settings are added back to `project.yml`, the generated pbxproj build settings override the xcconfig. `Tests/Architecture/SigningConfigurationTests.swift` guards this constraint.
|
||||
- The Release configuration disables `CODE_SIGN_INJECT_BASE_ENTITLEMENTS` so release entitlements come only from project files.
|
||||
- The Release configuration passes `OTHER_CODE_SIGN_FLAGS = $(inherited) --timestamp` so Xcode requests secure timestamps for the main app and two native tools it signs. The packaging gate checks these timestamps before notarization upload; a timestamp on the DMG cannot replace timestamps on the executables inside it.
|
||||
|
||||
## 发布门槛
|
||||
## Release gate
|
||||
|
||||
### 签名并公证的 DMG
|
||||
### Signed and notarized DMG
|
||||
|
||||
从 `ShadowsocksX-NG2/` 执行。首次准备外部二进制及 Developer ID 签名配置的方法见上文。钥匙串中必须有对应证书及私钥。
|
||||
Run these commands from `ShadowsocksX-NG2/`. See above for initial external-binary setup and Developer ID signing configuration. The keychain must contain the matching certificate and private key.
|
||||
|
||||
先用 Apple 的工具保存公证凭据。此命令交互输入 App 专用密码;凭据保存在钥匙串中,不写入仓库或 Taskfile:
|
||||
First, use Apple's tool to save notarization credentials. This command interactively requests the app-specific password and stores the credentials in the keychain, not in the repository or Taskfile:
|
||||
|
||||
```bash
|
||||
xcrun notarytool store-credentials shadowsocks-release \
|
||||
--apple-id YOUR_APPLE_ID --team-id YOUR_TEAM_ID
|
||||
```
|
||||
|
||||
完整发布入口:
|
||||
Full release entry point:
|
||||
|
||||
```bash
|
||||
task package NOTARY_PROFILE=shadowsocks-release \
|
||||
MARKETING_VERSION=2.1.0 CURRENT_PROJECT_VERSION=21
|
||||
```
|
||||
|
||||
`task package`、`task build:release` 和 `task i:release` 必须提供非空的 `MARKETING_VERSION` 与 `CURRENT_PROJECT_VERSION`,不使用工程默认版本。参数可以通过命令行、环境变量或本机 `.env` 提供,只覆盖本次 Release 构建,不改写 `project.yml`。`MARKETING_VERSION` 接受三个以点分隔的数字、可选的 `v` 前缀和以 `-` 开头的预发布后缀。后缀中的标识符使用 ASCII 字母、数字或连字符,以点分隔,例如 `v2.0.0-alpha-1`、`2.0.0-beta.2` 和 `v2.0.0-rc.1`。`CURRENT_PROJECT_VERSION` 接受一至三个以点分隔的数字。`package:notarize` 复用已有 DMG,不要求重新提供版本参数。
|
||||
`task package`, `task build:release`, and `task i:release` require non-empty `MARKETING_VERSION` and `CURRENT_PROJECT_VERSION`; they do not use the project defaults. Supply parameters on the command line, through environment variables, or in a local `.env` file. They override only the current Release build and do not rewrite `project.yml`. `MARKETING_VERSION` accepts three dot-separated numbers, an optional `v` prefix, and an optional prerelease suffix beginning with `-`. Identifiers in the suffix use ASCII letters, digits, or hyphens and are separated by dots; examples include `v2.0.0-alpha-1`, `2.0.0-beta.2`, and `v2.0.0-rc.1`. `CURRENT_PROJECT_VERSION` accepts one to three dot-separated numbers. `package:notarize` reuses an existing DMG and does not require the version parameters again.
|
||||
|
||||
预发布构建示例:
|
||||
Example prerelease build:
|
||||
|
||||
```bash
|
||||
task package NOTARY_PROFILE=shadowsocks-release \
|
||||
MARKETING_VERSION=v2.0.0-alpha-1 CURRENT_PROJECT_VERSION=22
|
||||
```
|
||||
|
||||
DMG 文件名保留完整发布版本:`ShadowsocksX-NG2-v2.0.0-alpha-1-22-arm64.dmg`。Task 用计算变量提取 `2.0.0` 传给 Xcode,因此 App 的 `CFBundleShortVersionString` 仍符合 Apple 的三段数字格式。预发布后缀不写入这个字段;App 内的预发布构建用递增的 `CURRENT_PROJECT_VERSION` 区分。不同 alpha、beta、rc 和正式构建都应继续递增构建号。
|
||||
The DMG filename retains the complete release version: `ShadowsocksX-NG2-v2.0.0-alpha-1-22-arm64.dmg`. Task extracts `2.0.0` and passes it to Xcode, so the app's `CFBundleShortVersionString` remains in Apple's three-number format. The prerelease suffix is not written to this field; prerelease builds inside the app are distinguished by the increasing `CURRENT_PROJECT_VERSION`. Continue incrementing the build number for every alpha, beta, rc, and final build.
|
||||
|
||||
流程由 `Taskfile.package.yml` 编排:检查公证凭据 → 获取外部二进制 → 真实进程冒烟 → Release 构建 → App 打包门槛 → 制作压缩 DMG → DMG 签名 → Apple 公证 → 装订票据 → 签名、票据及 Gatekeeper 检查 → 发布到 `dist/`。构建只支持 arm64。DMG 只包含 App 和指向 `/Applications` 的快捷方式,不增加背景图或 Finder 自动化依赖。
|
||||
`Taskfile.package.yml` orchestrates the flow: check notarization credentials → fetch external binaries → real-process smoke test → Release build → app packaging gate → create a compressed DMG → sign the DMG → notarize with Apple → staple the ticket → check signature, ticket, and Gatekeeper → publish to `dist/`. Builds support arm64 only. The DMG contains only the app and a shortcut to `/Applications`; it does not add a background image or depend on Finder automation.
|
||||
|
||||
DMG 复用 App 实际签名证书的 SHA-1 指纹选择签名身份,使用安全时间戳。公证仅接收 `NOTARY_PROFILE` 指定的钥匙串配置名称。发布产物示例为 `dist/ShadowsocksX-NG2-2.1.0-21-arm64.dmg`。名称保留传入的完整发布版本。构建号始终从 App 的 Info.plist 读取。打包前校验发布版本的数字部分与 App 版本一致,防止文件名与包内版本不符。已有同名发布产物时明确失败,不覆盖文件。
|
||||
The DMG is signed using the SHA-1 fingerprint of the certificate that actually signed the app, with a secure timestamp. Notarization uses only the keychain profile named by `NOTARY_PROFILE`. An example release artifact is `dist/ShadowsocksX-NG2-2.1.0-21-arm64.dmg`. The filename retains the supplied full release version. The build number is always read from the app's Info.plist. Before packaging, the numeric part of the release version is checked against the app version to prevent a mismatch between the filename and the version inside the app. If an artifact with the same name already exists, the process fails explicitly and does not overwrite it.
|
||||
|
||||
`package` 根据必填版本参数,在工具检查、公证凭据检查、冒烟和构建之前检查同名产物。制盘、公证和最终发布时仍保留覆盖保护,防止并发打包产生覆盖。
|
||||
Before tool checks, notarization-credential checks, smoke tests, or builds, `package` checks for an existing artifact with the required versioned filename. Overwrite protection also remains in place when creating the disk image, notarizing, and publishing, to prevent concurrent packaging runs from overwriting one another.
|
||||
|
||||
每次构建使用独立的 `.build/packaging/run-*/`,其中保留签名 DMG、构建产物与冒烟结果。每次公证在 `.build/notarization/run-*/` 保存提交响应、提交编号、等待结果与 Apple 诊断日志。控制台打印这两个目录。提交和等待分别执行,等待中断时仍保留提交编号。公证等待上限为 30 分钟;本地等待结束后 Apple 仍可能继续处理。
|
||||
Each build uses a separate `.build/packaging/run-*/` directory that retains the signed DMG, build artifacts, and smoke-test results. Each notarization uses `.build/notarization/run-*/` to retain the submission response, submission ID, wait results, and Apple's diagnostic logs. The console prints both directories. Submission and waiting are separate operations, so the submission ID remains available if waiting is interrupted. Notarization waits for up to 30 minutes; Apple may continue processing after the local wait ends.
|
||||
|
||||
公证失败后,可以直接重跑保留的签名 DMG,无须重新构建:
|
||||
After notarization fails, you can retry the retained signed DMG without rebuilding:
|
||||
|
||||
```bash
|
||||
task package:notarize \
|
||||
@@ -83,32 +83,32 @@ task package:notarize \
|
||||
NOTARY_PROFILE=shadowsocks-release
|
||||
```
|
||||
|
||||
此入口会重新提交公证。若此前仅等待超时,可以先用保留的提交编号查询 Apple 状态,避免重复提交:
|
||||
This entry point submits the DMG to Apple again. If only the wait timed out, query Apple with the retained submission ID first to avoid a duplicate submission:
|
||||
|
||||
```bash
|
||||
xcrun notarytool info SUBMISSION_ID --keychain-profile shadowsocks-release
|
||||
```
|
||||
|
||||
`package:notarize` 仅在 Apple 返回 `Accepted` 后装订 DMG 票据。签名、票据和 Gatekeeper 检查全部通过后,才发布完整文件到 `dist/`。失败时不发布新文件。此流程只装订 DMG 票据,不另外分发独立 App。公证重跑入口不代替完整入口中的冒烟和 App 打包门槛;它接收此前完整入口生成的签名 DMG。
|
||||
`package:notarize` staples the DMG ticket only after Apple returns `Accepted`. The complete file is published to `dist/` only after signature, ticket, and Gatekeeper checks pass. A failure does not publish a new file. This workflow staples a ticket to the DMG; it does not distribute a separate app. The retry entry point does not replace the smoke test or app packaging gate from the full release entry point; it accepts a signed DMG produced by that full flow.
|
||||
|
||||
`task clean` 删除 `.build/` 中间产物及日志,保留 `dist/`。仓库忽略这两类产物。此流程不上传 GitHub Release。
|
||||
`task clean` deletes intermediate artifacts and logs under `.build/` but preserves `dist/`. Both output locations are ignored by Git. This workflow does not upload a GitHub Release.
|
||||
|
||||
工具用法见 Apple 的[公证流程](https://developer.apple.com/documentation/security/customizing-the-notarization-workflow)与[分发打包说明](https://developer.apple.com/documentation/xcode/packaging-mac-software-for-distribution)。
|
||||
See Apple's [notarization workflow](https://developer.apple.com/documentation/security/customizing-the-notarization-workflow) and [software packaging for distribution](https://developer.apple.com/documentation/xcode/packaging-mac-software-for-distribution) for tool usage.
|
||||
|
||||
### 单独检查 App
|
||||
### Check an app separately
|
||||
|
||||
已有构建产物时,可以单独运行发布门槛检查:
|
||||
When a build artifact already exists, run the release gate separately:
|
||||
|
||||
```bash
|
||||
# 发布门槛(必须全绿):真实进程冒烟 + 打包门槛
|
||||
# Release gate (both stages must pass): real-process smoke test and packaging gate.
|
||||
task test:smoke
|
||||
task build:release MARKETING_VERSION=2.1.0 CURRENT_PROJECT_VERSION=21
|
||||
Scripts/packaging-gate.sh \
|
||||
.build/derivedData/Build/Products/Release/ShadowsocksX-NG2.app
|
||||
```
|
||||
|
||||
打包门槛只接受 Developer ID 签名的产物:Team 期望值取自 `Configs/Signing.local.xcconfig`(可用 `EXPECTED_TEAM_ID=<Team ID>` 显式覆盖),两者都没有时该断言直接失败,不静默跳过。无证书的 ad-hoc Debug 产物会在此明确失败(`gate: hint` 会说明原因),这是预期行为。
|
||||
The packaging gate accepts only Developer ID-signed artifacts. It gets the expected Team ID from `Configs/Signing.local.xcconfig` (or an explicit `EXPECTED_TEAM_ID=<Team ID>` override). If neither is available, the assertion fails; it is not silently skipped. An ad-hoc Debug artifact without a certificate fails here explicitly (`gate: hint` explains why), as expected.
|
||||
|
||||
## 依赖升级
|
||||
## Dependency upgrades
|
||||
|
||||
升级任一外部二进制是显式供应链动作:人工复验新 release(下载归档、静态观测哈希/架构/签名,不执行),随后在一次 PR 中同时改 `Vendor/<name>/manifest.json` 的 release/asset/url/archiveSHA256 与对应测试锚点。不接受 `latest`,不在仓库内构建,不做应用内更新通道(升级仅随 app 发版)。
|
||||
Upgrading an external binary is an explicit supply-chain action: manually re-verify the new release (download the archive and inspect its hash, architecture, and signature without executing it), then update the manifest's release, asset, URL, and `archiveSHA256` and the corresponding test anchors in the same PR. Do not use `latest`, build the binary in this repository, or add an in-app update channel; upgrades ship only with an app release.
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
# 外部二进制、签名与分发
|
||||
|
||||
[English](distribution.md) | [返回工程首页](../ShadowsocksX-NG2/README.zh-CN.md)
|
||||
|
||||
除特别说明外,命令从 `ShadowsocksX-NG2/` 执行,文中工程路径也相对于该目录。
|
||||
|
||||
## 外部二进制供应链
|
||||
|
||||
外部二进制只用固定版本的上游 release 构建产物:[`sslocal` 清单](../ShadowsocksX-NG2/Vendor/sslocal/manifest.json)(装于 `Contents/Helpers/`)、[`v2ray-plugin` 清单](../ShadowsocksX-NG2/Vendor/v2ray-plugin/manifest.json) 与 [`ck-client` 清单](../ShadowsocksX-NG2/Vendor/ck-client/manifest.json)(两个插件均装于 `Contents/Helpers/Plugins/`)。清单记录各 release 与哈希;构建链如下(spec #21 D6/D10):
|
||||
|
||||
1. **fetch** — `Scripts/fetch-external-binaries.sh` 下载清单固定 URL 的资产、逐字节校验 `archiveSHA256`,按 `assetFormat` 处理 tar 归档(默认,提取 `archiveMember`)或直接二进制(`binary`),并确认 arm64,落盘 `Vendor/<name>/` 并记录戳。资产哈希不符 → 立即失败;本地缓存漂移或清单变更 → 自动重新下载复验。
|
||||
2. **generate** — `xcodegen generate` 把清单对应二进制作为 Copy Files 项装入 `Contents/Helpers[/Plugins]`。注意顺序:**先 fetch 再 generate**,文件在生成时必须已存在,否则 Copy Files 阶段会被静默丢弃(generate 也会直接报缺文件)。
|
||||
3. **verify on every build** — 预构建脚本阶段重跑 fetch 脚本(缓存命中时只对本地戳快速复验),哈希或清单漂移使构建即失败。
|
||||
4. **sign** — `Scripts/sign-embedded-helpers.sh`(post-build 阶段)对每个二进制 Developer ID 重签:`--options runtime --timestamp --identifier <清单 signIdentifier>`(插件形如 `<bundle-id>.plugin.<name>`);随后 Xcode 完成 app 外层签名,封存重签后的嵌套代码。
|
||||
5. **gate** — 构建产物必须过 `Scripts/packaging-gate.sh <App.app>`:嵌套代码签名严格校验、主应用 DevID + Hardened Runtime 且无 App Sandbox entitlement、逐二进制清单覆盖(Helpers 下每个 Mach-O 都在清单内且 DevID 重签通过)。篡改任一二进制会因签名失效被断言发现。
|
||||
|
||||
## 签名与运行形态基线
|
||||
|
||||
- bundle id `com.qiuyuzhou.ShadowsocksX-NG2`,与 Legacy(`com.qiuyuzhou.ShadowsocksX-NG`)区分;运行时数据落 `~/Library/Application Support/ShadowsocksX-NG2/`。macOS 15+ / arm64。
|
||||
- Hardened Runtime(DevID 发布路径;免证书的 ad-hoc Debug 例外,详见下面的配置说明);不启用 App Sandbox,entitlements 为空 dict;签名样式固定 `Manual`。
|
||||
- **证书与 Team 都不入库**(开源仓库):`project.yml` 不含 `CODE_SIGN_IDENTITY` / `DEVELOPMENT_TEAM`,两个键由 project xcconfig 按 config 在构建期注入:
|
||||
- `Configs/Signing-Debug.xcconfig`(入库)基线是 **ad-hoc(`-`)**:新 clone 与 CI 不需要任何证书就能 `task build` / `task test`。arm64 要求代码至少有 ad-hoc 签名才会被加载,所以「免证书」不是「不签名」。ad-hoc 产物不带 Hardened Runtime(Xcode 对 ad-hoc 签名不传 `-o runtime`,实测;`Scripts/sign-embedded-helpers.sh` 的 ad-hoc 分支同样不加),Hardened Runtime 由 DevID 发布路径与打包门槛断言。
|
||||
- `Configs/Signing-Release.xcconfig`(入库)基线是 **Developer ID Application**:发布产物必须真签名,这里不给免证书路径。
|
||||
- 两者都可选包含 `Configs/Signing.local.xcconfig`(gitignore)。有证书的人(含维护者)用它升级本机身份,例如让 Debug 也走 Developer ID:
|
||||
|
||||
```bash
|
||||
cat > Configs/Signing.local.xcconfig <<'EOF'
|
||||
DEVELOPMENT_TEAM = YOUR_TEAM_ID
|
||||
CODE_SIGN_IDENTITY = Developer ID Application
|
||||
EOF
|
||||
```
|
||||
|
||||
- 该文件缺失时 Debug 构建为 ad-hoc(可跑单测),Release 构建以 Xcode 的「Signing for … requires selecting either a development team or a provisioning profile」明确失败,不静默降级。
|
||||
- 测试 target(单测与冒烟)身份随同一基线:去宿主化(ADR-0021)后 xctest 包不再注入宿主 app,ad-hoc 也能加载执行(实测),因此测试无需证书。
|
||||
- 注意 `project.yml` 一旦重新设置这两个键,pbxproj 构建设置会盖掉 xcconfig(`Tests/Architecture/SigningConfigurationTests.swift` 守卫该约束)。
|
||||
- Release 配置关闭 `CODE_SIGN_INJECT_BASE_ENTITLEMENTS`,保证发布产物 entitlements 只来自项目文件。
|
||||
- Release 配置通过 `OTHER_CODE_SIGN_FLAGS = $(inherited) --timestamp` 为 Xcode 签名的主程序和两个原生工具请求安全时间戳。打包门槛在上传公证前检查这些时间戳;DMG 的时间戳不能替代包内可执行文件的时间戳。
|
||||
|
||||
## 发布门槛
|
||||
|
||||
### 签名并公证的 DMG
|
||||
|
||||
从 `ShadowsocksX-NG2/` 执行。首次准备外部二进制及 Developer ID 签名配置的方法见上文。钥匙串中必须有对应证书及私钥。
|
||||
|
||||
先用 Apple 的工具保存公证凭据。此命令交互输入 App 专用密码;凭据保存在钥匙串中,不写入仓库或 Taskfile:
|
||||
|
||||
```bash
|
||||
xcrun notarytool store-credentials shadowsocks-release \
|
||||
--apple-id YOUR_APPLE_ID --team-id YOUR_TEAM_ID
|
||||
```
|
||||
|
||||
完整发布入口:
|
||||
|
||||
```bash
|
||||
task package NOTARY_PROFILE=shadowsocks-release \
|
||||
MARKETING_VERSION=2.1.0 CURRENT_PROJECT_VERSION=21
|
||||
```
|
||||
|
||||
`task package`、`task build:release` 和 `task i:release` 必须提供非空的 `MARKETING_VERSION` 与 `CURRENT_PROJECT_VERSION`,不使用工程默认版本。参数可以通过命令行、环境变量或本机 `.env` 提供,只覆盖本次 Release 构建,不改写 `project.yml`。`MARKETING_VERSION` 接受三个以点分隔的数字、可选的 `v` 前缀和以 `-` 开头的预发布后缀。后缀中的标识符使用 ASCII 字母、数字或连字符,以点分隔,例如 `v2.0.0-alpha-1`、`2.0.0-beta.2` 和 `v2.0.0-rc.1`。`CURRENT_PROJECT_VERSION` 接受一至三个以点分隔的数字。`package:notarize` 复用已有 DMG,不要求重新提供版本参数。
|
||||
|
||||
预发布构建示例:
|
||||
|
||||
```bash
|
||||
task package NOTARY_PROFILE=shadowsocks-release \
|
||||
MARKETING_VERSION=v2.0.0-alpha-1 CURRENT_PROJECT_VERSION=22
|
||||
```
|
||||
|
||||
DMG 文件名保留完整发布版本:`ShadowsocksX-NG2-v2.0.0-alpha-1-22-arm64.dmg`。Task 用计算变量提取 `2.0.0` 传给 Xcode,因此 App 的 `CFBundleShortVersionString` 仍符合 Apple 的三段数字格式。预发布后缀不写入这个字段;App 内的预发布构建用递增的 `CURRENT_PROJECT_VERSION` 区分。不同 alpha、beta、rc 和正式构建都应继续递增构建号。
|
||||
|
||||
流程由 `Taskfile.package.yml` 编排:检查公证凭据 → 获取外部二进制 → 真实进程冒烟 → Release 构建 → App 打包门槛 → 制作压缩 DMG → DMG 签名 → Apple 公证 → 装订票据 → 签名、票据及 Gatekeeper 检查 → 发布到 `dist/`。构建只支持 arm64。DMG 只包含 App 和指向 `/Applications` 的快捷方式,不增加背景图或 Finder 自动化依赖。
|
||||
|
||||
DMG 复用 App 实际签名证书的 SHA-1 指纹选择签名身份,使用安全时间戳。公证仅接收 `NOTARY_PROFILE` 指定的钥匙串配置名称。发布产物示例为 `dist/ShadowsocksX-NG2-2.1.0-21-arm64.dmg`。名称保留传入的完整发布版本。构建号始终从 App 的 Info.plist 读取。打包前校验发布版本的数字部分与 App 版本一致,防止文件名与包内版本不符。已有同名发布产物时明确失败,不覆盖文件。
|
||||
|
||||
`package` 根据必填版本参数,在工具检查、公证凭据检查、冒烟和构建之前检查同名产物。制盘、公证和最终发布时仍保留覆盖保护,防止并发打包产生覆盖。
|
||||
|
||||
每次构建使用独立的 `.build/packaging/run-*/`,其中保留签名 DMG、构建产物与冒烟结果。每次公证在 `.build/notarization/run-*/` 保存提交响应、提交编号、等待结果与 Apple 诊断日志。控制台打印这两个目录。提交和等待分别执行,等待中断时仍保留提交编号。公证等待上限为 30 分钟;本地等待结束后 Apple 仍可能继续处理。
|
||||
|
||||
公证失败后,可以直接重跑保留的签名 DMG,无须重新构建:
|
||||
|
||||
```bash
|
||||
task package:notarize \
|
||||
DMG_PATH=/absolute/path/to/ShadowsocksX-NG2-2.1.0-21-arm64.dmg \
|
||||
NOTARY_PROFILE=shadowsocks-release
|
||||
```
|
||||
|
||||
此入口会重新提交公证。若此前仅等待超时,可以先用保留的提交编号查询 Apple 状态,避免重复提交:
|
||||
|
||||
```bash
|
||||
xcrun notarytool info SUBMISSION_ID --keychain-profile shadowsocks-release
|
||||
```
|
||||
|
||||
`package:notarize` 仅在 Apple 返回 `Accepted` 后装订 DMG 票据。签名、票据和 Gatekeeper 检查全部通过后,才发布完整文件到 `dist/`。失败时不发布新文件。此流程只装订 DMG 票据,不另外分发独立 App。公证重跑入口不代替完整入口中的冒烟和 App 打包门槛;它接收此前完整入口生成的签名 DMG。
|
||||
|
||||
`task clean` 删除 `.build/` 中间产物及日志,保留 `dist/`。仓库忽略这两类产物。此流程不上传 GitHub Release。
|
||||
|
||||
工具用法见 Apple 的[公证流程](https://developer.apple.com/documentation/security/customizing-the-notarization-workflow)与[分发打包说明](https://developer.apple.com/documentation/xcode/packaging-mac-software-for-distribution)。
|
||||
|
||||
### 单独检查 App
|
||||
|
||||
已有构建产物时,可以单独运行发布门槛检查:
|
||||
|
||||
```bash
|
||||
# 发布门槛(必须全绿):真实进程冒烟 + 打包门槛
|
||||
task test:smoke
|
||||
task build:release MARKETING_VERSION=2.1.0 CURRENT_PROJECT_VERSION=21
|
||||
Scripts/packaging-gate.sh \
|
||||
.build/derivedData/Build/Products/Release/ShadowsocksX-NG2.app
|
||||
```
|
||||
|
||||
打包门槛只接受 Developer ID 签名的产物:Team 期望值取自 `Configs/Signing.local.xcconfig`(可用 `EXPECTED_TEAM_ID=<Team ID>` 显式覆盖),两者都没有时该断言直接失败,不静默跳过。无证书的 ad-hoc Debug 产物会在此明确失败(`gate: hint` 会说明原因),这是预期行为。
|
||||
|
||||
## 依赖升级
|
||||
|
||||
升级任一外部二进制是显式供应链动作:人工复验新 release(下载归档、静态观测哈希/架构/签名,不执行),随后在一次 PR 中同时改 `Vendor/<name>/manifest.json` 的 release/asset/url/archiveSHA256 与对应测试锚点。不接受 `latest`,不在仓库内构建,不做应用内更新通道(升级仅随 app 发版)。
|
||||
+48
-48
@@ -1,63 +1,63 @@
|
||||
# 工程结构与源码搜索
|
||||
# Project Structure and Source Search
|
||||
|
||||
[返回工程首页](../ShadowsocksX-NG2/README.md)
|
||||
[Project home](../ShadowsocksX-NG2/README.md) | [简体中文](project-structure.zh-CN.md)
|
||||
|
||||
除特别说明外,命令从 `ShadowsocksX-NG2/` 执行,文中工程路径也相对于该目录。
|
||||
Unless noted otherwise, run commands from `ShadowsocksX-NG2/`; paths in this document are relative to that directory.
|
||||
|
||||
## 工程结构
|
||||
## Project structure
|
||||
|
||||
- `project.yml` — XcodeGen 工程定义,唯一事实来源。`.xcodeproj` 与 `App/Resources/Info.plist`、`*.entitlements`、`Tests/Info.plist` 都是生成物(已 gitignore),改工程结构一律改 `project.yml` 后重新生成。
|
||||
- `App/` — app target 源码(SwiftUI 菜单栏 app,LSUIElement)。目录即分层,层内按功能分组:
|
||||
- `Composition/` — 组合根与进程生命周期:`@main`、依赖装配、应用形态与窗口激活策略。
|
||||
- `Application/` — 应用层(无 SwiftUI 视图):`*Workflow/` 工作流 module(UI-facing interface 与实现协作者)、`ProxyRuntime/` 运行时控制器家族与事实投影、`Services/` LaunchAgent/登录项/helper/防火墙。
|
||||
- `Presentation/` — 跨功能表现层:错误呈现、被多个界面共用的状态模型。
|
||||
- `UI/` — 视图层,按子视图分目录(`Workspace/`、`Home/`、`Servers/`、`Subscriptions/`、`Rules/`、`Settings/`、`Diagnostics/`、`StatusMenu/`,`Servers/Plugin/` 为插件管理子视图)。
|
||||
- `PlatformEffects/` — 平台效应 seam(协议 + AppKit adapter;测试替身在 `Tests/Support/`),系统框架访问集中于此。
|
||||
- `Resources/` — `Info.plist`、`entitlements`(生成物)与 `Assets.xcassets`、`*.xcstrings`(入库)。
|
||||
- `project.yml` — XcodeGen project definition and single source of truth. `.xcodeproj`, `App/Resources/Info.plist`, `*.entitlements`, and `Tests/Info.plist` are generated and gitignored. Change the project structure in `project.yml`, then regenerate the project.
|
||||
- `App/` — app target source code (a SwiftUI menu bar app with `LSUIElement`). Directories define layers; files within each layer are grouped by feature:
|
||||
- `Composition/` — composition root and process lifecycle: `@main`, dependency assembly, app form, and window activation strategy.
|
||||
- `Application/` — application layer without SwiftUI views: `*Workflow/` workflow modules (UI-facing interfaces and implementation collaborators), the `ProxyRuntime/` runtime-controller family and fact projections, and `Services/` for LaunchAgent, login item, helper, and firewall.
|
||||
- `Presentation/` — cross-feature presentation: error presentation and state models shared by multiple views.
|
||||
- `UI/` — view layer, organized by subview (`Workspace/`, `Home/`, `Servers/`, `Subscriptions/`, `Rules/`, `Settings/`, `Diagnostics/`, and `StatusMenu/`); `Servers/Plugin/` contains plugin-management views.
|
||||
- `PlatformEffects/` — platform-effect seams (protocols and AppKit adapters; test doubles are in `Tests/Support/`); access to system frameworks is centralized here.
|
||||
- `Resources/` — generated `Info.plist` and entitlements, plus checked-in `Assets.xcassets` and `*.xcstrings`.
|
||||
|
||||
分层边界由 `Tests/Architecture/SkeletonTests.swift` 的正向圈定守卫断言:只扫描 `App/UI/`,视图层不得引用运行时控制器、工作流实现协作者或原始目录/凭据存储类型;新增运行时适配器放进 `Application/` 即自动落在扫描范围外,无需登记豁免。注意 `App/` 下任何非源码文件都会被 XcodeGen 收进 Resources 阶段并封进签名后的 bundle,因此文档不放 `App/`(`project.yml` 已排除 `**/*.md`)。分层与主题的组织方式、守卫为何取正面圈定、以及 XcodeGen 的上述约束见 [ADR-0029](adr/0029-source-layout-declares-layer-and-theme.md)。
|
||||
- `Agent/` — 独立的后台监管可执行文件,装入 `Contents/MacOS/`,由 LaunchAgent 常驻。只读取非秘密 `agent-supervision.json`,切换稳定配置与 ACL 链接,启动和监管 sslocal;不解析 sslocal 配置或 LAN 凭据。发布与失败处理见 ADR-0037、ADR-0038,GUI 单向配置输出见 ADR-0043。
|
||||
- `LaunchAgent/` — `SMAppService.agent(plistName:)` 的注册清单,装入 `Contents/Library/LaunchAgents/`;`ProgramArguments` 用 bundle 相对路径,由 launchd 按注册 app 的 bundle 位置解析。
|
||||
- `Domain/` — 领域核心(配置目录树、凭据引用与持久化,spec #21 D3/D5),与 UI 无关。目录即主题,根下不留平铺文件,子目录口径取自 [术语表](../GLOSSARY.md) 的章节划分:
|
||||
- `Catalog/` — 配置目录树、结构不变量错误、目录文件存储、节点与凭据身份,以及 `ss://` 编解码 `SsUri`。
|
||||
- `Subscription/` — 订阅文档解析、抓取、订阅记录与刷新失败。
|
||||
- `Rules/` — 代理模式、规则身份与匹配、内置规则快照、规则集合/浏览/分析、离线规则测试;`Rules/Custom/` 为用户自定义规则的编辑草稿、校验与存储。
|
||||
- `Runtime/` — 代理运行服务:运行时文档与监听设置、ACL 与部署收据、路径、原子写入、文件存储、日志与事件(统一日志发射单元 `SystemLog`、统一日志行 `LogLine`、sslocal 输出 intake)、端点/端口/socket 探针。
|
||||
- `Activation/` — 激活候选校验、激活状态机与 `RuntimeConfiguration`。
|
||||
- `Settings/` — 代理监听配置文件、独立代理控制偏好、运行计算的内存快照与静默启动偏好。
|
||||
- `SystemProxy/` — 系统代理 typed 配置、property list 映射、计划器、特权 helper 的 XPC 契约/请求引擎与 helper 侧日志事件。
|
||||
- `Plugin/` — 托管插件、用户插件映射与插件安全校验。
|
||||
- `Credentials/`、`LegacyImport/`、`Diagnostics/` — 主密钥存储与加密文件提交、旧版导入、诊断报告。
|
||||
`Tests/Architecture/SkeletonTests.swift` enforces layer boundaries with an allowlist guard that scans only `App/UI/`. The view layer must not reference runtime controllers, workflow implementation collaborators, or raw directory/credential storage types. New runtime adapters belong in `Application/`, which is outside the scan scope and needs no exemption. Note that XcodeGen includes any non-source file under `App/` in the Resources build phase and signs it into the bundle, so do not put documents there (`project.yml` excludes `**/*.md`). See [ADR-0029](adr/0029-source-layout-declares-layer-and-theme.md) for the layer and theme organization, why the guard uses an allowlist, and the XcodeGen constraint.
|
||||
- `Agent/` — standalone background-supervision executable, installed in `Contents/MacOS/` and kept running by a LaunchAgent. It reads only non-secret `agent-supervision.json`, switches stable configuration and ACL links, and launches and supervises `sslocal`; it does not parse sslocal configuration or LAN credentials. See ADR-0037 and ADR-0038 for release and failure handling, and ADR-0043 for one-way configuration output from the GUI.
|
||||
- `LaunchAgent/` — registration manifest for `SMAppService.agent(plistName:)`, installed in `Contents/Library/LaunchAgents/`. `ProgramArguments` uses bundle-relative paths, which launchd resolves from the registered app's bundle location.
|
||||
- `Domain/` — domain core for the configuration catalog tree, credential references, and persistence (spec #21 D3/D5), independent of the UI. Directories define topics; there are no flat files at the root. Child-directory boundaries follow the sections in the [glossary](../GLOSSARY.md):
|
||||
- `Catalog/` — configuration catalog tree, structural invariant errors, catalog file storage, node and credential identities, and `SsUri` for `ss://` encoding and decoding.
|
||||
- `Subscription/` — subscription document parsing, fetching, subscription records, and refresh failures.
|
||||
- `Rules/` — proxy modes, rule identity and matching, bundled rule snapshots, rule collections/browsing/analysis, and offline rule tests; `Rules/Custom/` contains edit drafts, validation, and storage for user-defined rules.
|
||||
- `Runtime/` — proxy runtime services: runtime documents and listener settings, ACLs and deployment receipts, paths, atomic writes, file storage, logs and events (`SystemLog` unified-log emitter, `LogLine` unified-log line, and sslocal-output intake), plus endpoint/port/socket probes.
|
||||
- `Activation/` — activation-candidate validation, activation state machine, and `RuntimeConfiguration`.
|
||||
- `Settings/` — proxy-listening configuration file, independent proxy-control preferences, in-memory snapshots of runtime calculations, and silent-launch preference.
|
||||
- `SystemProxy/` — typed system-proxy configuration, property-list mapping, planner, privileged-helper XPC contract/request engine, and helper-side log events.
|
||||
- `Plugin/` — managed plugins, user-plugin mappings, and plugin security inspection.
|
||||
- `Credentials/`, `LegacyImport/`, and `Diagnostics/` — master-key storage and encrypted-file commits, legacy import, and diagnostic reports.
|
||||
|
||||
跨进程契约按**文件**编入其它 target:`Runtime/` 的 9 个文件和 `Settings/ApplicationSupportPaths.swift` 同时编入 wrapper(`RuntimePaths`、`RuntimeSupervision`、`RuntimeLog`、`SystemLog`、`LogLine`、`SslocalOutputIntake` 等),`SystemProxy/` 全部 7 个文件连同 `Runtime/SystemLog.swift` 编入特权 helper(helper 侧日志只进统一日志,ADR-0047)。`project.yml` 里这 18 条是显式路径,在 `Domain/` 内移动文件必须同步,漏改会让 `xcodegen generate` 以缺文件直接失败。布局守卫(`Tests/Architecture/SkeletonTests.swift` 的 `DomainLayoutTests`)断言根下无平铺 `.swift`、树下只有 `.swift`(非源码文件会被 XcodeGen 收进 Resources 阶段并封进签名后的 bundle)。
|
||||
- `Tests/` — 单元测试 target,随 `ShadowsocksX-NG2` scheme 运行。目录即主题,根下不留平铺测试文件:
|
||||
- `Architecture/` — 跨切面工程约束守卫(分层与布局、平台效应 seam、开窗落点、清单完整性),扫源码或构建产物而不驱动行为。
|
||||
- `TestSupport/` — 测试等待与清理 helper 的故障回归用例。
|
||||
- `Support/` — 夹具、替身与 helper,不含用例(`CatalogFixtures`、`ProxyRuntimeFixture`、`SystemProxyFakes`、`AppArtifact`、`TestSourceTree` 等)。
|
||||
- `Agent/`、`Activation/`、`Catalog/`(工作流用例在 `Catalog/Workflow/`)、`Subscription/`、`Rules/`(自定义规则在 `Rules/Custom/`)、`ProxyRuntime/`、`ProxyControl/`、`Runtime/`、`Settings/`、`SystemProxy/`、`Plugin/`、`Diagnostics/`、`Credentials/`、`Workspace/`、`Servers/` — 按功能领域的用例,与生产代码的领域主题和 `*Workflow` 同名同界。
|
||||
- `Smoke/` — 真实进程冒烟,独立 target 与 scheme(`task test:smoke`)。
|
||||
- `Fixtures/RuleSnapshots/` — 生成的内置规则快照夹具,作为 folder resource 随测试 bundle 发布(`GeneratedRuleSnapshotTests` 按 `subdirectory:` 读)。
|
||||
Cross-process contracts are compiled into other targets file by file: nine files from `Runtime/` and `Settings/ApplicationSupportPaths.swift` are also compiled into the wrapper (`RuntimePaths`, `RuntimeSupervision`, `RuntimeLog`, `SystemLog`, `LogLine`, `SslocalOutputIntake`, and others); all seven files from `SystemProxy/`, together with `Runtime/SystemLog.swift`, are compiled into the privileged helper (helper logs go only to the unified log; see ADR-0047). These 18 entries are explicit paths in `project.yml`. Moving a file under `Domain/` requires updating the project definition too; otherwise, `xcodegen generate` fails immediately because a file is missing. The layout guard (`DomainLayoutTests` in `Tests/Architecture/SkeletonTests.swift`) asserts that there are no flat `.swift` files at the root and that the tree contains only `.swift` files. XcodeGen would otherwise include non-source files in the Resources build phase and sign them into the bundle.
|
||||
- `Tests/` — unit-test target, run with the `ShadowsocksX-NG2` scheme. Directories define topics; there are no flat test files at the root:
|
||||
- `Architecture/` — guards for cross-cutting project constraints (layers and layout, platform-effect seams, window entry points, and manifest completeness). They scan source or build artifacts without driving behavior.
|
||||
- `TestSupport/` — regression tests for test-wait and cleanup helpers.
|
||||
- `Support/` — fixtures, doubles, and helpers, with no test cases (`CatalogFixtures`, `ProxyRuntimeFixture`, `SystemProxyFakes`, `AppArtifact`, `TestSourceTree`, and others).
|
||||
- `Agent/`, `Activation/`, `Catalog/` (workflow tests are in `Catalog/Workflow/`), `Subscription/`, `Rules/` (custom rules are in `Rules/Custom/`), `ProxyRuntime/`, `ProxyControl/`, `Runtime/`, `Settings/`, `SystemProxy/`, `Plugin/`, `Diagnostics/`, `Credentials/`, `Workspace/`, and `Servers/` — feature-domain tests aligned with production domain topics and `*Workflow` boundaries.
|
||||
- `Smoke/` — real-process smoke tests in a separate target and scheme (`task test:smoke`).
|
||||
- `Fixtures/RuleSnapshots/` — generated fixtures for bundled rule snapshots, distributed as folder resources in the test bundle and read by `GeneratedRuleSnapshotTests` with `subdirectory:`.
|
||||
|
||||
断言需要读源码树(`Vendor/` 清单、`App/`、`Domain/`)时一律经 `Support/TestSourceTree.swift` 从 `#filePath` 沿父目录上溯定位 `ShadowsocksX-NG2/`,不写死父目录层数——测试文件处于不同深度,写死层数会在文件移动后静默指错。布局守卫 `TestsLayoutTests` 只断言根层无平铺 `.swift`(比 `Domain/` 那条弱一档:根层的 `Info.plist` 与 `Fixtures/` 下的 JSON 都必须在)。`Tests/Support/AppArtifact.swift` 同时编入 Smoke target,是 `project.yml` 里的显式文件路径,移动需同步。
|
||||
- `Vendor/<name>/manifest.json` — 外部二进制的固定供应链清单(tag + 资产 URL + 资产 SHA-256 + bundle 内位置 + 签名 identifier);二进制本体与 `.fetched.sha256` 戳是构建缓存,不入库。
|
||||
- `Vendor/rules/geolocation-cn/` — 内置中国域名规则快照(issue #63):`snapshot.json`(规范化规则 + 元数据 + 损失报告)、`manifest.json`(上游版本与快照 SHA-256)、`NOTICE`(许可证与归属)。普通构建只读本地快照,绝不抓取或转换。
|
||||
- `Vendor/rules/china-ipv4/` — 内置中国 IPv4 CIDR 直连候选快照(issue #64):同上三件套,来源为 [gaoyifan/china-operator-ip](https://github.com/gaoyifan/china-operator-ip) `china.txt` 固定 commit。规则模式的 `proxy_all` ACL 同时编入中国域名与 IPv4 CIDR 直连候选。
|
||||
- `Vendor/rules/gfwlist/` — 内置 GFWList 代理候选快照(issue #65):同上三件套,来源为 [gfwlist/gfwlist](https://github.com/gfwlist/gfwlist) 官方 Base64 AutoProxy `gfwlist.txt` 固定 commit。域名锚点转换为后缀匹配,满足条件的空路径或根路径 URL 前缀转换为精确域名匹配;「未匹配时直连」的 `bypass_all` ACL 编入这些代理候选。
|
||||
- `Scripts/` — 供应链脚本(见[外部二进制供应链](distribution.md#外部二进制供应链))与打包门槛断言;另有 `update-geolocation-cn.sh` / `update-china-ipv4.sh` / `update-gfwlist.sh`(显式维护动作,抓取固定版上游并转换)与 `verify-rule-snapshots.sh`(构建前离线校验快照完整性)。
|
||||
When an assertion needs to read the source tree (`Vendor/` manifests, `App/`, or `Domain/`), use `Support/TestSourceTree.swift` to locate `ShadowsocksX-NG2/` by walking up from `#filePath`. Do not hard-code the number of parent directories: test files are at different depths, and a move could make the path silently point elsewhere. `TestsLayoutTests` only asserts that there are no flat `.swift` files at the root. This is weaker than the `Domain/` guard because the root `Info.plist` and JSON under `Fixtures/` are required. `Tests/Support/AppArtifact.swift` is also compiled into the Smoke target and is an explicit file path in `project.yml`; update it when moving the file.
|
||||
- `Vendor/<name>/manifest.json` — pinned supply-chain manifest for an external binary (tag, asset URL, asset SHA-256, location inside the bundle, and signing identifier). The binary itself and `.fetched.sha256` stamp are build cache and are not checked in.
|
||||
- `Vendor/rules/geolocation-cn/` — bundled snapshot of Chinese domain rules (issue #63): `snapshot.json` (normalized rules, metadata, and loss report), `manifest.json` (upstream version and snapshot SHA-256), and `NOTICE` (license and attribution). Normal builds read only the local snapshot; they never fetch or convert rules.
|
||||
- `Vendor/rules/china-ipv4/` — bundled snapshot of Chinese IPv4 CIDRs considered for direct routing (issue #64): the same three files, sourced from a pinned commit of `china.txt` in [gaoyifan/china-operator-ip](https://github.com/gaoyifan/china-operator-ip). The rule-mode `proxy_all` ACL includes both Chinese-domain and IPv4-CIDR direct-routing candidates.
|
||||
- `Vendor/rules/gfwlist/` — bundled snapshot of GFWList proxy candidates (issue #65): the same three files, sourced from the official Base64 AutoProxy `gfwlist.txt` in [gfwlist/gfwlist](https://github.com/gfwlist/gfwlist) at a pinned commit. Domain anchors are converted to suffix matching. Eligible URL prefixes with an empty or root path are converted to exact-domain matching. The `bypass_all` ACL for “direct when unmatched” includes these proxy candidates.
|
||||
- `Scripts/` — supply-chain scripts (see [External binary supply chain](distribution.md#external-binary-supply-chain)) and packaging-gate assertions. `update-geolocation-cn.sh`, `update-china-ipv4.sh`, and `update-gfwlist.sh` are explicit maintenance actions that fetch pinned upstream versions and convert them. `verify-rule-snapshots.sh` checks snapshot integrity offline before a build.
|
||||
|
||||
### 源码搜索
|
||||
## Source search
|
||||
|
||||
先按上面的目录分层用 `rg --files` 定位,再限定目录或文件类型搜索内容;未找到时逐步扩大范围。以下命令从 `ShadowsocksX-NG2/` 执行:
|
||||
First use `rg --files` to locate files based on the directory layers above, then search content in the relevant directory or file type. If there are no matches, expand the search gradually. Run these commands from `ShadowsocksX-NG2/`:
|
||||
|
||||
```sh
|
||||
# 查外部二进制版本与来源,先定位对应清单
|
||||
# Find the external-binary version and source by locating its manifest first.
|
||||
rg --files Vendor/sslocal -g 'manifest.json'
|
||||
# 查运行职责,限定相关 Swift 源码
|
||||
# Search relevant Swift source for runtime responsibilities.
|
||||
rg -n -g '*.swift' 'sslocal|reload' Agent Domain/Runtime App/Application/ProxyRuntime
|
||||
# 查规则内容时先只列出命中文件
|
||||
rg -l '关键词' Vendor/rules Tests/Fixtures/RuleSnapshots
|
||||
# 需要显示匹配行时,省略超长行并保留命中提示
|
||||
rg -n -M 300 '关键词' Vendor/rules
|
||||
# List files matching a rule term before displaying matches.
|
||||
rg -l 'keyword' Vendor/rules Tests/Fixtures/RuleSnapshots
|
||||
# Display matching lines while limiting long lines and retaining match context.
|
||||
rg -n -M 300 'keyword' Vendor/rules
|
||||
```
|
||||
|
||||
`Vendor/rules/*/snapshot.json` 与 `Tests/Fixtures/RuleSnapshots/*.json` 是生成的单行 JSON。`-M 300` 按字节限制长行显示,不限制总输出量;`-m` 和 `head -n` 只限制行数,不能防止单条长行撑大输出。需要完整规则时,定位具体快照后按 JSON 字段读取。清单与 `NOTICE` 也保留在各自的 Vendor 目录中,可按需单独搜索。
|
||||
`Vendor/rules/*/snapshot.json` and `Tests/Fixtures/RuleSnapshots/*.json` are generated, single-line JSON files. `-M 300` limits the displayed length of a line in bytes; it does not limit total output. `-m` and `head -n` limit line counts but do not prevent one long line from producing too much output. To inspect complete rules, locate the specific snapshot and read the relevant JSON fields. Manifests and `NOTICE` files remain in their respective `Vendor` directories and can be searched separately as needed.
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# 工程结构与源码搜索
|
||||
|
||||
[English](project-structure.md) | [返回工程首页](../ShadowsocksX-NG2/README.zh-CN.md)
|
||||
|
||||
除特别说明外,命令从 `ShadowsocksX-NG2/` 执行,文中工程路径也相对于该目录。
|
||||
|
||||
## 工程结构
|
||||
|
||||
- `project.yml` — XcodeGen 工程定义,唯一事实来源。`.xcodeproj` 与 `App/Resources/Info.plist`、`*.entitlements`、`Tests/Info.plist` 都是生成物(已 gitignore),改工程结构一律改 `project.yml` 后重新生成。
|
||||
- `App/` — app target 源码(SwiftUI 菜单栏 app,LSUIElement)。目录即分层,层内按功能分组:
|
||||
- `Composition/` — 组合根与进程生命周期:`@main`、依赖装配、应用形态与窗口激活策略。
|
||||
- `Application/` — 应用层(无 SwiftUI 视图):`*Workflow/` 工作流 module(UI-facing interface 与实现协作者)、`ProxyRuntime/` 运行时控制器家族与事实投影、`Services/` LaunchAgent/登录项/helper/防火墙。
|
||||
- `Presentation/` — 跨功能表现层:错误呈现、被多个界面共用的状态模型。
|
||||
- `UI/` — 视图层,按子视图分目录(`Workspace/`、`Home/`、`Servers/`、`Subscriptions/`、`Rules/`、`Settings/`、`Diagnostics/`、`StatusMenu/`,`Servers/Plugin/` 为插件管理子视图)。
|
||||
- `PlatformEffects/` — 平台效应 seam(协议 + AppKit adapter;测试替身在 `Tests/Support/`),系统框架访问集中于此。
|
||||
- `Resources/` — `Info.plist`、`entitlements`(生成物)与 `Assets.xcassets`、`*.xcstrings`(入库)。
|
||||
|
||||
分层边界由 `Tests/Architecture/SkeletonTests.swift` 的正向圈定守卫断言:只扫描 `App/UI/`,视图层不得引用运行时控制器、工作流实现协作者或原始目录/凭据存储类型;新增运行时适配器放进 `Application/` 即自动落在扫描范围外,无需登记豁免。注意 `App/` 下任何非源码文件都会被 XcodeGen 收进 Resources 阶段并封进签名后的 bundle,因此文档不放 `App/`(`project.yml` 已排除 `**/*.md`)。分层与主题的组织方式、守卫为何取正面圈定、以及 XcodeGen 的上述约束见 [ADR-0029](adr/0029-source-layout-declares-layer-and-theme.md)。
|
||||
- `Agent/` — 独立的后台监管可执行文件,装入 `Contents/MacOS/`,由 LaunchAgent 常驻。只读取非秘密 `agent-supervision.json`,切换稳定配置与 ACL 链接,启动和监管 sslocal;不解析 sslocal 配置或 LAN 凭据。发布与失败处理见 ADR-0037、ADR-0038,GUI 单向配置输出见 ADR-0043。
|
||||
- `LaunchAgent/` — `SMAppService.agent(plistName:)` 的注册清单,装入 `Contents/Library/LaunchAgents/`;`ProgramArguments` 用 bundle 相对路径,由 launchd 按注册 app 的 bundle 位置解析。
|
||||
- `Domain/` — 领域核心(配置目录树、凭据引用与持久化,spec #21 D3/D5),与 UI 无关。目录即主题,根下不留平铺文件,子目录口径取自 [术语表](../GLOSSARY.md) 的章节划分:
|
||||
- `Catalog/` — 配置目录树、结构不变量错误、目录文件存储、节点与凭据身份,以及 `ss://` 编解码 `SsUri`。
|
||||
- `Subscription/` — 订阅文档解析、抓取、订阅记录与刷新失败。
|
||||
- `Rules/` — 代理模式、规则身份与匹配、内置规则快照、规则集合/浏览/分析、离线规则测试;`Rules/Custom/` 为用户自定义规则的编辑草稿、校验与存储。
|
||||
- `Runtime/` — 代理运行服务:运行时文档与监听设置、ACL 与部署收据、路径、原子写入、文件存储、日志与事件(统一日志发射单元 `SystemLog`、统一日志行 `LogLine`、sslocal 输出 intake)、端点/端口/socket 探针。
|
||||
- `Activation/` — 激活候选校验、激活状态机与 `RuntimeConfiguration`。
|
||||
- `Settings/` — 代理监听配置文件、独立代理控制偏好、运行计算的内存快照与静默启动偏好。
|
||||
- `SystemProxy/` — 系统代理 typed 配置、property list 映射、计划器、特权 helper 的 XPC 契约/请求引擎与 helper 侧日志事件。
|
||||
- `Plugin/` — 托管插件、用户插件映射与插件安全校验。
|
||||
- `Credentials/`、`LegacyImport/`、`Diagnostics/` — 主密钥存储与加密文件提交、旧版导入、诊断报告。
|
||||
|
||||
跨进程契约按**文件**编入其它 target:`Runtime/` 的 9 个文件和 `Settings/ApplicationSupportPaths.swift` 同时编入 wrapper(`RuntimePaths`、`RuntimeSupervision`、`RuntimeLog`、`SystemLog`、`LogLine`、`SslocalOutputIntake` 等),`SystemProxy/` 全部 7 个文件连同 `Runtime/SystemLog.swift` 编入特权 helper(helper 侧日志只进统一日志,ADR-0047)。`project.yml` 里这 18 条是显式路径,在 `Domain/` 内移动文件必须同步,漏改会让 `xcodegen generate` 以缺文件直接失败。布局守卫(`Tests/Architecture/SkeletonTests.swift` 的 `DomainLayoutTests`)断言根下无平铺 `.swift`、树下只有 `.swift`(非源码文件会被 XcodeGen 收进 Resources 阶段并封进签名后的 bundle)。
|
||||
- `Tests/` — 单元测试 target,随 `ShadowsocksX-NG2` scheme 运行。目录即主题,根下不留平铺测试文件:
|
||||
- `Architecture/` — 跨切面工程约束守卫(分层与布局、平台效应 seam、开窗落点、清单完整性),扫源码或构建产物而不驱动行为。
|
||||
- `TestSupport/` — 测试等待与清理 helper 的故障回归用例。
|
||||
- `Support/` — 夹具、替身与 helper,不含用例(`CatalogFixtures`、`ProxyRuntimeFixture`、`SystemProxyFakes`、`AppArtifact`、`TestSourceTree` 等)。
|
||||
- `Agent/`、`Activation/`、`Catalog/`(工作流用例在 `Catalog/Workflow/`)、`Subscription/`、`Rules/`(自定义规则在 `Rules/Custom/`)、`ProxyRuntime/`、`ProxyControl/`、`Runtime/`、`Settings/`、`SystemProxy/`、`Plugin/`、`Diagnostics/`、`Credentials/`、`Workspace/`、`Servers/` — 按功能领域的用例,与生产代码的领域主题和 `*Workflow` 同名同界。
|
||||
- `Smoke/` — 真实进程冒烟,独立 target 与 scheme(`task test:smoke`)。
|
||||
- `Fixtures/RuleSnapshots/` — 生成的内置规则快照夹具,作为 folder resource 随测试 bundle 发布(`GeneratedRuleSnapshotTests` 按 `subdirectory:` 读)。
|
||||
|
||||
断言需要读源码树(`Vendor/` 清单、`App/`、`Domain/`)时一律经 `Support/TestSourceTree.swift` 从 `#filePath` 沿父目录上溯定位 `ShadowsocksX-NG2/`,不写死父目录层数——测试文件处于不同深度,写死层数会在文件移动后静默指错。布局守卫 `TestsLayoutTests` 只断言根层无平铺 `.swift`(比 `Domain/` 那条弱一档:根层的 `Info.plist` 与 `Fixtures/` 下的 JSON 都必须在)。`Tests/Support/AppArtifact.swift` 同时编入 Smoke target,是 `project.yml` 里的显式文件路径,移动需同步。
|
||||
- `Vendor/<name>/manifest.json` — 外部二进制的固定供应链清单(tag + 资产 URL + 资产 SHA-256 + bundle 内位置 + 签名 identifier);二进制本体与 `.fetched.sha256` 戳是构建缓存,不入库。
|
||||
- `Vendor/rules/geolocation-cn/` — 内置中国域名规则快照(issue #63):`snapshot.json`(规范化规则 + 元数据 + 损失报告)、`manifest.json`(上游版本与快照 SHA-256)、`NOTICE`(许可证与归属)。普通构建只读本地快照,绝不抓取或转换。
|
||||
- `Vendor/rules/china-ipv4/` — 内置中国 IPv4 CIDR 直连候选快照(issue #64):同上三件套,来源为 [gaoyifan/china-operator-ip](https://github.com/gaoyifan/china-operator-ip) `china.txt` 固定 commit。规则模式的 `proxy_all` ACL 同时编入中国域名与 IPv4 CIDR 直连候选。
|
||||
- `Vendor/rules/gfwlist/` — 内置 GFWList 代理候选快照(issue #65):同上三件套,来源为 [gfwlist/gfwlist](https://github.com/gfwlist/gfwlist) 官方 Base64 AutoProxy `gfwlist.txt` 固定 commit。域名锚点转换为后缀匹配,满足条件的空路径或根路径 URL 前缀转换为精确域名匹配;「未匹配时直连」的 `bypass_all` ACL 编入这些代理候选。
|
||||
- `Scripts/` — 供应链脚本(见[外部二进制供应链](distribution.zh-CN.md#外部二进制供应链))与打包门槛断言;另有 `update-geolocation-cn.sh` / `update-china-ipv4.sh` / `update-gfwlist.sh`(显式维护动作,抓取固定版上游并转换)与 `verify-rule-snapshots.sh`(构建前离线校验快照完整性)。
|
||||
|
||||
### 源码搜索
|
||||
|
||||
先按上面的目录分层用 `rg --files` 定位,再限定目录或文件类型搜索内容;未找到时逐步扩大范围。以下命令从 `ShadowsocksX-NG2/` 执行:
|
||||
|
||||
```sh
|
||||
# 查外部二进制版本与来源,先定位对应清单
|
||||
rg --files Vendor/sslocal -g 'manifest.json'
|
||||
# 查运行职责,限定相关 Swift 源码
|
||||
rg -n -g '*.swift' 'sslocal|reload' Agent Domain/Runtime App/Application/ProxyRuntime
|
||||
# 查规则内容时先只列出命中文件
|
||||
rg -l '关键词' Vendor/rules Tests/Fixtures/RuleSnapshots
|
||||
# 需要显示匹配行时,省略超长行并保留命中提示
|
||||
rg -n -M 300 '关键词' Vendor/rules
|
||||
```
|
||||
|
||||
`Vendor/rules/*/snapshot.json` 与 `Tests/Fixtures/RuleSnapshots/*.json` 是生成的单行 JSON。`-M 300` 按字节限制长行显示,不限制总输出量;`-m` 和 `head -n` 只限制行数,不能防止单条长行撑大输出。需要完整规则时,定位具体快照后按 JSON 字段读取。清单与 `NOTICE` 也保留在各自的 Vendor 目录中,可按需单独搜索。
|
||||
+11
-13
@@ -1,21 +1,19 @@
|
||||
# 内置规则快照
|
||||
# Bundled Rule Snapshots
|
||||
|
||||
[返回工程首页](../ShadowsocksX-NG2/README.md)
|
||||
[Project home](../ShadowsocksX-NG2/README.md) | [简体中文](rule-snapshots.zh-CN.md)
|
||||
|
||||
除特别说明外,命令从 `ShadowsocksX-NG2/` 执行,文中工程路径也相对于该目录。
|
||||
Bundled snapshots used by rule mode are explicitly maintained artifacts:
|
||||
|
||||
规则模式使用的内置快照是显式维护产物:
|
||||
- **geolocation-cn** (issue #63): after manually reviewing the upstream source, run `Scripts/update-geolocation-cn.sh`. It parses typed entries from the pinned `geosite.dat` release of [Loyalsoldier/domain-list-custom](https://github.com/Loyalsoldier/domain-list-custom) and generates `Vendor/rules/geolocation-cn/snapshot.json`.
|
||||
- **china-ipv4** (issue #64): run `Scripts/update-china-ipv4.sh` to normalize and deduplicate IPv4 CIDRs from the pinned `china.txt` version of [gaoyifan/china-operator-ip](https://github.com/gaoyifan/china-operator-ip), generating `Vendor/rules/china-ipv4/snapshot.json`.
|
||||
- **gfwlist** (issue #65): run `Scripts/update-gfwlist.sh` to decode the official Base64 AutoProxy list from a pinned `gfwlist.txt` version of [gfwlist/gfwlist](https://github.com/gfwlist/gfwlist), generating `Vendor/rules/gfwlist/snapshot.json`. `||host` and `||host^` are converted to domain-suffix matches. A `|scheme://host` rule (optionally with a valid port) is converted to an exact-domain match when its path is empty or `/`, it has no query, fragment, or user information, and its host is a valid domain without wildcards; the scheme and port are ignored. Only a root path is eligible; rules with a specific path are skipped. `@@` retains the direct-routing action. Other URL prefixes, paths, wildcards, regular expressions, single-label prefixes, and IP-literal rules are counted by category in the loss report. A `@@` exception completely covered by proxy rules is not written as an ineffective ACL entry (`sslocal` domain matching gives `proxy_list` priority over `bypass_list`). The proxy rule is retained, and the number of shadowed exceptions is included only in `absorbedCount`. The maintenance script prints per-rule evidence to stdout.
|
||||
|
||||
- **geolocation-cn**(issue #63):人工复验上游后运行 `Scripts/update-geolocation-cn.sh`,从固定版 [Loyalsoldier/domain-list-custom](https://github.com/Loyalsoldier/domain-list-custom) `geosite.dat` 解析 typed 条目并生成 `Vendor/rules/geolocation-cn/snapshot.json`。
|
||||
- **china-ipv4**(issue #64):运行 `Scripts/update-china-ipv4.sh`,从固定版 [gaoyifan/china-operator-ip](https://github.com/gaoyifan/china-operator-ip) `china.txt` 规范化并去重 IPv4 CIDR,生成 `Vendor/rules/china-ipv4/snapshot.json`。
|
||||
- **gfwlist**(issue #65):运行 `Scripts/update-gfwlist.sh`,从固定版 [gfwlist/gfwlist](https://github.com/gfwlist/gfwlist) `gfwlist.txt` 解码官方 Base64 AutoProxy 列表并生成 `Vendor/rules/gfwlist/snapshot.json`。`||host` / `||host^` 转换为域名后缀匹配;`|scheme://host`(可含合法端口)在路径为空或仅为 `/`,无查询、fragment 或用户信息,且 host 为无通配符的合法域名时,提取 host 转换为精确域名匹配,忽略协议和端口。仅含根路径 `/` 可转换,包含具体路径的规则继续跳过。`@@` 保留直连动作。其他 URL 前缀、路径、通配符、正则、单标签前缀与 IP 字面量规则逐类计入损失报告。被代理规则完全覆盖的 `@@` 例外不写入无效 ACL 项(sslocal 域名匹配 proxy_list 优先于 bypass_list),保留代理规则,遮蔽数只计入 `absorbedCount`;逐条取证输出到维护脚本 stdout。
|
||||
Snapshots use compact JSON with schema 2 / converter 2.0.0: each rule has only `action` and `match`; the source appears only in `metadata.source`; the conversion report retains counts only. Absorbed `.cn` entries and shadowed GFWList exceptions are not distributed in the snapshot and are not restored after a broad rule is disabled. See [ADR-0024](adr/0024-snapshot-drops-absorbed-and-forensics.md). Older snapshot versions are rejected.
|
||||
|
||||
快照使用 schema 2 / converter 2.0.0 的紧凑 JSON:每条规则只有 `action` 和 `match`,来源只在 `metadata.source`,转换报告只保留计数。被吸收的 `.cn` 条目与被遮蔽的 GFWList 例外不随快照分发,禁用宽规则后不再恢复;详见 [ADR-0024](adr/0024-snapshot-drops-absorbed-and-forensics.md)。旧版本快照拒载。
|
||||
Domain values and file DTOs for rules evolve independently. `ProxyRule` represents both a normalized rule value and rule identity across sources; custom entries also retain a UUID. Custom-rule files are written as schema 3 and no longer store `source` per rule. Schemas 1 and 2 remain readable; reading does not rewrite the file, and the next normal save updates its format. The bundled snapshot format is unchanged. See [ADR-0039](adr/0039-rule-values-and-independent-storage-models.md).
|
||||
|
||||
规则领域值与文件 DTO 独立演化。`ProxyRule` 同时表达规范化规则值与跨来源规则身份,自定义条目另外保留 UUID。自定义规则文件写入 schema 3,不再逐条保存 `source`;兼容读取 schema 1/2,读取不重写,下一次正常保存才更新格式。内置快照格式不变。见 [ADR-0039](adr/0039-rule-values-and-independent-storage-models.md)。
|
||||
Python is the only upstream conversion implementation. Swift loads, validates, and consumes snapshots; it does not parse upstream input independently. Run the offline regression checks for conversion scripts with `PYTHONDONTWRITEBYTECODE=1 python3 Scripts/test-rule-snapshot-converters.py`. The default `task test` also runs both Python checks; run `task test:rules` for just these checks. Also run `PYTHONDONTWRITEBYTECODE=1 python3 Scripts/test-rule-conversion-behavior.py` to verify conversion behavior and the fixed snapshot fixtures used by the unit-test bundle. After reviewing a conversion change, add `--write-fixtures` to the same command to rebuild the fixtures. Fixture timestamps are fixed, and normal tests compare generated output byte for byte. Check snapshot integrity with `Scripts/verify-rule-snapshots.sh`.
|
||||
|
||||
Python 是唯一的上游转换实现;Swift 只加载、校验并消费快照,不另行解析上游输入。转换脚本的离线回归检查:`PYTHONDONTWRITEBYTECODE=1 python3 Scripts/test-rule-snapshot-converters.py`。默认 `task test` 也会运行两组 Python 检查;可单独运行 `task test:rules`。另运行 `PYTHONDONTWRITEBYTECODE=1 python3 Scripts/test-rule-conversion-behavior.py`,验证转换行为和单测 bundle 使用的固定快照夹具。审查转换变化后,可通过同一命令加 `--write-fixtures` 重建夹具。夹具时间固定,正常测试逐字节检查生成结果。快照完整性检查:`Scripts/verify-rule-snapshots.sh`。
|
||||
If an update fails, the previous valid snapshot is kept. geolocation-cn conversion fails on unknown syntax or corrupted input. china-ipv4 conversion also fails on abnormal formats (when too many lines are rejected), when all entries are invalid, or when the snapshot size changes abnormally compared with the previous version. gfwlist conversion fails on unknown syntax, corrupted Base64, abnormal size, or an abnormal size change compared with the previous version. Normal builds run `Scripts/verify-rule-snapshots.sh` offline to check that snapshots exist, their digests match, and schema/converter versions agree; missing or corrupted snapshots fail the build. Distributed artifacts must include each source's `NOTICE` (license and attribution).
|
||||
|
||||
更新失败保留上一份有效快照。geolocation-cn 转换会因未知语法/损坏输入失败;china-ipv4 转换还会因异常格式(拒绝率过高)、全部失效或相对上一份快照的异常规模变化失败;gfwlist 转换因未知语法、损坏 Base64、异常规模或相对上一份快照的异常规模变化失败。普通构建由 `Scripts/verify-rule-snapshots.sh` 离线校验快照存在、摘要匹配且 schema/转换器版本一致,缺失或损坏即构建失败。分发物必须携带各自的 `NOTICE`(许可证与归属)。
|
||||
|
||||
规则模式「未匹配时代理」生成的 `proxy_all` ACL 同时包含中国域名与中国 IPv4 CIDR 直连候选,固定本地绕过优先。规则模式「未匹配时直连」生成的 `bypass_all` ACL 只编入 GFWList 可生效代理候选;两种默认动作不把全部内置来源无条件并集。未匹配目标与无可用规则命中的 IP 字面目标直连。**CIDR 判定可能触发本地 DNS 查询**:sslocal 为未命中域名做 IP 匹配时可能发起本地 DNS 查询,产品不承诺 DNS 查询均经远端 Shadowsocks 服务器。
|
||||
In rule mode, “proxy when unmatched” generates a `proxy_all` ACL containing both Chinese-domain and Chinese IPv4-CIDR direct-routing candidates, with fixed local bypass taking priority. “Direct when unmatched” generates a `bypass_all` ACL containing only effective GFWList proxy candidates. The two default actions do not unconditionally union all bundled sources. Unmatched destinations and IP literals that match no available rule go direct. **CIDR checks may trigger local DNS queries:** when matching an unmatched domain by IP, sslocal may perform a local DNS query. The product does not promise that all DNS queries travel through the remote Shadowsocks server.
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# 内置规则快照
|
||||
|
||||
[English](rule-snapshots.md) | [返回工程首页](../ShadowsocksX-NG2/README.zh-CN.md)
|
||||
|
||||
除特别说明外,命令从 `ShadowsocksX-NG2/` 执行,文中工程路径也相对于该目录。
|
||||
|
||||
规则模式使用的内置快照是显式维护产物:
|
||||
|
||||
- **geolocation-cn**(issue #63):人工复验上游后运行 `Scripts/update-geolocation-cn.sh`,从固定版 [Loyalsoldier/domain-list-custom](https://github.com/Loyalsoldier/domain-list-custom) `geosite.dat` 解析 typed 条目并生成 `Vendor/rules/geolocation-cn/snapshot.json`。
|
||||
- **china-ipv4**(issue #64):运行 `Scripts/update-china-ipv4.sh`,从固定版 [gaoyifan/china-operator-ip](https://github.com/gaoyifan/china-operator-ip) `china.txt` 规范化并去重 IPv4 CIDR,生成 `Vendor/rules/china-ipv4/snapshot.json`。
|
||||
- **gfwlist**(issue #65):运行 `Scripts/update-gfwlist.sh`,从固定版 [gfwlist/gfwlist](https://github.com/gfwlist/gfwlist) `gfwlist.txt` 解码官方 Base64 AutoProxy 列表并生成 `Vendor/rules/gfwlist/snapshot.json`。`||host` / `||host^` 转换为域名后缀匹配;`|scheme://host`(可含合法端口)在路径为空或仅为 `/`,无查询、fragment 或用户信息,且 host 为无通配符的合法域名时,提取 host 转换为精确域名匹配,忽略协议和端口。仅含根路径 `/` 可转换,包含具体路径的规则继续跳过。`@@` 保留直连动作。其他 URL 前缀、路径、通配符、正则、单标签前缀与 IP 字面量规则逐类计入损失报告。被代理规则完全覆盖的 `@@` 例外不写入无效 ACL 项(sslocal 域名匹配 proxy_list 优先于 bypass_list),保留代理规则,遮蔽数只计入 `absorbedCount`;逐条取证输出到维护脚本 stdout。
|
||||
|
||||
快照使用 schema 2 / converter 2.0.0 的紧凑 JSON:每条规则只有 `action` 和 `match`,来源只在 `metadata.source`,转换报告只保留计数。被吸收的 `.cn` 条目与被遮蔽的 GFWList 例外不随快照分发,禁用宽规则后不再恢复;详见 [ADR-0024](adr/0024-snapshot-drops-absorbed-and-forensics.md)。旧版本快照拒载。
|
||||
|
||||
规则领域值与文件 DTO 独立演化。`ProxyRule` 同时表达规范化规则值与跨来源规则身份,自定义条目另外保留 UUID。自定义规则文件写入 schema 3,不再逐条保存 `source`;兼容读取 schema 1/2,读取不重写,下一次正常保存才更新格式。内置快照格式不变。见 [ADR-0039](adr/0039-rule-values-and-independent-storage-models.md)。
|
||||
|
||||
Python 是唯一的上游转换实现;Swift 只加载、校验并消费快照,不另行解析上游输入。转换脚本的离线回归检查:`PYTHONDONTWRITEBYTECODE=1 python3 Scripts/test-rule-snapshot-converters.py`。默认 `task test` 也会运行两组 Python 检查;可单独运行 `task test:rules`。另运行 `PYTHONDONTWRITEBYTECODE=1 python3 Scripts/test-rule-conversion-behavior.py`,验证转换行为和单测 bundle 使用的固定快照夹具。审查转换变化后,可通过同一命令加 `--write-fixtures` 重建夹具。夹具时间固定,正常测试逐字节检查生成结果。快照完整性检查:`Scripts/verify-rule-snapshots.sh`。
|
||||
|
||||
更新失败保留上一份有效快照。geolocation-cn 转换会因未知语法/损坏输入失败;china-ipv4 转换还会因异常格式(拒绝率过高)、全部失效或相对上一份快照的异常规模变化失败;gfwlist 转换因未知语法、损坏 Base64、异常规模或相对上一份快照的异常规模变化失败。普通构建由 `Scripts/verify-rule-snapshots.sh` 离线校验快照存在、摘要匹配且 schema/转换器版本一致,缺失或损坏即构建失败。分发物必须携带各自的 `NOTICE`(许可证与归属)。
|
||||
|
||||
规则模式「未匹配时代理」生成的 `proxy_all` ACL 同时包含中国域名与中国 IPv4 CIDR 直连候选,固定本地绕过优先。规则模式「未匹配时直连」生成的 `bypass_all` ACL 只编入 GFWList 可生效代理候选;两种默认动作不把全部内置来源无条件并集。未匹配目标与无可用规则命中的 IP 字面目标直连。**CIDR 判定可能触发本地 DNS 查询**:sslocal 为未命中域名做 IP 匹配时可能发起本地 DNS 查询,产品不承诺 DNS 查询均经远端 Shadowsocks 服务器。
|
||||
@@ -128,4 +128,4 @@ The client handles the extension as follows:
|
||||
- Use the SIP-008 standard fields for traffic information; use RFC 3339 strings for expiration and reset dates.
|
||||
- Verify that a standard SIP-008 client that does not support `x_shadowsocksx_ng` still receives all servers in the flat `servers` list.
|
||||
|
||||
For details about how the application presents subscription fields and handles refreshes, see [“Subscription information” in the configuration guide](configuration.md#订阅资料).
|
||||
For details about how the application presents subscription fields and handles refreshes, see [“Subscription information” in the configuration guide](configuration.md#subscription-information).
|
||||
|
||||
@@ -128,4 +128,4 @@ SIP-008 订阅 URL 通常包含访问令牌。请使用不可猜测的令牌,
|
||||
- 流量字段使用 SIP-008 标准字段;到期与重置日期使用本文规定的 RFC 3339 字符串。
|
||||
- 以不支持 `x_shadowsocksx_ng` 的普通 SIP-008 客户端验证时,扁平 `servers` 列表仍包含全部服务器。
|
||||
|
||||
订阅字段的应用内呈现与刷新行为见[配置文档中的“订阅资料”](configuration.md#订阅资料)。
|
||||
订阅字段的应用内呈现与刷新行为见[配置文档中的“订阅资料”](configuration.zh-CN.md#订阅资料)。
|
||||
|
||||
Reference in New Issue
Block a user