mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-10-28 13:13:47 +00:00
refactor(ng2): change bundle id to ShadowsocksX-NG2
Distinct id from Legacy (com.qiuyuzhou.ShadowsocksX-NG) lets NG2 own ~/Library/Application Support/ShadowsocksX-NG2/ directly; the v2/ subdir prefix is removed from all runtime paths. - app/agent/tests targets + LaunchAgent plist renamed accordingly - keychain service, queue labels, and Vendor signIdentifiers follow the new bundle id; embedded helpers re-sign at build time - Legacy handoff/import constants keep pointing at the old id Migration: dev data under ShadowsocksX-NG/v2/ is not carried over (fresh catalog/activation); old LaunchAgent registration and old keychain entries must be cleaned up manually.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import Foundation
|
||||
|
||||
// 代理运行时 wrapper(spec #21 D2/D5/D7,issue #27/#28):LaunchAgent
|
||||
// 常驻进程,读取跨进程契约 `v2/sslocal-active.json`,承载 PAC HTTP endpoint,
|
||||
// 常驻进程,读取跨进程契约 `sslocal-active.json`,承载 PAC HTTP endpoint,
|
||||
// 并以绝对配置路径启动官方 sslocal、监管二者生命周期。
|
||||
//
|
||||
// 协议要点:
|
||||
@@ -17,7 +17,7 @@ import Foundation
|
||||
// 清理后干净退出,避免把上游必然拒绝的配置反复交给 sslocal。
|
||||
//
|
||||
// 测试缝(生产走默认值):`SSXNG_CONTRACT_PATH`、`SSXNG_SSLOCAL_PATH`、
|
||||
// `SSXNG_V2_DIR`(pid 文件与收敛日志的替代目录,测试隔离用)。
|
||||
// `SSXNG_RUNTIME_DIR`(pid 文件与收敛日志的替代目录,测试隔离用)。
|
||||
|
||||
private let environment = ProcessInfo.processInfo.environment
|
||||
|
||||
@@ -29,20 +29,20 @@ private let sslocalURL: URL =
|
||||
environment["SSXNG_SSLOCAL_PATH"].map { URL(fileURLWithPath: $0) }
|
||||
?? Bundle.main.bundleURL.appendingPathComponent("Contents/Helpers/sslocal")
|
||||
|
||||
private let v2DirectoryOverride: URL? = environment["SSXNG_V2_DIR"].map {
|
||||
private let runtimeDirectoryOverride: URL? = environment["SSXNG_RUNTIME_DIR"].map {
|
||||
URL(fileURLWithPath: $0, isDirectory: true)
|
||||
}
|
||||
|
||||
private let pidFileURL: URL =
|
||||
v2DirectoryOverride?.appendingPathComponent("agent.pid") ?? RuntimePaths.agentPIDFileURL()
|
||||
runtimeDirectoryOverride?.appendingPathComponent("agent.pid") ?? RuntimePaths.agentPIDFileURL()
|
||||
|
||||
// 顶层脚本是按源码顺序执行的:队列必须先于下方任何函数调用完成初始化。
|
||||
private let signalsQueue = DispatchQueue(label: "com.qiuyuzhou.ShadowsocksX-NG.agent.signals")
|
||||
private let signalsQueue = DispatchQueue(label: "com.qiuyuzhou.ShadowsocksX-NG2.agent.signals")
|
||||
|
||||
// wrapper 与 sslocal 的输出收敛到应用支持目录内的 0600 日志;launchd 统一日
|
||||
// 志不承载代理细节。
|
||||
redirectStandardStreams(
|
||||
to: v2DirectoryOverride?.appendingPathComponent("agent.log") ?? RuntimePaths.agentLogURL())
|
||||
to: runtimeDirectoryOverride?.appendingPathComponent("agent.log") ?? RuntimePaths.agentLogURL())
|
||||
|
||||
FileManager.default.createFile(
|
||||
atPath: pidFileURL.path,
|
||||
|
||||
@@ -194,7 +194,7 @@ struct DiagnosticsView: View {
|
||||
|
||||
private static func fileFacts() -> [DiagnosticFileFacts] {
|
||||
[
|
||||
DiagnosticFileCollector.collect(label: "v2 目录", url: RuntimePaths.v2Directory()),
|
||||
DiagnosticFileCollector.collect(label: "运行时目录", url: RuntimePaths.runtimeDirectory()),
|
||||
DiagnosticFileCollector.collect(
|
||||
label: "catalog.json", url: CatalogFileStore.defaultFileURL()),
|
||||
DiagnosticFileCollector.collect(
|
||||
|
||||
@@ -12,7 +12,7 @@ protocol LaunchAgentControlling {
|
||||
/// 生产实现:plist 必须位于 app bundle `Contents/Library/LaunchAgents/`,
|
||||
/// ProgramArguments 为 bundle 相对路径(launchd 按注册 app 的 bundle 解析)。
|
||||
struct SMAppLaunchAgentService: LaunchAgentControlling {
|
||||
static let plistName = "com.qiuyuzhou.ShadowsocksX-NG.agent.plist"
|
||||
static let plistName = "com.qiuyuzhou.ShadowsocksX-NG2.agent.plist"
|
||||
|
||||
private let service = SMAppService.agent(plistName: Self.plistName)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Foundation
|
||||
|
||||
/// 活动目标持久化(spec #21 D3/D5):落盘 `~/Library/Application Support/
|
||||
/// ShadowsocksX-NG/v2/activation.json`,权限与原子替换基线同 catalog.json。
|
||||
/// ShadowsocksX-NG2/activation.json`,权限与原子替换基线同 catalog.json。
|
||||
/// 只含目标身份,永不落秘密明文。损坏或版本未知按安全侧恢复为「无活动目标」
|
||||
/// (代理保持停止,由状态机重校验同步);文件系统级错误仍抛出。
|
||||
struct ActivationStateFileStore {
|
||||
@@ -19,11 +19,11 @@ struct ActivationStateFileStore {
|
||||
|
||||
let fileURL: URL
|
||||
|
||||
/// 默认位置:`~/Library/Application Support/ShadowsocksX-NG/v2/activation.json`。
|
||||
/// 默认位置:`~/Library/Application Support/ShadowsocksX-NG2/activation.json`。
|
||||
static func defaultFileURL() -> URL {
|
||||
let support = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[
|
||||
0]
|
||||
return support.appendingPathComponent("ShadowsocksX-NG/v2/activation.json")
|
||||
return support.appendingPathComponent("ShadowsocksX-NG2/activation.json")
|
||||
}
|
||||
|
||||
/// 文件缺失/损坏/版本未知 → `nil`(安全侧:无活动目标)。
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Foundation
|
||||
|
||||
/// 配置树磁盘持久化(spec #21 D5):落盘 `~/Library/Application Support/
|
||||
/// ShadowsocksX-NG/v2/catalog.json`,`v2/` 权限 0700、文件 0600;写临时文件
|
||||
/// ShadowsocksX-NG2/catalog.json`,目录权限 0700、文件 0600;写临时文件
|
||||
/// (创建即 0600)后原子替换。文件只含结构化目录、凭据引用与订阅元数据
|
||||
/// (URL 仍是凭据引用),永不落秘密明文。
|
||||
struct CatalogFileStore {
|
||||
@@ -23,11 +23,11 @@ struct CatalogFileStore {
|
||||
|
||||
let fileURL: URL
|
||||
|
||||
/// 默认位置:`~/Library/Application Support/ShadowsocksX-NG/v2/catalog.json`。
|
||||
/// 默认位置:`~/Library/Application Support/ShadowsocksX-NG2/catalog.json`。
|
||||
static func defaultFileURL() -> URL {
|
||||
let support = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[
|
||||
0]
|
||||
return support.appendingPathComponent("ShadowsocksX-NG/v2/catalog.json")
|
||||
return support.appendingPathComponent("ShadowsocksX-NG2/catalog.json")
|
||||
}
|
||||
|
||||
/// 文件缺失 → 全新空文档;存在但损坏/版本未知/结构不一致 → `.corrupt`。
|
||||
|
||||
@@ -24,7 +24,7 @@ enum CredentialStoreError: Error, Equatable {
|
||||
struct KeychainCredentialStore: CredentialStoring {
|
||||
let service: String
|
||||
|
||||
init(service: String = "com.qiuyuzhou.ShadowsocksX-NG.credentials") {
|
||||
init(service: String = "com.qiuyuzhou.ShadowsocksX-NG2.credentials") {
|
||||
self.service = service
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,8 @@ struct RestoredListenSettings: Equatable {
|
||||
}
|
||||
|
||||
/// 用户监听设置(端口、HTTP 启用、UDP、监听范围)的磁盘持久化,落盘
|
||||
/// `~/Library/Application Support/ShadowsocksX-NG/listen-settings.json`。
|
||||
/// 只承载用户显式确认过的配置;与 v2/ 运行时契约文件分离。监听范围只按
|
||||
/// `~/Library/Application Support/ShadowsocksX-NG2/listen-settings.json`。
|
||||
/// 只承载用户显式确认过的配置;与运行时契约文件分离。监听范围只按
|
||||
/// 原样保留,有效性由派生文档的读取侧校验兜底。
|
||||
struct ListenSettingsFileStore: ListenSettingsStoring {
|
||||
private static let jsonEncoder: JSONEncoder = {
|
||||
@@ -52,7 +52,7 @@ struct ListenSettingsFileStore: ListenSettingsStoring {
|
||||
static func defaultFileURL() -> URL {
|
||||
let support = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[
|
||||
0]
|
||||
return support.appendingPathComponent("ShadowsocksX-NG/listen-settings.json")
|
||||
return support.appendingPathComponent("ShadowsocksX-NG2/listen-settings.json")
|
||||
}
|
||||
|
||||
init(fileURL: URL = ListenSettingsFileStore.defaultFileURL()) {
|
||||
|
||||
@@ -35,7 +35,7 @@ enum ManagedPluginCatalog {
|
||||
projectURL: "https://github.com/shadowsocks/v2ray-plugin",
|
||||
release: "v1.3.2",
|
||||
license: "MIT",
|
||||
signIdentifier: "com.qiuyuzhou.ShadowsocksX-NG.plugin.v2ray-plugin")
|
||||
signIdentifier: "com.qiuyuzhou.ShadowsocksX-NG2.plugin.v2ray-plugin")
|
||||
]
|
||||
|
||||
static func info(forProgram program: String) -> ManagedPluginInfo? {
|
||||
|
||||
@@ -15,7 +15,7 @@ final class PACServer {
|
||||
|
||||
private let configuration: PACRuntimeDocument
|
||||
private let requestHeaderTimeout: TimeInterval
|
||||
private let queue = DispatchQueue(label: "com.qiuyuzhou.ShadowsocksX-NG.pac")
|
||||
private let queue = DispatchQueue(label: "com.qiuyuzhou.ShadowsocksX-NG2.pac")
|
||||
private let lock = NSLock()
|
||||
private var listener: NWListener?
|
||||
/// 只在 queue 上访问;停止时一并取消,避免 listener 已停但旧连接仍可取 PAC。
|
||||
|
||||
@@ -13,7 +13,7 @@ enum LaunchAgentStatus: Equatable, Sendable {
|
||||
case notFound
|
||||
}
|
||||
|
||||
/// wrapper 进程活性:GUI 读 `v2/agent.pid` 并以 `kill(pid, 0)` 判定(D2)。
|
||||
/// wrapper 进程活性:GUI 读 `agent.pid` 并以 `kill(pid, 0)` 判定(D2)。
|
||||
enum WrapperProcessState: Equatable, Sendable {
|
||||
case notRunning
|
||||
case running(pid: Int32)
|
||||
|
||||
@@ -206,7 +206,7 @@ struct ProxySettingsFileStore: ProxySettingsStoring {
|
||||
static func defaultFileURL() -> URL {
|
||||
let support = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[
|
||||
0]
|
||||
return support.appendingPathComponent("ShadowsocksX-NG/settings.json")
|
||||
return support.appendingPathComponent("ShadowsocksX-NG2/settings.json")
|
||||
}
|
||||
|
||||
func load() throws -> ProxySettings {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Foundation
|
||||
|
||||
/// 运行时文件存取(spec #21 D5,issue #27):`v2/sslocal-active.json` 是 GUI
|
||||
/// 运行时文件存取(spec #21 D5,issue #27):`sslocal-active.json` 是 GUI
|
||||
/// 写、wrapper 读的跨进程交接契约。写入复用 `AtomicFileWriter` 的物理安全
|
||||
/// 基线(目录 0700 含自愈、临时文件创建即 0600、写全校验后原子替换);显式
|
||||
/// 停止后的清理只做普通 unlink,不承诺安全擦除(D5)。
|
||||
@@ -16,7 +16,7 @@ struct RuntimeFileStore {
|
||||
self.fileURL = fileURL
|
||||
}
|
||||
|
||||
/// wrapper pid 文件与契约同目录(v2/):GUI 判活与 SIGUSR1 投递依据
|
||||
/// wrapper pid 文件与契约同目录:GUI 判活与 SIGUSR1 投递依据
|
||||
/// (SMAppService 不暴露运行中 agent 的 pid)。
|
||||
var pidFileURL: URL {
|
||||
fileURL.deletingLastPathComponent().appendingPathComponent("agent.pid")
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
import Foundation
|
||||
|
||||
/// 跨进程运行时路径契约(spec #21 D5,issue #27):GUI 写、wrapper 读的固定
|
||||
/// 位置。目录 `v2/` 0700、文件 0600,由写入方与 wrapper 共同维护。
|
||||
/// 位置。目录 0700、文件 0600,由写入方与 wrapper 共同维护。
|
||||
enum RuntimePaths {
|
||||
static func v2Directory() -> URL {
|
||||
static func runtimeDirectory() -> URL {
|
||||
FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
|
||||
.appendingPathComponent("ShadowsocksX-NG/v2", isDirectory: true)
|
||||
.appendingPathComponent("ShadowsocksX-NG2", isDirectory: true)
|
||||
}
|
||||
|
||||
/// sslocal 配置契约:激活派生的完整 JSON,GUI 原子写入、wrapper 以绝对路径
|
||||
/// 交给 `sslocal -c`(也是 SIGUSR1 热重载时上游重读的文件)。
|
||||
static func runtimeFileURL() -> URL {
|
||||
v2Directory().appendingPathComponent("sslocal-active.json")
|
||||
runtimeDirectory().appendingPathComponent("sslocal-active.json")
|
||||
}
|
||||
|
||||
/// wrapper 进程 pid 文件:SMAppService.Status 不暴露运行中 agent 的 pid,
|
||||
/// wrapper 自写此文件供 GUI 判活(kill(pid, 0))并投递 SIGUSR1。
|
||||
static func agentPIDFileURL() -> URL {
|
||||
v2Directory().appendingPathComponent("agent.pid")
|
||||
runtimeDirectory().appendingPathComponent("agent.pid")
|
||||
}
|
||||
|
||||
/// wrapper 与 sslocal 的收敛日志(stderr 重定向目标);滚动与诊断导出由 #33
|
||||
/// 完善。不属于「运行时文件」,显式停止时不删除。
|
||||
static func agentLogURL() -> URL {
|
||||
v2Directory().appendingPathComponent("agent.log")
|
||||
runtimeDirectory().appendingPathComponent("agent.log")
|
||||
}
|
||||
|
||||
/// Original network-service proxy dictionaries captured before 2.0 writes
|
||||
/// system proxy settings. It is protected like the other v2 runtime files.
|
||||
/// system proxy settings. It is protected like the other runtime files.
|
||||
static func systemProxyOwnershipURL() -> URL {
|
||||
v2Directory().appendingPathComponent("system-proxy-ownership.json")
|
||||
runtimeDirectory().appendingPathComponent("system-proxy-ownership.json")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>KeepAlive</key>
|
||||
<dict>
|
||||
<key>SuccessfulExit</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>Label</key>
|
||||
<string>com.qiuyuzhou.ShadowsocksX-NG2.agent</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>Contents/MacOS/ShadowsocksX-NG2Agent</string>
|
||||
</array>
|
||||
<key>ThrottleInterval</key>
|
||||
<integer>2</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -10,7 +10,7 @@ ShadowsocksX-NG 的现代化重写版本。新工程的全部源码与构建配
|
||||
|
||||
- `project.yml` — XcodeGen 工程定义,唯一事实来源。`.xcodeproj` 与 `App/Info.plist`、`*.entitlements`、`Tests/Info.plist` 都是生成物(已 gitignore),改工程结构一律改 `project.yml` 后重新生成。
|
||||
- `App/` — app target 源码(SwiftUI 菜单栏 app,LSUIElement)。
|
||||
- `Agent/` — 代理运行时 wrapper(独立可执行文件,装入 `Contents/MacOS/`,由 LaunchAgent 常驻):读 `v2/sslocal-active.json` 契约、承载版本化 PAC HTTP endpoint,并以绝对路径启动和监管 sslocal(SIGTERM 链式停止、SIGUSR1 热重载/结构重启、崩溃时非零退出交 KeepAlive 重放,spec #21 D2/D5/D7)。
|
||||
- `Agent/` — 代理运行时 wrapper(独立可执行文件,装入 `Contents/MacOS/`,由 LaunchAgent 常驻):读 `sslocal-active.json` 契约、承载版本化 PAC HTTP endpoint,并以绝对路径启动和监管 sslocal(SIGTERM 链式停止、SIGUSR1 热重载/结构重启、崩溃时非零退出交 KeepAlive 重放,spec #21 D2/D5/D7)。
|
||||
- `LaunchAgent/` — `SMAppService.agent(plistName:)` 的注册清单,装入 `Contents/Library/LaunchAgents/`;`ProgramArguments` 用 bundle 相对路径,由 launchd 按注册 app 的 bundle 位置解析。
|
||||
- `Domain/` — 领域核心(配置目录树、凭据引用与持久化,spec #21 D3/D5),与 UI 无关;其中 `RuntimePaths`、`SslocalRuntimeDocument`、`RuntimeLog` 同时编入 wrapper 目标,保证跨进程契约单一来源。
|
||||
- `Tests/` — 单元测试 target,随 `ShadowsocksX-NG2` scheme 运行。
|
||||
@@ -29,7 +29,7 @@ ShadowsocksX-NG 的现代化重写版本。新工程的全部源码与构建配
|
||||
|
||||
## 签名与运行形态基线
|
||||
|
||||
- 继承 bundle id `com.qiuyuzhou.ShadowsocksX-NG`;macOS 15+ / arm64。
|
||||
- bundle id `com.qiuyuzhou.ShadowsocksX-NG2`,与 Legacy(`com.qiuyuzhou.ShadowsocksX-NG`)区分;运行时数据落 `~/Library/Application Support/ShadowsocksX-NG2/`。macOS 15+ / arm64。
|
||||
- Hardened Runtime + Developer ID 签名(`DEVELOPMENT_TEAM` / 证书见 `project.yml`);不启用 App Sandbox,entitlements 为空 dict。
|
||||
- 测试 target 用 Apple Development 证书签名:宿主 app 开启 Hardened Runtime 后 library validation 要求被注入的 xctest 包同 Team 签名。
|
||||
- Release 配置关闭 `CODE_SIGN_INJECT_BASE_ENTITLEMENTS`,保证发布产物 entitlements 只来自项目文件。
|
||||
|
||||
@@ -73,7 +73,7 @@ final class ActivationStateFileStoreTests: XCTestCase {
|
||||
|
||||
func testDefaultFileURLMatchesRuntimeDirectoryContract() {
|
||||
XCTAssertTrue(
|
||||
ActivationStateFileStore.defaultFileURL().path.hasSuffix("ShadowsocksX-NG/v2/activation.json")
|
||||
ActivationStateFileStore.defaultFileURL().path.hasSuffix("ShadowsocksX-NG2/activation.json")
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ final class AgentLifecycleTests: XCTestCase {
|
||||
var environment = ProcessInfo.processInfo.environment
|
||||
environment["SSXNG_CONTRACT_PATH"] = contractURL.path
|
||||
environment["SSXNG_SSLOCAL_PATH"] = stubURL.path
|
||||
environment["SSXNG_V2_DIR"] = workDir.path
|
||||
environment["SSXNG_RUNTIME_DIR"] = workDir.path
|
||||
environment["SSLOCAL_STUB_STATE"] = stubStateURL.path
|
||||
environment["SSLOCAL_STUB_BEHAVIOR"] = behavior
|
||||
process.environment = environment
|
||||
|
||||
@@ -11,7 +11,7 @@ final class DiagnosticFileCollectorTests: XCTestCase {
|
||||
directory = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent("ssxng-diag-\(UUID().uuidString)", isDirectory: true)
|
||||
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
|
||||
// 临时目录默认带 0755:对齐产品基线(v2/ 0700)再断言。
|
||||
// 临时目录默认带 0755:对齐产品基线(运行时目录 0700)再断言。
|
||||
try FileManager.default.setAttributes(
|
||||
[.posixPermissions: 0o700], ofItemAtPath: directory.path)
|
||||
}
|
||||
@@ -38,7 +38,7 @@ final class DiagnosticFileCollectorTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testCollectsDirectoryPermissionsWithoutSize() throws {
|
||||
let facts = DiagnosticFileCollector.collect(label: "v2 目录", url: directory)
|
||||
let facts = DiagnosticFileCollector.collect(label: "运行时目录", url: directory)
|
||||
|
||||
XCTAssertTrue(facts.exists)
|
||||
XCTAssertTrue(facts.isDirectory)
|
||||
|
||||
@@ -167,7 +167,7 @@ final class DiagnosticReportTests: XCTestCase {
|
||||
var snapshot = DiagnosticSnapshot()
|
||||
snapshot.eventLines = [
|
||||
RuntimeLogEvent.runtimePersistFailed(
|
||||
detail: "/Users/SECRETUSER/Library/Application Support/ShadowsocksX-NG/v2: errno 13"
|
||||
detail: "/Users/SECRETUSER/Library/Application Support/ShadowsocksX-NG2: errno 13"
|
||||
).description
|
||||
]
|
||||
snapshot.homePathForRedaction = "/Users/SECRETUSER"
|
||||
|
||||
@@ -16,7 +16,7 @@ final class ExternalBinaryManifestTests: XCTestCase {
|
||||
var signIdentifier: String
|
||||
}
|
||||
|
||||
private static let bundleID = "com.qiuyuzhou.ShadowsocksX-NG"
|
||||
private static let bundleID = "com.qiuyuzhou.ShadowsocksX-NG2"
|
||||
private static let vendorRoot = URL(fileURLWithPath: #filePath)
|
||||
.deletingLastPathComponent() // Tests/
|
||||
.deletingLastPathComponent() // ShadowsocksX-NG2/
|
||||
|
||||
@@ -7,7 +7,7 @@ import XCTest
|
||||
/// 代理开关、目录重展开消费与 GUI 重同步重合(不触真实 SMAppService/launchd)。
|
||||
@MainActor
|
||||
final class ProxyRuntimeControllerTests: XCTestCase {
|
||||
private var runtime: ProxyRuntimeFixture.TemporaryV2!
|
||||
private var runtime: ProxyRuntimeFixture.TemporaryRuntime!
|
||||
private var catalogFileURL: URL!
|
||||
private var activationFileURL: URL!
|
||||
private var credentials: InMemoryCredentialStore!
|
||||
@@ -40,7 +40,7 @@ final class ProxyRuntimeControllerTests: XCTestCase {
|
||||
|
||||
override func setUpWithError() throws {
|
||||
try super.setUpWithError()
|
||||
runtime = ProxyRuntimeFixture.makeTemporaryV2()
|
||||
runtime = ProxyRuntimeFixture.makeTemporaryRuntime()
|
||||
catalogFileURL = runtime.directory.appendingPathComponent("catalog.json")
|
||||
activationFileURL = runtime.directory.appendingPathComponent("activation.json")
|
||||
credentials = InMemoryCredentialStore()
|
||||
|
||||
@@ -3,21 +3,23 @@ import XCTest
|
||||
|
||||
@testable import ShadowsocksX_NG2
|
||||
|
||||
/// 代理运行时测试共享夹具(票 #27):临时 v2 目录、契约文档构造、LaunchAgent
|
||||
/// 代理运行时测试共享夹具(票 #27):临时运行时目录、契约文档构造、LaunchAgent
|
||||
/// 与探测替身。
|
||||
enum ProxyRuntimeFixture {
|
||||
/// 在临时目录中建一套隔离的运行时文件组(不触碰真实 ~/Library)。
|
||||
struct TemporaryV2 {
|
||||
struct TemporaryRuntime {
|
||||
let directory: URL
|
||||
let contract: URL
|
||||
let pidFile: URL
|
||||
}
|
||||
|
||||
static func makeTemporaryV2(file: StaticString = #filePath, line: UInt = #line) -> TemporaryV2 {
|
||||
static func makeTemporaryRuntime(file: StaticString = #filePath, line: UInt = #line)
|
||||
-> TemporaryRuntime
|
||||
{
|
||||
let directory = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent("ssxng-tests-\(UUID().uuidString)", isDirectory: true)
|
||||
try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
|
||||
return TemporaryV2(
|
||||
return TemporaryRuntime(
|
||||
directory: directory,
|
||||
contract: directory.appendingPathComponent("sslocal-active.json"),
|
||||
pidFile: directory.appendingPathComponent("agent.pid"))
|
||||
|
||||
@@ -72,7 +72,7 @@ final class RealSslocalSmokeTests: XCTestCase {
|
||||
var environment = ProcessInfo.processInfo.environment
|
||||
environment["SSXNG_CONTRACT_PATH"] = contractURL.path
|
||||
environment["SSXNG_SSLOCAL_PATH"] = try sslocalURL.path
|
||||
environment["SSXNG_V2_DIR"] = workDir.path
|
||||
environment["SSXNG_RUNTIME_DIR"] = workDir.path
|
||||
process.environment = environment
|
||||
try process.run()
|
||||
return process
|
||||
|
||||
@@ -5,12 +5,12 @@ import XCTest
|
||||
/// 运行时文件存取(spec #21 D5,issue #27):0700/0600 权限基线、原子替换、
|
||||
/// 读取侧判定与显式停止清理。
|
||||
final class RuntimeFileStoreTests: XCTestCase {
|
||||
private var runtime: ProxyRuntimeFixture.TemporaryV2!
|
||||
private var runtime: ProxyRuntimeFixture.TemporaryRuntime!
|
||||
private var store: RuntimeFileStore!
|
||||
|
||||
override func setUpWithError() throws {
|
||||
try super.setUpWithError()
|
||||
runtime = ProxyRuntimeFixture.makeTemporaryV2()
|
||||
runtime = ProxyRuntimeFixture.makeTemporaryRuntime()
|
||||
store = RuntimeFileStore(fileURL: runtime.contract)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,9 @@ final class SkeletonTests: XCTestCase {
|
||||
XCTAssertFalse(delegate.applicationShouldTerminateAfterLastWindowClosed(.shared))
|
||||
}
|
||||
|
||||
func testAppBundleIDInheritedFromLegacy() {
|
||||
XCTAssertEqual(Bundle.main.bundleIdentifier, "com.qiuyuzhou.ShadowsocksX-NG")
|
||||
func testAppBundleIDIsDistinctFromLegacy() {
|
||||
XCTAssertEqual(Bundle.main.bundleIdentifier, "com.qiuyuzhou.ShadowsocksX-NG2")
|
||||
XCTAssertNotEqual(Bundle.main.bundleIdentifier, "com.qiuyuzhou.ShadowsocksX-NG")
|
||||
}
|
||||
|
||||
func testAppIsMenuBarAgent() {
|
||||
|
||||
@@ -6,7 +6,7 @@ import XCTest
|
||||
/// issue #30):占位出厂端口不得部署——停止运行时并点名呈现启动失败。
|
||||
@MainActor
|
||||
final class UnreadableListenSettingsTests: XCTestCase {
|
||||
private var runtime: ProxyRuntimeFixture.TemporaryV2!
|
||||
private var runtime: ProxyRuntimeFixture.TemporaryRuntime!
|
||||
private var catalogFileURL: URL!
|
||||
private var activationFileURL: URL!
|
||||
private var credentials: InMemoryCredentialStore!
|
||||
@@ -15,7 +15,7 @@ final class UnreadableListenSettingsTests: XCTestCase {
|
||||
|
||||
override func setUpWithError() throws {
|
||||
try super.setUpWithError()
|
||||
runtime = ProxyRuntimeFixture.makeTemporaryV2()
|
||||
runtime = ProxyRuntimeFixture.makeTemporaryRuntime()
|
||||
catalogFileURL = runtime.directory.appendingPathComponent("catalog.json")
|
||||
activationFileURL = runtime.directory.appendingPathComponent("activation.json")
|
||||
credentials = InMemoryCredentialStore()
|
||||
|
||||
+1
-1
@@ -8,5 +8,5 @@
|
||||
"archiveMember": "sslocal",
|
||||
"binary": "sslocal",
|
||||
"bundleSubpath": "Helpers",
|
||||
"signIdentifier": "com.qiuyuzhou.ShadowsocksX-NG.sslocal"
|
||||
"signIdentifier": "com.qiuyuzhou.ShadowsocksX-NG2.sslocal"
|
||||
}
|
||||
|
||||
+1
-1
@@ -8,5 +8,5 @@
|
||||
"archiveMember": "v2ray-plugin_darwin_arm64",
|
||||
"binary": "v2ray-plugin",
|
||||
"bundleSubpath": "Helpers/Plugins",
|
||||
"signIdentifier": "com.qiuyuzhou.ShadowsocksX-NG.plugin.v2ray-plugin"
|
||||
"signIdentifier": "com.qiuyuzhou.ShadowsocksX-NG2.plugin.v2ray-plugin"
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ targets:
|
||||
# LaunchAgent 注册清单:SMAppService.agent(plistName:) 从 bundle 内
|
||||
# Contents/Library/LaunchAgents/ 读取;ProgramArguments 用 bundle 相对路径
|
||||
# (spec #21 D2,issue #27)。
|
||||
- path: LaunchAgent/com.qiuyuzhou.ShadowsocksX-NG.agent.plist
|
||||
- path: LaunchAgent/com.qiuyuzhou.ShadowsocksX-NG2.agent.plist
|
||||
buildPhase:
|
||||
copyFiles:
|
||||
destination: wrapper
|
||||
@@ -67,7 +67,7 @@ targets:
|
||||
"$SRCROOT/Scripts/sign-embedded-helpers.sh"
|
||||
settings:
|
||||
base:
|
||||
PRODUCT_BUNDLE_IDENTIFIER: com.qiuyuzhou.ShadowsocksX-NG
|
||||
PRODUCT_BUNDLE_IDENTIFIER: com.qiuyuzhou.ShadowsocksX-NG2
|
||||
PRODUCT_NAME: ShadowsocksX-NG2
|
||||
CODE_SIGN_IDENTITY: "Developer ID Application"
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
|
||||
@@ -114,7 +114,7 @@ targets:
|
||||
settings:
|
||||
base:
|
||||
PRODUCT_NAME: ShadowsocksX-NG2Agent
|
||||
PRODUCT_BUNDLE_IDENTIFIER: com.qiuyuzhou.ShadowsocksX-NG.agent
|
||||
PRODUCT_BUNDLE_IDENTIFIER: com.qiuyuzhou.ShadowsocksX-NG2.agent
|
||||
CODE_SIGN_IDENTITY: "Developer ID Application"
|
||||
ShadowsocksX-NG2Tests:
|
||||
type: bundle.unit-test
|
||||
@@ -123,7 +123,7 @@ targets:
|
||||
- path: Tests
|
||||
settings:
|
||||
base:
|
||||
PRODUCT_BUNDLE_IDENTIFIER: com.qiuyuzhou.ShadowsocksX-NG.Tests
|
||||
PRODUCT_BUNDLE_IDENTIFIER: com.qiuyuzhou.ShadowsocksX-NG2.Tests
|
||||
# 必须与宿主 app 同 Team 签名:Hardened Runtime 的 library validation 拒绝
|
||||
# 加载不同 Team ID 的非平台库,ad-hoc("-")签名的 xctest 包无法注入。
|
||||
CODE_SIGN_IDENTITY: "Apple Development"
|
||||
|
||||
Reference in New Issue
Block a user