refactor: Helper uninstallation and reinstallation logic

This commit is contained in:
X1a0He
2025-03-27 15:36:13 +08:00
parent ee2ac727ce
commit f7a5456da1
4 changed files with 125 additions and 43 deletions

View File

@@ -49,6 +49,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
} }
return event return event
} }
PrivilegedHelperManager.shared.executeCommand("id -u") { _ in }
} }
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {

View File

@@ -228,43 +228,49 @@ class PrivilegedHelperManager: NSObject {
} }
func reinstallHelper(completion: @escaping (Bool, String) -> Void) { func reinstallHelper(completion: @escaping (Bool, String) -> Void) {
disconnectHelper() uninstallHelperViaTerminal { [weak self] success, message in
removeInstallHelper()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
guard let self = self else { return } guard let self = self else { return }
DispatchQueue.main.asyncAfter(deadline: .now()) {
let result = self.installHelperDaemon() let result = self.installHelperDaemon()
switch result {
case .success:
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { [weak self] in
guard let self = self else { return }
self.tryConnect(retryCount: 5, delay: 2.0, completion: completion)
}
case .authorizationFail: switch result {
completion(false, String(localized: "获取授权失败")) case .success:
case .getAdminFail: DispatchQueue.main.asyncAfter(deadline: .now()) { [weak self] in
completion(false, String(localized: "获取管理员权限失败")) guard let self = self else { return }
case .blessError(_):
completion(false, String(localized: "安装失败: \(result.alertContent)")) self.tryConnect(retryCount: 3, delay: 1, completion: completion)
}
case .authorizationFail:
completion(false, String(localized: "获取授权失败"))
case .getAdminFail:
completion(false, String(localized: "获取管理员权限失败"))
case .blessError(_):
completion(false, String(localized: "安装失败: \(result.alertContent)"))
}
} }
} }
} }
private func tryConnect(retryCount: Int, delay: TimeInterval = 2.0, completion: @escaping (Bool, String) -> Void) { private func tryConnect(retryCount: Int, delay: TimeInterval = 2.0, completion: @escaping (Bool, String) -> Void) {
struct Static {
static var currentAttempt = 0
}
if retryCount == 3 {
Static.currentAttempt = 0
}
Static.currentAttempt += 1
guard retryCount > 0 else { guard retryCount > 0 else {
completion(false, String(localized: "多次尝试连接失败")) completion(false, String(localized: "多次尝试连接失败"))
return return
} }
guard let connection = connectToHelper() else { guard let connection = connectToHelper() else {
print("连接尝试失败,剩余重试次数: \(retryCount - 1)")
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
self?.tryConnect(retryCount: retryCount - 1, delay: delay, completion: completion) self?.tryConnect(retryCount: retryCount - 1, delay: delay * 1, completion: completion)
} }
return return
} }
@@ -274,23 +280,29 @@ class PrivilegedHelperManager: NSObject {
return return
} }
helper.executeCommand(type: .shellCommand, path1: "whoami", path2: "", permissions: 0) { result in helper.executeCommand(type: .shellCommand, path1: "id -u", path2: "", permissions: 0) { result in
if result.contains("root") { if result == "0" || result.contains("0") {
completion(true, String(localized: "Helper 重新安装成功")) completion(true, String(localized: "Helper 重新安装成功"))
} else { } else {
completion(false, String(localized: "Helper 安装失败")) print("Helper验证失败返回结果: \(result)")
completion(false, String(localized: "Helper 安装失败: \(result)"))
} }
} }
} }
func removeInstallHelper() { func removeInstallHelper(completion: ((Bool) -> Void)? = nil) {
try? FileManager.default.removeItem(atPath: "/Library/PrivilegedHelperTools/\(PrivilegedHelperManager.machServiceName)") if FileManager.default.fileExists(atPath: "/Library/LaunchDaemons/\(PrivilegedHelperManager.machServiceName).plist") {
try? FileManager.default.removeItem(atPath: "/Library/LaunchDaemons/\(PrivilegedHelperManager.machServiceName).plist") try? FileManager.default.removeItem(atPath: "/Library/LaunchDaemons/\(PrivilegedHelperManager.machServiceName).plist")
}
if FileManager.default.fileExists(atPath: "/Library/PrivilegedHelperTools/\(PrivilegedHelperManager.machServiceName)") {
try? FileManager.default.removeItem(atPath: "/Library/PrivilegedHelperTools/\(PrivilegedHelperManager.machServiceName)")
}
completion?(true)
} }
func connectToHelper() -> NSXPCConnection? { func connectToHelper() -> NSXPCConnection? {
return connectionQueue.sync { return connectionQueue.sync {
createConnection() return createConnection()
} }
} }
@@ -334,8 +346,8 @@ class PrivilegedHelperManager: NSObject {
var isConnected = false var isConnected = false
if let helper = newConnection.remoteObjectProxy as? HelperToolProtocol { if let helper = newConnection.remoteObjectProxy as? HelperToolProtocol {
helper.executeCommand(type: .shellCommand, path1: "whoami", path2: "", permissions: 0) { [weak self] result in helper.executeCommand(type: .shellCommand, path1: "id -u", path2: "", permissions: 0) { [weak self] result in
if result.contains("root") { if result.contains("0") || result == "0" {
isConnected = true isConnected = true
DispatchQueue.main.async { DispatchQueue.main.async {
self?.connection = newConnection self?.connection = newConnection
@@ -344,8 +356,6 @@ class PrivilegedHelperManager: NSObject {
} }
semaphore.signal() semaphore.signal()
} }
_ = semaphore.wait(timeout: .now() + 1.0)
} }
if !isConnected { if !isConnected {
@@ -430,7 +440,7 @@ class PrivilegedHelperManager: NSObject {
do { do {
let helper = try self.getHelperProxy() let helper = try self.getHelperProxy()
helper.executeCommand(type: .install, path1: "whoami", path2: "", permissions: 0) { result in helper.executeCommand(type: .install, path1: "id -u", path2: "", permissions: 0) { result in
DispatchQueue.main.async { DispatchQueue.main.async {
if result == "root" { if result == "root" {
self.connectionState = .connected self.connectionState = .connected
@@ -509,11 +519,14 @@ class PrivilegedHelperManager: NSObject {
func forceReinstallHelper() { func forceReinstallHelper() {
guard !isInitializing else { return } guard !isInitializing else { return }
isInitializing = true isInitializing = true
removeInstallHelper() uninstallHelperViaTerminal { [weak self] success, _ in
notifyInstall() guard let self = self else { return }
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
isInitializing = false self.notifyInstall()
self.isInitializing = false
}
}
} }
func disconnectHelper() { func disconnectHelper() {
@@ -525,6 +538,59 @@ class PrivilegedHelperManager: NSObject {
connectionState = .disconnected connectionState = .disconnected
} }
} }
func uninstallHelperViaTerminal(completion: @escaping (Bool, String) -> Void) {
disconnectHelper()
let script = """
#!/bin/bash
sudo /bin/launchctl unload /Library/LaunchDaemons/\(PrivilegedHelperManager.machServiceName).plist
sudo /bin/rm -f /Library/LaunchDaemons/\(PrivilegedHelperManager.machServiceName).plist
sudo /bin/rm -f /Library/PrivilegedHelperTools/\(PrivilegedHelperManager.machServiceName)
sudo /usr/bin/killall -u root -9 \(PrivilegedHelperManager.machServiceName)
exit 0
"""
let tempDir = FileManager.default.temporaryDirectory
let scriptURL = tempDir.appendingPathComponent("uninstall_helper.sh")
do {
try script.write(to: scriptURL, atomically: true, encoding: .utf8)
try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: scriptURL.path)
let task = Process()
task.executableURL = URL(fileURLWithPath: "/usr/bin/osascript")
task.arguments = ["-e", "do shell script \"\(scriptURL.path)\" with administrator privileges"]
let outputPipe = Pipe()
let errorPipe = Pipe()
task.standardOutput = outputPipe
task.standardError = errorPipe
do {
try task.run()
task.waitUntilExit()
if task.terminationStatus == 0 {
UserDefaults.standard.removeObject(forKey: "InstalledHelperBuild")
connectionState = .disconnected
connection = nil
completion(true, String(localized: "Helper 已完全卸载"))
} else {
let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
let errorString = String(data: errorData, encoding: .utf8) ?? "未知错误"
completion(false, String(localized: "卸载失败: \(errorString)"))
}
} catch {
completion(false, String(localized: "执行卸载脚本失败: \(error.localizedDescription)"))
}
try? FileManager.default.removeItem(at: scriptURL)
} catch {
completion(false, String(localized: "准备卸载脚本失败: \(error.localizedDescription)"))
}
}
} }
extension PrivilegedHelperManager { extension PrivilegedHelperManager {
@@ -663,7 +729,7 @@ extension PrivilegedHelperManager {
}) as? HelperToolProtocol else { }) as? HelperToolProtocol else {
throw HelperError.proxyError throw HelperError.proxyError
} }
return helper return helper
} }
} }

View File

@@ -233,8 +233,6 @@ final class GeneralSettingsViewModel: ObservableObject {
} }
.store(in: &cancellables) .store(in: &cancellables)
PrivilegedHelperManager.shared.executeCommand("whoami") { _ in }
NotificationCenter.default.publisher(for: .storageDidChange) NotificationCenter.default.publisher(for: .storageDidChange)
.receive(on: RunLoop.main) .receive(on: RunLoop.main)
.sink { [weak self] _ in .sink { [weak self] _ in

View File

@@ -339,6 +339,7 @@
} }
}, },
"Helper 安装失败" : { "Helper 安装失败" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@@ -347,6 +348,12 @@
} }
} }
} }
},
"Helper 安装失败: %@" : {
},
"Helper 已完全卸载" : {
}, },
"Helper 是一个具有管理员权限的辅助工具,用于执行需要管理员权限的操作,如修改系统文件等。没有 Helper 将无法正常使用软件的某些功能。" : { "Helper 是一个具有管理员权限的辅助工具,用于执行需要管理员权限的操作,如修改系统文件等。没有 Helper 将无法正常使用软件的某些功能。" : {
"localizations" : { "localizations" : {
@@ -978,6 +985,9 @@
} }
} }
} }
},
"准备卸载脚本失败: %@" : {
}, },
"准备安装..." : { "准备安装..." : {
"localizations" : { "localizations" : {
@@ -1098,6 +1108,9 @@
} }
} }
} }
},
"卸载失败: %@" : {
}, },
"取消" : { "取消" : {
"localizations" : { "localizations" : {
@@ -1713,6 +1726,9 @@
} }
} }
} }
},
"执行卸载脚本失败: %@" : {
}, },
"执行成功" : { "执行成功" : {
"localizations" : { "localizations" : {