Files
ShadowsocksX-NG/ShadowsocksX-NG2/Domain/RuntimePaths.swift
T
Qiu Yuzhou d2a8d6105c feat(system-proxy): route writes through privileged LaunchDaemon helper
Implements issue #71 with ADR-0020 (supersedes ADR-0019's endpoint-signature
cleanup, startup cleanup, and health-gate cleanup).

- New ShadowsocksX-NG2SystemProxyHelper target: app-bundled root LaunchDaemon
  (SMAppService.daemon, on-demand via MachServices) that mechanically applies
  a closed typed configuration or executes an unconditional clear. Clients are
  accepted by code-signing bundle identifier only; requests run serially on a
  shared queue (last-writer-wins).
- SystemProxyConfiguration is now the closed typed apply payload (Codable over
  JSON XPC): explicit SOCKS/HTTP/HTTPS enable + endpoints, PAC and
  auto-discovery disabled with URL/JS removed, explicit simple-hostname
  exclusion, complete ExceptionsList. Unmodeled Proxies keys are preserved and
  equivalent values skip the commit.
- Cleanup is a side effect of a persisted system-proxy intent on-to-off
  transition only: the switch itself, or the agent-off cascade (persist both
  intents off, clear, then stop listeners; failed cascade persistence records
  no transition and clears nothing). Launch with the switch off, health-gate
  closure, invalid targets, and passive events never clear. Failed clears are
  surfaced without deferred retry.
- Endpoint-signature store, ownership migration, and direct Authorization
  Services writes are removed; old signature files stay inert. Helper
  unavailability or pending approval keeps an enabled intent pending and
  surfaces the Login Items approval path (HomeView button + menu label).
2026-09-30 12:26:40 +08:00

67 lines
3.1 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Foundation
/// 跨进程运行时路径契约(spec #21 D5,issue #27):GUI 写、wrapper 读的固定
/// 位置。目录 0700、文件 0600,由写入方与 wrapper 共同维护。
enum RuntimePaths {
static func runtimeDirectory() -> URL {
FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
.appendingPathComponent("ShadowsocksX-NG2", isDirectory: true)
}
/// sslocal 配置契约:激活派生的完整 JSON,GUI 原子写入、wrapper 以绝对路径
/// 交给 `sslocal -c`(也是 SIGUSR1 热重载时上游重读的文件)。
static func runtimeFileURL() -> URL {
runtimeDirectory().appendingPathComponent("sslocal-active.json")
}
/// ACL 稳定链接(ADR-0011):sslocal 经契约 `acl` 键读取;指向某个
/// `acl-<summary>.ini` 变体,换模式只改链接指向。
static func aclFileURL() -> URL {
runtimeDirectory().appendingPathComponent("acl-active.ini")
}
/// wrapper 进程 pid 文件:SMAppService.Status 不暴露运行中 agent 的 pid,
/// wrapper 自写此文件供 GUI 判活(kill(pid, 0))并投递 SIGUSR1。
static func agentPIDFileURL() -> URL {
runtimeDirectory().appendingPathComponent("agent.pid")
}
/// The wrapper's sslocal deployment receipt. ACL-backed receipts are written
/// after the child owns the configured TCP listeners; the GUI checks its
/// digest and PID during direct-mode health checks.
static func agentRuntimeStatusURL() -> URL {
runtimeDirectory().appendingPathComponent("agent-runtime-status.json")
}
/// wrapper 与 sslocal 的收敛日志(stderr 重定向目标);滚动与诊断导出由 #33
/// 完善。不属于「运行时文件」,显式停止时不删除。
static func agentLogURL() -> URL {
runtimeDirectory().appendingPathComponent("agent.log")
}
// 旧版端点签名/所有权文件(system-proxy-endpoint-signature.json、
// system-proxy-ownership.json)按 issue #71 就地遗留:不读取、不迁移、
// 不删除。
}
/// ACL 活动链接的防逃逸校验(ADR-0011):解析 symlink 后必须仍是运行目录
/// 内的普通文件。目录与目标都做 symlink 解析,避免 `/var` vs `/private/var`
/// 造成假阴性。GUI 写入侧与 Agent 加载侧共用此判定。
enum ACLActiveLinkPolicy {
static func resolvesToRegularFile(inside directory: URL, linkURL: URL) -> Bool {
let fileManager = FileManager.default
let linkPath = linkURL.standardizedFileURL.path
guard fileManager.fileExists(atPath: linkPath) else { return false }
let resolved = URL(fileURLWithPath: linkPath).resolvingSymlinksInPath().standardizedFileURL
let resolvedDirectory = URL(fileURLWithPath: directory.path).resolvingSymlinksInPath()
.standardizedFileURL.path
guard resolved.path == resolvedDirectory || resolved.path.hasPrefix(resolvedDirectory + "/")
else { return false }
var isDirectory: ObjCBool = false
guard fileManager.fileExists(atPath: resolved.path, isDirectory: &isDirectory) else {
return false
}
return !isDirectory.boolValue
}
}