diff --git a/ShadowsocksX-NG2/App/Application/CatalogWorkflow/PluginOptionsDraft.swift b/ShadowsocksX-NG2/App/Application/CatalogWorkflow/PluginOptionsDraft.swift index 0105a04b..594c27f6 100644 --- a/ShadowsocksX-NG2/App/Application/CatalogWorkflow/PluginOptionsDraft.swift +++ b/ShadowsocksX-NG2/App/Application/CatalogWorkflow/PluginOptionsDraft.swift @@ -3,7 +3,7 @@ import Foundation /// 插件参数会话草稿(见 GLOSSARY.md「Plugin parameter draft」): /// 带稳定行身份的有序参数行 + 原始文本双模式。解析与拼装遵循受管 -/// v2ray-plugin v1.3.2 的 SIP003 转义语义(未转义 `;` 分隔项目、第一个 +/// v2ray-plugin 的 SIP003 转义语义(未转义 `;` 分隔项目、第一个 /// 未转义 `=` 分隔参数名与值、反斜杠转义后续字符);原始参数字符串仍是 /// 持久化、凭据与运行时边界的唯一载荷,本模块状态不进入配置目录。 @MainActor @@ -233,7 +233,7 @@ final class PluginOptionsDraft: ObservableObject { } } -// MARK: - SIP003 解析与编码(v2ray-plugin v1.3.2 args.go 语义) +// MARK: - SIP003 解析与编码(v2ray-plugin args.go 语义) extension PluginOptionsDraft { /// 解析:未转义 `;` 分隔项目,第一个未转义 `=` 分隔参数名与值,反斜杠 diff --git a/ShadowsocksX-NG2/Domain/Plugin/ManagedPluginProviding.swift b/ShadowsocksX-NG2/Domain/Plugin/ManagedPluginProviding.swift index 825a47a4..0bef3747 100644 --- a/ShadowsocksX-NG2/Domain/Plugin/ManagedPluginProviding.swift +++ b/ShadowsocksX-NG2/Domain/Plugin/ManagedPluginProviding.swift @@ -57,7 +57,7 @@ enum ManagedPluginCatalog { executableName: "v2ray-plugin", project: "shadowsocks/v2ray-plugin", projectURL: "https://github.com/shadowsocks/v2ray-plugin", - release: "v1.3.2", + release: "v1.4.0", license: "MIT", signIdentifier: "com.qiuyuzhou.ShadowsocksX-NG2.plugin.v2ray-plugin"), ManagedPluginInfo( diff --git a/ShadowsocksX-NG2/Tests/Architecture/ExternalBinaryManifestTests.swift b/ShadowsocksX-NG2/Tests/Architecture/ExternalBinaryManifestTests.swift index c0169f3f..a40a1a49 100644 --- a/ShadowsocksX-NG2/Tests/Architecture/ExternalBinaryManifestTests.swift +++ b/ShadowsocksX-NG2/Tests/Architecture/ExternalBinaryManifestTests.swift @@ -1,8 +1,6 @@ import XCTest -/// Vendor 外部二进制清单的固定 schema 与锚点值。 -/// 供应链脚本(fetch/sign/gate)与这里共同守护同一份事实: -/// tag + 资产 URL + 归档 SHA-256 一经固定,改动必须显式重做静态清单。 +/// 校验 Vendor 外部二进制清单的通用结构与稳定打包属性;发布值只在清单中维护。 final class ExternalBinaryManifestTests: XCTestCase { private struct Manifest: Decodable { var project: String @@ -58,45 +56,33 @@ final class ExternalBinaryManifestTests: XCTestCase { "\(name): signIdentifier 必须以宿主 bundle id 为前缀") } - func testSslocalManifestPinned() throws { + func testSslocalManifestStructure() throws { let manifest = try manifest("sslocal") try assertPinnedSchema(manifest, name: "sslocal") XCTAssertEqual(manifest.project, "shadowsocks-rust") - XCTAssertEqual(manifest.release, "v1.26.0") let url = try XCTUnwrap(URL(string: manifest.url)) XCTAssertTrue( url.path.hasPrefix("/qiuyuzhou/shadowsocks-rust/releases/download/"), "sslocal 应固定到 ShadowsocksX-NG fork 的 release") - XCTAssertEqual( - manifest.archiveSHA256, - "deda5c3591dd08ec1cd72acdec07c18c33c93ca2d40c6e9fc68121b3e9fa3707") XCTAssertEqual(manifest.bundleSubpath, "Helpers") XCTAssertEqual(manifest.signIdentifier, Self.bundleID + ".sslocal") } - func testV2rayPluginManifestPinned() throws { + func testV2rayPluginManifestStructure() throws { let manifest = try manifest("v2ray-plugin") try assertPinnedSchema(manifest, name: "v2ray-plugin") XCTAssertEqual(manifest.project, "v2ray-plugin") - XCTAssertEqual(manifest.release, "v1.3.2") - XCTAssertEqual( - manifest.archiveSHA256, - "357476695ec06498cdc38f3a9efd5b99e4d601db4807661117bf3bae917286ae") XCTAssertEqual(manifest.archiveMember, "v2ray-plugin_darwin_arm64") XCTAssertEqual(manifest.bundleSubpath, "Helpers/Plugins") // SIP003 插件的签名 identifier 形如 .plugin.。 XCTAssertEqual(manifest.signIdentifier, Self.bundleID + ".plugin.v2ray-plugin") } - func testCloakClientManifestPinned() throws { + func testCloakClientManifestStructure() throws { let manifest = try manifest("ck-client") try assertPinnedSchema(manifest, name: "ck-client") XCTAssertEqual(manifest.project, "Cloak") - XCTAssertEqual(manifest.release, "v2.12.0") XCTAssertEqual(manifest.assetFormat, "binary") XCTAssertNil(manifest.archiveMember) - XCTAssertEqual( - manifest.archiveSHA256, - "dbef5cd5dd1b551a046c422e07854eb4736fc958e684c6532eb7e809b593ad5c") XCTAssertEqual(manifest.bundleSubpath, "Helpers/Plugins") XCTAssertEqual(manifest.signIdentifier, Self.bundleID + ".plugin.ck-client") } diff --git a/ShadowsocksX-NG2/Tests/Diagnostics/DiagnosticReportTests.swift b/ShadowsocksX-NG2/Tests/Diagnostics/DiagnosticReportTests.swift index 87f168e1..226e70b0 100644 --- a/ShadowsocksX-NG2/Tests/Diagnostics/DiagnosticReportTests.swift +++ b/ShadowsocksX-NG2/Tests/Diagnostics/DiagnosticReportTests.swift @@ -123,14 +123,14 @@ final class DiagnosticReportTests: XCTestCase { func testManagedPluginSectionListsNameAndVersionWithoutOptions() throws { var snapshot = poisonedSnapshot(catalog: try poisonedCatalog()) snapshot.managedPlugins = [ - DiagnosticPluginFacts(program: "v2ray-plugin", version: "v1.3.2", present: true), + DiagnosticPluginFacts(program: "v2ray-plugin", version: "test-version", present: true), DiagnosticPluginFacts(program: "shadow-tls", version: "v3", present: false), ] let report = DiagnosticReportBuilder.markdown(from: snapshot) XCTAssertTrue(report.contains("## 受管插件"), "缺少受管插件清单:\n\(report)") - XCTAssertTrue(report.contains("- v2ray-plugin v1.3.2:已提供")) + XCTAssertTrue(report.contains("- v2ray-plugin test-version:已提供")) XCTAssertTrue(report.contains("- shadow-tls v3:缺失")) assertNoSecrets(report) } diff --git a/ShadowsocksX-NG2/Tests/Diagnostics/DiagnosticsReportWorkflowTests.swift b/ShadowsocksX-NG2/Tests/Diagnostics/DiagnosticsReportWorkflowTests.swift index ae1d5b8b..d39fa02c 100644 --- a/ShadowsocksX-NG2/Tests/Diagnostics/DiagnosticsReportWorkflowTests.swift +++ b/ShadowsocksX-NG2/Tests/Diagnostics/DiagnosticsReportWorkflowTests.swift @@ -29,7 +29,7 @@ final class DiagnosticsReportWorkflowTests: DiagnosticsWorkflowTestCase { }, managedPlugins: { [ - DiagnosticPluginFacts(program: "v2ray-plugin", version: "v1.3.2", present: true), + DiagnosticPluginFacts(program: "v2ray-plugin", version: "test-version", present: true), DiagnosticPluginFacts(program: "shadow-tls", version: "v3", present: false), ] }, @@ -50,7 +50,7 @@ final class DiagnosticsReportWorkflowTests: DiagnosticsWorkflowTestCase { XCTAssertTrue(report.contains("servers=1 protocols=socks mode=tcp_and_udp")) XCTAssertTrue(report.contains("服务器:2(配置插件 1;手动 1 / 订阅 1)")) XCTAssertTrue(report.contains("分组:2")) - XCTAssertTrue(report.contains("- v2ray-plugin v1.3.2:已提供")) + XCTAssertTrue(report.contains("- v2ray-plugin test-version:已提供")) XCTAssertTrue(report.contains("- shadow-tls v3:缺失")) XCTAssertTrue(report.contains("| sslocal-active.json | 是 | 0600 | 512 |")) XCTAssertTrue(report.contains("| agent.pid | 否 | — | — |")) diff --git a/ShadowsocksX-NG2/Tests/Plugin/ManagedPluginCatalogTests.swift b/ShadowsocksX-NG2/Tests/Plugin/ManagedPluginCatalogTests.swift index a840a1cd..16c55aaf 100644 --- a/ShadowsocksX-NG2/Tests/Plugin/ManagedPluginCatalogTests.swift +++ b/ShadowsocksX-NG2/Tests/Plugin/ManagedPluginCatalogTests.swift @@ -2,13 +2,10 @@ import XCTest @testable import ShadowsocksX_NG2 -/// 受管集静态事实表与 Vendor 供应链清单的双向锚定: -/// 插件新增/升级必须显式重做静态清单——事实表与 `Vendor/*/manifest.json` -/// 任何一侧漂移即测试失败,与 `ExternalBinaryManifestTests` 共同守护同一份事实。 +/// 受管插件目录与 Vendor 清单的稳定身份和打包事实保持一致。 final class ManagedPluginCatalogTests: XCTestCase { private struct Manifest: Decodable { var project: String - var release: String var binary: String var bundleSubpath: String var signIdentifier: String @@ -45,7 +42,6 @@ final class ManagedPluginCatalogTests: XCTestCase { XCTAssertTrue( info.project.hasSuffix(manifest.project), "\(directory): 受管事实表 project(\(info.project))须锚定清单 project(\(manifest.project))") - XCTAssertEqual(info.release, manifest.release, "\(directory): release 漂移") XCTAssertEqual(info.signIdentifier, manifest.signIdentifier, "\(directory): 重签标识漂移") XCTAssertFalse(info.license.isEmpty, "\(directory): 许可证必须显式") let url = try XCTUnwrap(URL(string: info.projectURL), "\(directory): 项目 URL 不可解析") @@ -61,7 +57,7 @@ final class ManagedPluginCatalogTests: XCTestCase { } func testProgramLookup() { - XCTAssertEqual(ManagedPluginCatalog.info(forProgram: "v2ray-plugin")?.release, "v1.3.2") + XCTAssertNotNil(ManagedPluginCatalog.info(forProgram: "v2ray-plugin")) XCTAssertNil(ManagedPluginCatalog.info(forProgram: "obfs-local"), "集外程序名不在受管集") XCTAssertNil(ManagedPluginCatalog.info(forProgram: ""), "空程序名不在受管集") } diff --git a/ShadowsocksX-NG2/Tests/Smoke/RealSslocalSmokeTests+Plugin.swift b/ShadowsocksX-NG2/Tests/Smoke/RealSslocalSmokeTests+Plugin.swift index ae7d8259..9265b497 100644 --- a/ShadowsocksX-NG2/Tests/Smoke/RealSslocalSmokeTests+Plugin.swift +++ b/ShadowsocksX-NG2/Tests/Smoke/RealSslocalSmokeTests+Plugin.swift @@ -6,7 +6,7 @@ import XCTest final class RealSslocalPluginSmokeTests: RealSslocalSmokeTests { // MARK: - 受管插件端到端 - /// v2ray-plugin v1.3.2 端到端边界:契约携带 bundle 内插件绝对路径 → sslocal + /// 受管插件端到端边界:契约携带 bundle 内插件绝对路径 → sslocal /// 拉起 → SIP003 插件进程建立 → 停止链随 sslocal 一并退出。插件进程退出会 /// 连带 sslocal 退出(以 sslocal 信号为准);真实服务器数据面验证属发布 /// 真实服务器数据面验证属于发布门槛人工项;此处只验证进程边界。 diff --git a/ShadowsocksX-NG2/Vendor/v2ray-plugin/manifest.json b/ShadowsocksX-NG2/Vendor/v2ray-plugin/manifest.json index d51f4a4f..0a159119 100644 --- a/ShadowsocksX-NG2/Vendor/v2ray-plugin/manifest.json +++ b/ShadowsocksX-NG2/Vendor/v2ray-plugin/manifest.json @@ -1,10 +1,10 @@ { "project": "v2ray-plugin", - "release": "v1.3.2", + "release": "v1.4.0", "target": "darwin-arm64", - "asset": "v2ray-plugin-darwin-arm64-v1.3.2.tar.gz", - "url": "https://github.com/shadowsocks/v2ray-plugin/releases/download/v1.3.2/v2ray-plugin-darwin-arm64-v1.3.2.tar.gz", - "archiveSHA256": "357476695ec06498cdc38f3a9efd5b99e4d601db4807661117bf3bae917286ae", + "asset": "v2ray-plugin-darwin-arm64-v1.4.0.tar.gz", + "url": "https://github.com/shadowsocks/v2ray-plugin/releases/download/v1.4.0/v2ray-plugin-darwin-arm64-v1.4.0.tar.gz", + "archiveSHA256": "d8fa5226b9e30f11c9df4823405ac726ece2a81891690482aae97ac604b6e0f7", "archiveMember": "v2ray-plugin_darwin_arm64", "binary": "v2ray-plugin", "bundleSubpath": "Helpers/Plugins", diff --git a/docs/adr/0033-plugin-records-own-aliases-and-local-arguments.md b/docs/adr/0033-plugin-records-own-aliases-and-local-arguments.md index 754bd9e6..9b946005 100644 --- a/docs/adr/0033-plugin-records-own-aliases-and-local-arguments.md +++ b/docs/adr/0033-plugin-records-own-aliases-and-local-arguments.md @@ -4,7 +4,7 @@ 用户记录具有独立持久身份,可编辑名称集合。不同用户记录禁止重名;用户只覆盖明确声明的内置名称,失效覆盖不回退内置程序。删除或移动名称不改写服务器和订阅引用。无版本名称通过先从旧记录删除并保存、再加入新记录并保存转移,接受中间暂时不可解析,以免增加专门的跨记录转移流程。 -内置记录的固定身份、引用名称与打包文件名分开维护。现有 v2ray-plugin v1.3.2 同时提供 `v2ray-plugin` 与 `v2ray-plugin-v1`,仍使用原二进制。内置名称由应用发布维护;本机可以设置内置记录的启动参数与插件传输模式,不必覆盖路径。仍使用内置记录的名称共享这些参数与模式,被用户覆盖的名称使用用户记录自己的参数与模式。 +内置记录的固定身份、引用名称与打包文件名分开维护。当前 v2ray-plugin 同时提供 `v2ray-plugin` 与 `v2ray-plugin-v1`,仍使用同一二进制。内置名称由应用发布维护;本机可以设置内置记录的启动参数与插件传输模式,不必覆盖路径。仍使用内置记录的名称共享这些参数与模式,被用户覆盖的名称使用用户记录自己的参数与模式。 本地启动参数按普通配置保存,不额外接入凭据存储或增加敏感信息处理。每项是一个完整 argument,保留顺序、空格与空字符串,不执行 shell 解析,拒绝无法传给进程的 NUL。运行配置生成时写入 `plugin_args`,与服务器 `plugin_opts` 独立;本地路径和启动参数不进入服务器分享、导出或订阅资料。 diff --git a/docs/distribution.md b/docs/distribution.md index 88c695d2..9265ab81 100644 --- a/docs/distribution.md +++ b/docs/distribution.md @@ -6,7 +6,7 @@ ## 外部二进制供应链 -外部二进制只用固定版本的上游 release 构建产物:`sslocal`([qiuyuzhou/shadowsocks-rust v1.26.0](https://github.com/qiuyuzhou/shadowsocks-rust/releases/tag/v1.26.0),装于 `Contents/Helpers/`)、`v2ray-plugin`(v1.3.2)与 Cloak `ck-client`(v2.12.0,两个插件均装于 `Contents/Helpers/Plugins/`)。信任根是提交在库里的清单哈希;构建链如下(spec #21 D6/D10): +外部二进制只用固定版本的上游 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//` 并记录戳。资产哈希不符 → 立即失败;本地缓存漂移或清单变更 → 自动重新下载复验。 2. **generate** — `xcodegen generate` 把清单对应二进制作为 Copy Files 项装入 `Contents/Helpers[/Plugins]`。注意顺序:**先 fetch 再 generate**,文件在生成时必须已存在,否则 Copy Files 阶段会被静默丢弃(generate 也会直接报缺文件)。