Fix XPC validation
This commit is contained in:
@@ -76,9 +76,15 @@ public struct AppView: View {
|
|||||||
Section {
|
Section {
|
||||||
if rootHelperRegistrationStatus == .enabled {
|
if rootHelperRegistrationStatus == .enabled {
|
||||||
FormButton {
|
FormButton {
|
||||||
performHelperAction {
|
Task {
|
||||||
try HelperServiceManager.unregisterRootHelper()
|
do {
|
||||||
try HelperServiceManager.registerRootHelper()
|
try HelperServiceManager.unregisterRootHelper()
|
||||||
|
try await Task.sleep(for: .seconds(1))
|
||||||
|
try HelperServiceManager.registerRootHelper()
|
||||||
|
refreshHelperStatus()
|
||||||
|
} catch {
|
||||||
|
alert = AlertState(error: error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Label("Update", systemImage: "arrow.down.doc.fill")
|
Label("Update", systemImage: "arrow.down.doc.fill")
|
||||||
|
|||||||
@@ -8,46 +8,22 @@ class RootHelperService: NSObject {
|
|||||||
private var listener: NSXPCListener?
|
private var listener: NSXPCListener?
|
||||||
|
|
||||||
func start() {
|
func start() {
|
||||||
setupLogging()
|
listener = NSXPCListener(machServiceName: AppConfiguration.rootHelperMachService)
|
||||||
startXPCListener()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupLogging() {
|
|
||||||
let basePath = "/var/log/sing-box"
|
|
||||||
try? FileManager.default.createDirectory(atPath: basePath, withIntermediateDirectories: true)
|
|
||||||
|
|
||||||
let logPath = basePath + "/roothelper.log"
|
|
||||||
freopen(logPath, "a", stderr)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func startXPCListener() {
|
|
||||||
let machServiceName = getMachServiceName()
|
|
||||||
listener = NSXPCListener(machServiceName: machServiceName)
|
|
||||||
listener?.delegate = self
|
listener?.delegate = self
|
||||||
listener?.resume()
|
listener?.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getMachServiceName() -> String {
|
|
||||||
if let identifier = Bundle.main.object(forInfoDictionaryKey: "AppGroupIdentifier") as? String {
|
|
||||||
return "\(identifier).helper"
|
|
||||||
}
|
|
||||||
fatalError("Missing AppGroupIdentifier in Info.plist")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension RootHelperService: NSXPCListenerDelegate {
|
extension RootHelperService: NSXPCListenerDelegate {
|
||||||
func listener(_: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
|
func listener(_: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
|
||||||
let allowedBundleIDs = [
|
let systemExtensionID = AppConfiguration.systemExtensionBundleID
|
||||||
AppConfiguration.systemExtensionBundleID,
|
let standaloneID = AppConfiguration.packageName + ".standalone"
|
||||||
AppConfiguration.packageName + ".standalone",
|
let teamID = AppConfiguration.teamID
|
||||||
]
|
let requirement = "(identifier \"\(systemExtensionID)\" or identifier \"\(standaloneID)\") and anchor apple generic and certificate leaf[subject.OU] = \"\(teamID)\""
|
||||||
guard XPCConnectionValidator.validateConnection(
|
do {
|
||||||
newConnection,
|
try newConnection.setCodeSigningRequirement(requirement)
|
||||||
teamID: AppConfiguration.teamID,
|
} catch {
|
||||||
allowedBundleIDs: allowedBundleIDs
|
logger.warning("Rejected XPC connection: \(error.localizedDescription)")
|
||||||
) else {
|
|
||||||
let info = XPCConnectionValidator.getConnectionInfo(newConnection)
|
|
||||||
logger.warning("Rejected XPC connection: pid=\(info.pid), bundleID=\(info.bundleID ?? "unknown"), teamID=\(info.teamID ?? "unknown")")
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,4 +82,8 @@ extension RootHelperService: RootHelperProtocol {
|
|||||||
reply(error as NSError)
|
reply(error as NSError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getVersion(reply: @escaping (String) -> Void) {
|
||||||
|
reply(Bundle.main.version)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,14 +53,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
func listener(_: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
|
func listener(_: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
|
||||||
let allowedBundleIDs = [AppConfiguration.packageName + ".standalone"]
|
let bundleID = AppConfiguration.packageName + ".standalone"
|
||||||
guard XPCConnectionValidator.validateConnection(
|
let requirement = "identifier \"\(bundleID)\" and anchor apple generic and certificate leaf[subject.OU] = \"\(AppConfiguration.teamID)\""
|
||||||
newConnection,
|
do {
|
||||||
teamID: AppConfiguration.teamID,
|
try newConnection.setCodeSigningRequirement(requirement)
|
||||||
allowedBundleIDs: allowedBundleIDs
|
} catch {
|
||||||
) else {
|
logger.warning("Rejected XPC connection: \(error.localizedDescription)")
|
||||||
let info = XPCConnectionValidator.getConnectionInfo(newConnection)
|
|
||||||
logger.warning("Rejected XPC connection: pid=\(info.pid), bundleID=\(info.bundleID ?? "unknown"), teamID=\(info.teamID ?? "unknown")")
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import os
|
||||||
import ServiceManagement
|
import ServiceManagement
|
||||||
|
|
||||||
|
private let logger = Logger(category: "HelperServiceManager")
|
||||||
|
|
||||||
public enum HelperServiceManager {
|
public enum HelperServiceManager {
|
||||||
private static var rootHelperService: SMAppService {
|
private static var rootHelperService: SMAppService {
|
||||||
SMAppService.daemon(plistName: "\(AppConfiguration.rootHelperBundleID).plist")
|
SMAppService.daemon(plistName: "\(AppConfiguration.rootHelperBundleID).plist")
|
||||||
@@ -21,5 +24,25 @@
|
|||||||
public static func unregisterRootHelper() throws {
|
public static func unregisterRootHelper() throws {
|
||||||
try rootHelperService.unregister()
|
try rootHelperService.unregister()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func updateRootHelperIfNeeded() async {
|
||||||
|
guard rootHelperStatus == .enabled else { return }
|
||||||
|
|
||||||
|
do {
|
||||||
|
let installedVersion = try RootHelperClient.shared.getVersion()
|
||||||
|
let currentVersion = Bundle.main.version
|
||||||
|
guard currentVersion != installedVersion else { return }
|
||||||
|
} catch {
|
||||||
|
logger.warning("Failed to get root helper version, updating: \(error.localizedDescription)")
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
try unregisterRootHelper()
|
||||||
|
try await Task.sleep(for: .seconds(1))
|
||||||
|
try registerRootHelper()
|
||||||
|
} catch {
|
||||||
|
logger.error("Failed to update root helper: \(error.localizedDescription)")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
|
|
||||||
func getWorkingDirectorySize(reply: @escaping (Int64, NSError?) -> Void)
|
func getWorkingDirectorySize(reply: @escaping (Int64, NSError?) -> Void)
|
||||||
func cleanWorkingDirectory(reply: @escaping (NSError?) -> Void)
|
func cleanWorkingDirectory(reply: @escaping (NSError?) -> Void)
|
||||||
|
func getVersion(reply: @escaping (String) -> Void)
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum RootHelperXPC {
|
public enum RootHelperXPC {
|
||||||
@@ -217,5 +218,52 @@
|
|||||||
proxy.cleanWorkingDirectory(reply: reply)
|
proxy.cleanWorkingDirectory(reply: reply)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func getVersion() throws -> String {
|
||||||
|
let semaphore = DispatchSemaphore(value: 0)
|
||||||
|
var result: String?
|
||||||
|
var resultError: NSError?
|
||||||
|
|
||||||
|
let conn = getConnection()
|
||||||
|
guard let proxy = conn.remoteObjectProxyWithErrorHandler({ error in
|
||||||
|
logger.error("getVersion XPC error: \(error.localizedDescription)")
|
||||||
|
resultError = error as NSError
|
||||||
|
semaphore.signal()
|
||||||
|
}) as? RootHelperProtocol else {
|
||||||
|
connectionLock.lock()
|
||||||
|
connection = nil
|
||||||
|
connectionLock.unlock()
|
||||||
|
conn.invalidate()
|
||||||
|
throw NSError(domain: "RootHelper", code: -1, userInfo: [
|
||||||
|
NSLocalizedDescriptionKey: "Failed to get RootHelper proxy",
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy.getVersion { version in
|
||||||
|
result = version
|
||||||
|
semaphore.signal()
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeout = DispatchTime.now() + .seconds(5)
|
||||||
|
if semaphore.wait(timeout: timeout) == .timedOut {
|
||||||
|
let error = NSError(domain: "RootHelper", code: -1, userInfo: [
|
||||||
|
NSLocalizedDescriptionKey: "getVersion request timeout",
|
||||||
|
])
|
||||||
|
logger.error("getVersion: timeout")
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
if let error = resultError {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let value = result else {
|
||||||
|
throw NSError(domain: "RootHelper", code: -1, userInfo: [
|
||||||
|
NSLocalizedDescriptionKey: "getVersion returned nil",
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -46,14 +46,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func listener(_: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
|
public func listener(_: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
|
||||||
let allowedBundleIDs = [AppConfiguration.systemExtensionBundleID]
|
let bundleID = AppConfiguration.systemExtensionBundleID
|
||||||
guard XPCConnectionValidator.validateConnection(
|
let requirement = "identifier \"\(bundleID)\" and anchor apple generic and certificate leaf[subject.OU] = \"\(AppConfiguration.teamID)\""
|
||||||
newConnection,
|
do {
|
||||||
teamID: AppConfiguration.teamID,
|
try newConnection.setCodeSigningRequirement(requirement)
|
||||||
allowedBundleIDs: allowedBundleIDs
|
} catch {
|
||||||
) else {
|
logger.warning("Rejected XPC connection: \(error.localizedDescription)")
|
||||||
let info = XPCConnectionValidator.getConnectionInfo(newConnection)
|
|
||||||
logger.warning("Rejected XPC connection: pid=\(info.pid), bundleID=\(info.bundleID ?? "unknown"), teamID=\(info.teamID ?? "unknown")")
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
#if os(macOS)
|
|
||||||
import Foundation
|
|
||||||
import Security
|
|
||||||
|
|
||||||
public struct XPCConnectionInfo {
|
|
||||||
public let pid: pid_t
|
|
||||||
public let bundleID: String?
|
|
||||||
public let teamID: String?
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum XPCConnectionValidator {
|
|
||||||
private static func getSecCode(for connection: NSXPCConnection) -> SecCode? {
|
|
||||||
let pid = connection.processIdentifier
|
|
||||||
var code: SecCode?
|
|
||||||
let attributes = [kSecGuestAttributePid: pid] as CFDictionary
|
|
||||||
guard SecCodeCopyGuestWithAttributes(nil, attributes, [], &code) == errSecSuccess else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
private static func getSigningInfo(_ code: SecCode) -> [String: Any]? {
|
|
||||||
var staticCode: SecStaticCode?
|
|
||||||
guard SecCodeCopyStaticCode(code, [], &staticCode) == errSecSuccess,
|
|
||||||
let staticCode
|
|
||||||
else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var info: CFDictionary?
|
|
||||||
guard SecCodeCopySigningInformation(staticCode, [], &info) == errSecSuccess else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return info as? [String: Any]
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func getConnectionInfo(_ connection: NSXPCConnection) -> XPCConnectionInfo {
|
|
||||||
let pid = connection.processIdentifier
|
|
||||||
|
|
||||||
guard let secCode = getSecCode(for: connection),
|
|
||||||
let signingInfo = getSigningInfo(secCode)
|
|
||||||
else {
|
|
||||||
return XPCConnectionInfo(pid: pid, bundleID: nil, teamID: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
let bundleID = signingInfo[kSecCodeInfoIdentifier as String] as? String
|
|
||||||
let teamID = signingInfo[kSecCodeInfoTeamIdentifier as String] as? String
|
|
||||||
|
|
||||||
return XPCConnectionInfo(pid: pid, bundleID: bundleID, teamID: teamID)
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func validateConnection(
|
|
||||||
_ connection: NSXPCConnection,
|
|
||||||
teamID: String,
|
|
||||||
allowedBundleIDs: [String]
|
|
||||||
) -> Bool {
|
|
||||||
guard let secCode = getSecCode(for: connection) else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
let requirement = "anchor apple generic and certificate leaf[subject.OU] = \"\(teamID)\""
|
|
||||||
var secRequirement: SecRequirement?
|
|
||||||
guard SecRequirementCreateWithString(requirement as CFString, [], &secRequirement) == errSecSuccess,
|
|
||||||
let req = secRequirement,
|
|
||||||
SecCodeCheckValidity(secCode, [], req) == errSecSuccess
|
|
||||||
else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let signingInfo = getSigningInfo(secCode),
|
|
||||||
let bundleID = signingInfo[kSecCodeInfoIdentifier as String] as? String,
|
|
||||||
allowedBundleIDs.contains(bundleID)
|
|
||||||
else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension Bundle {
|
public extension Bundle {
|
||||||
var version: String {
|
var version: String {
|
||||||
infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown"
|
infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown"
|
||||||
}
|
}
|
||||||
|
|||||||
+162
-11
@@ -202,7 +202,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"App Settings" : {
|
"App Settings" : {
|
||||||
|
"localizations" : {
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "应用设置"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"Append `0.0.0.0/31` and `::/127` to `route_exclude_address` if not exists." : {
|
"Append `0.0.0.0/31` and `::/127` to `route_exclude_address` if not exists." : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -562,6 +569,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Context destroyed" : {
|
||||||
|
"localizations" : {
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "上下文已销毁"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Copy" : {
|
"Copy" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"zh-Hans" : {
|
"zh-Hans" : {
|
||||||
@@ -984,7 +1001,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Enable" : {
|
"Enable" : {
|
||||||
|
"localizations" : {
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "启用"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"enforceRoutes" : {
|
"enforceRoutes" : {
|
||||||
"shouldTranslate" : false
|
"shouldTranslate" : false
|
||||||
@@ -1068,6 +1092,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Failed to save rules" : {
|
||||||
|
"localizations" : {
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "保存规则失败"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"false" : {
|
"false" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"zh-Hans" : {
|
"zh-Hans" : {
|
||||||
@@ -1176,10 +1210,24 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Helper Service" : {
|
"Helper Service" : {
|
||||||
|
"localizations" : {
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "辅助服务"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"Helper Service Required" : {
|
"Helper Service Required" : {
|
||||||
|
"localizations" : {
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "需要辅助服务"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"Hide VPN Icon" : {
|
"Hide VPN Icon" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -1355,7 +1403,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"In the standalone version of SFM, reading WiFi state requires this app to be running. After you quit the SFM app, the sing-box service cannot continue to provide `wifi_ssid` and `wifi_bssid` routing rules." : {
|
"In the standalone version of SFM, reading WiFi state requires this app to be running. After you quit the SFM app, the sing-box service cannot continue to provide `wifi_ssid` and `wifi_bssid` routing rules." : {
|
||||||
|
"localizations" : {
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "在 SFM 独立版本中,读取 WiFi 状态需要应用保持运行。退出 SFM 应用后,sing-box 服务将无法继续提供 `wifi_ssid` 和 `wifi_bssid` 路由规则。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"Inbound" : {
|
"Inbound" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -1381,7 +1436,14 @@
|
|||||||
"shouldTranslate" : false
|
"shouldTranslate" : false
|
||||||
},
|
},
|
||||||
"Install" : {
|
"Install" : {
|
||||||
|
"localizations" : {
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "安装"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"Install Network Extension" : {
|
"Install Network Extension" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -1424,6 +1486,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Invalid QR Code" : {
|
"Invalid QR Code" : {
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"zh-Hans" : {
|
"zh-Hans" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@@ -1550,7 +1613,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Managing working directory requires Helper Service." : {
|
"Managing working directory requires Helper Service." : {
|
||||||
|
"localizations" : {
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "管理工作目录需要辅助服务。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"Match Domains" : {
|
"Match Domains" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -1585,6 +1655,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Missing access to selected file" : {
|
||||||
|
"localizations" : {
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "无法访问所选文件"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Missing file" : {
|
||||||
|
"localizations" : {
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "缺少文件"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Missing path" : {
|
"Missing path" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"zh-Hans" : {
|
"zh-Hans" : {
|
||||||
@@ -1897,6 +1987,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Profile missing" : {
|
||||||
|
"localizations" : {
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "配置丢失"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Profile not found" : {
|
||||||
|
"localizations" : {
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "未找到配置"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Profile Override" : {
|
"Profile Override" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"zh-Hans" : {
|
"zh-Hans" : {
|
||||||
@@ -2075,6 +2185,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Scanner Error" : {
|
"Scanner Error" : {
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"zh-Hans" : {
|
"zh-Hans" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@@ -2115,6 +2226,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Service Error" : {
|
"Service Error" : {
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"zh-Hans" : {
|
"zh-Hans" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@@ -2451,7 +2563,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"The sing-box service requires Helper Service to provide process lookup functionality, which supports `process_name` and `process_path` routing rules." : {
|
"The sing-box service requires Helper Service to provide process lookup functionality, which supports `process_name` and `process_path` routing rules." : {
|
||||||
|
"localizations" : {
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "sing-box 服务需要辅助服务来提供进程查找功能,以支持 `process_name` 和 `process_path` 路由规则。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"This app needs to be placed under the Applications folder to work." : {
|
"This app needs to be placed under the Applications folder to work." : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -2464,7 +2583,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"This helper service provides process lookup for `process_name` and `process_path` routing rules, and manages the working directory." : {
|
"This helper service provides process lookup for `process_name` and `process_path` routing rules, and manages the working directory." : {
|
||||||
|
"localizations" : {
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "此辅助服务为 `process_name` 和 `process_path` 路由规则提供进程查找功能,并管理工作目录。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"To Clipboard" : {
|
"To Clipboard" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -2537,7 +2663,24 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Unavailable" : {
|
"Unavailable" : {
|
||||||
|
"localizations" : {
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "不可用"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Unexpected message type %lld" : {
|
||||||
|
"localizations" : {
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "意外的消息类型 %lld"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"Uninstall" : {
|
"Uninstall" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -2570,6 +2713,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Update Failed" : {
|
"Update Failed" : {
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"zh-Hans" : {
|
"zh-Hans" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@@ -2663,7 +2807,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"WiFi State Access" : {
|
"WiFi State Access" : {
|
||||||
|
"localizations" : {
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "WiFi 状态访问"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"Working Directory" : {
|
"Working Directory" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
SHELL := /bin/bash
|
||||||
|
.SHELLFLAGS := -o pipefail -c
|
||||||
|
|
||||||
build_all: build_ios build_macos build_tvos
|
build_all: build_ios build_macos build_tvos
|
||||||
|
|
||||||
build_ios:
|
build_ios:
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class StandaloneApplicationDelegate: ApplicationDelegate {
|
|||||||
UserServiceEndpointPublisher.shared.start()
|
UserServiceEndpointPublisher.shared.start()
|
||||||
Task {
|
Task {
|
||||||
await setupSystemExtension()
|
await setupSystemExtension()
|
||||||
|
await HelperServiceManager.updateRootHelperIfNeeded()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1836,6 +1836,7 @@
|
|||||||
3AAAFB2F2EF5218F004C69AD /* Debug */ = {
|
3AAAFB2F2EF5218F004C69AD /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
APP_GROUP_IDENTIFIER = "group.$(BASE_PACKAGE_IDENTIFIER)";
|
||||||
CODE_SIGN_ENTITLEMENTS = FileProviderExtension/FileProviderExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = FileProviderExtension/FileProviderExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
@@ -1868,6 +1869,7 @@
|
|||||||
3AAAFB302EF5218F004C69AD /* Release */ = {
|
3AAAFB302EF5218F004C69AD /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
APP_GROUP_IDENTIFIER = "group.$(BASE_PACKAGE_IDENTIFIER)";
|
||||||
CODE_SIGN_ENTITLEMENTS = FileProviderExtension/FileProviderExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = FileProviderExtension/FileProviderExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
@@ -2127,6 +2129,7 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
MARKETING_VERSION = "1.13.0-alpha.21";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_PACKAGE_IDENTIFIER).helper";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_PACKAGE_IDENTIFIER).helper";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
RUNTIME_EXCEPTION_ALLOW_DYLD_ENVIRONMENT_VARIABLES = NO;
|
RUNTIME_EXCEPTION_ALLOW_DYLD_ENVIRONMENT_VARIABLES = NO;
|
||||||
@@ -2167,6 +2170,7 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
MARKETING_VERSION = "1.13.0-alpha.21";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_PACKAGE_IDENTIFIER).helper";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_PACKAGE_IDENTIFIER).helper";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
RUNTIME_EXCEPTION_ALLOW_DYLD_ENVIRONMENT_VARIABLES = NO;
|
RUNTIME_EXCEPTION_ALLOW_DYLD_ENVIRONMENT_VARIABLES = NO;
|
||||||
@@ -2513,6 +2517,7 @@
|
|||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
STRIP_STYLE = "non-global";
|
||||||
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx";
|
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx";
|
||||||
SUPPORTS_MACCATALYST = NO;
|
SUPPORTS_MACCATALYST = NO;
|
||||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
@@ -2536,7 +2541,6 @@
|
|||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEFINES_MODULE = YES;
|
DEFINES_MODULE = YES;
|
||||||
STRIP_STYLE = "non-global";
|
|
||||||
DEVELOPMENT_TEAM = 287TTNZF8L;
|
DEVELOPMENT_TEAM = 287TTNZF8L;
|
||||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||||
DYLIB_CURRENT_VERSION = 1;
|
DYLIB_CURRENT_VERSION = 1;
|
||||||
@@ -2561,6 +2565,7 @@
|
|||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
STRIP_STYLE = "non-global";
|
||||||
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx";
|
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx";
|
||||||
SUPPORTS_MACCATALYST = NO;
|
SUPPORTS_MACCATALYST = NO;
|
||||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
|
|||||||
Reference in New Issue
Block a user