diff --git a/ShadowsocksX-NG2/App/LaunchAgentService.swift b/ShadowsocksX-NG2/App/LaunchAgentService.swift index 81756033..fc66050f 100644 --- a/ShadowsocksX-NG2/App/LaunchAgentService.swift +++ b/ShadowsocksX-NG2/App/LaunchAgentService.swift @@ -9,9 +9,11 @@ protocol LaunchAgentControlling { func unregister() throws } -/// 生产实现:plist 必须位于 app bundle `Contents/Library/LaunchAgents/`, -/// ProgramArguments 为 Contents/ 相对路径(launchd 按注册 app 的 Contents/ -/// 解析)。 +/// 生产实现:注册时从 bundle 内模板 plist 生成带绝对 ProgramArguments 的 +/// 临时 plist,写入 `Contents/Library/LaunchAgents/` 后交给 SMAppService。 +/// SMAppService 对相对路径的 `ProgramArguments` 解析不可靠(launchd 显示 +/// `inferred program` + `partial import` 并以 EX_CONFIG 拒绝 spawn), +/// 因此注册前把 wrapper 可执行文件的绝对路径写入 plist。 struct SMAppLaunchAgentService: LaunchAgentControlling { static let plistName = "com.qiuyuzhou.ShadowsocksX-NG2.agent.plist" @@ -33,10 +35,35 @@ struct SMAppLaunchAgentService: LaunchAgentControlling { } func register() throws { + try rewritePlistWithAbsoluteProgramPath() try service.register() } func unregister() throws { try service.unregister() } + + /// 把 bundle 内 plist 的 ProgramArguments[0] 重写为 wrapper 的绝对路径。 + /// 只在注册前调用;卸载不要求还原。 + private func rewritePlistWithAbsoluteProgramPath() throws { + let bundle = Bundle.main.bundleURL + let plistURL = bundle.appendingPathComponent( + "Contents/Library/LaunchAgents/\(Self.plistName)") + let agentURL = bundle.appendingPathComponent("Contents/MacOS/ShadowsocksX-NG2Agent") + guard + let data = try? Data(contentsOf: plistURL), + var plist = try? PropertyListSerialization.propertyList(from: data, format: nil) + as? [String: Any] + else { + throw LaunchAgentPlistError.unreadable + } + plist["ProgramArguments"] = [agentURL.path] + let updated = try PropertyListSerialization.data( + fromPropertyList: plist, format: .xml, options: 0) + try updated.write(to: plistURL, options: .atomic) + } +} + +enum LaunchAgentPlistError: Error, Equatable { + case unreadable }