Files
ShadowsocksX-NG/ShadowsocksX-NG/LaunchAgentUtils.swift
2017-01-20 00:02:02 +08:00

441 lines
15 KiB
Swift

//
// BGUtils.swift
// ShadowsocksX-NG
//
// Created by on 16/6/6.
// Copyright © 2016 qiuyuzhou. All rights reserved.
//
import Foundation
let SS_LOCAL_VERSION = "2.5.6"
let KCPTUN_CLIENT_VERSION = "20170117"
let PRIVOXY_VERSION = "3.0.26.static"
let APP_SUPPORT_DIR = "/Library/Application Support/ShadowsocksX-NG/"
let LAUNCH_AGENT_DIR = "/Library/LaunchAgents/"
let LAUNCH_AGENT_CONF_SSLOCAL_NAME = "com.qiuyuzhou.shadowsocksX-NG.local.plist"
let LAUNCH_AGENT_CONF_PRIVOXY_NAME = "com.qiuyuzhou.shadowsocksX-NG.http.plist"
let LAUNCH_AGENT_CONF_KCPTUN_NAME = "com.qiuyuzhou.shadowsocksX-NG.kcptun.plist"
func getFileSHA1Sum(_ filepath: String) -> String {
if let data = try? Data(contentsOf: URL(fileURLWithPath: filepath)) {
return data.sha1()
}
return ""
}
// Ref: https://developer.apple.com/library/mac/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html
// Genarate the mac launch agent service plist
// MARK: sslocal
func generateSSLocalLauchAgentPlist() -> Bool {
let sslocalPath = NSHomeDirectory() + APP_SUPPORT_DIR + "ss-local"
let logFilePath = NSHomeDirectory() + "/Library/Logs/ss-local.log"
let launchAgentDirPath = NSHomeDirectory() + LAUNCH_AGENT_DIR
let plistFilepath = launchAgentDirPath + LAUNCH_AGENT_CONF_SSLOCAL_NAME
// Ensure launch agent directory is existed.
let fileMgr = FileManager.default
if !fileMgr.fileExists(atPath: launchAgentDirPath) {
try! fileMgr.createDirectory(atPath: launchAgentDirPath, withIntermediateDirectories: true, attributes: nil)
}
let oldSha1Sum = getFileSHA1Sum(plistFilepath)
let defaults = UserDefaults.standard
let enableUdpRelay = defaults.bool(forKey: "LocalSocks5.EnableUDPRelay")
let enableVerboseMode = defaults.bool(forKey: "LocalSocks5.EnableVerboseMode")
var arguments = [sslocalPath, "-c", "ss-local-config.json"]
if enableUdpRelay {
arguments.append("-u")
}
if enableVerboseMode {
arguments.append("-v")
}
// For a complete listing of the keys, see the launchd.plist manual page.
let dict: NSMutableDictionary = [
"Label": "com.qiuyuzhou.shadowsocksX-NG.local",
"WorkingDirectory": NSHomeDirectory() + APP_SUPPORT_DIR,
"KeepAlive": true,
"StandardOutPath": logFilePath,
"StandardErrorPath": logFilePath,
"ProgramArguments": arguments,
"EnvironmentVariables": ["DYLD_LIBRARY_PATH": NSHomeDirectory() + APP_SUPPORT_DIR]
]
dict.write(toFile: plistFilepath, atomically: true)
let Sha1Sum = getFileSHA1Sum(plistFilepath)
if oldSha1Sum != Sha1Sum {
return true
} else {
return false
}
}
func StartSSLocal() {
let bundle = Bundle.main
let installerPath = bundle.path(forResource: "start_ss_local.sh", ofType: nil)
let task = Process.launchedProcess(launchPath: installerPath!, arguments: [""])
task.waitUntilExit()
if task.terminationStatus == 0 {
NSLog("Start ss-local succeeded.")
} else {
NSLog("Start ss-local failed.")
}
}
func StopSSLocal() {
let bundle = Bundle.main
let installerPath = bundle.path(forResource: "stop_ss_local.sh", ofType: nil)
let task = Process.launchedProcess(launchPath: installerPath!, arguments: [""])
task.waitUntilExit()
if task.terminationStatus == 0 {
NSLog("Stop ss-local succeeded.")
} else {
NSLog("Stop ss-local failed.")
}
}
func InstallSSLocal() {
let fileMgr = FileManager.default
let homeDir = NSHomeDirectory()
let appSupportDir = homeDir+APP_SUPPORT_DIR
if !fileMgr.fileExists(atPath: appSupportDir + "ss-local-\(SS_LOCAL_VERSION)/ss-local")
|| !fileMgr.fileExists(atPath: appSupportDir + "libcrypto.1.0.0.dylib")
|| !fileMgr.fileExists(atPath: appSupportDir + "libpcre.1.dylib") {
let bundle = Bundle.main
let installerPath = bundle.path(forResource: "install_ss_local.sh", ofType: nil)
let task = Process.launchedProcess(launchPath: installerPath!, arguments: [""])
task.waitUntilExit()
if task.terminationStatus == 0 {
NSLog("Install ss-local succeeded.")
} else {
NSLog("Install ss-local failed.")
}
}
}
func writeSSLocalConfFile(_ conf:[String:AnyObject]) -> Bool {
do {
let filepath = NSHomeDirectory() + APP_SUPPORT_DIR + "ss-local-config.json"
let data: Data = try JSONSerialization.data(withJSONObject: conf, options: .prettyPrinted)
let oldSum = getFileSHA1Sum(filepath)
try data.write(to: URL(fileURLWithPath: filepath), options: .atomic)
let newSum = getFileSHA1Sum(filepath)
if oldSum == newSum {
return false
}
return true
} catch {
NSLog("Write ss-local file failed.")
}
return false
}
func removeSSLocalConfFile() {
do {
let filepath = NSHomeDirectory() + APP_SUPPORT_DIR + "ss-local-config.json"
try FileManager.default.removeItem(atPath: filepath)
} catch {
}
}
func SyncSSLocal() {
var changed: Bool = false
changed = changed || generateSSLocalLauchAgentPlist()
let mgr = ServerProfileManager.instance
if mgr.activeProfileId != nil {
changed = changed || writeSSLocalConfFile((mgr.getActiveProfile()?.toJsonConfig())!)
let on = UserDefaults.standard.bool(forKey: "ShadowsocksOn")
if on {
StopSSLocal()
StartSSLocal()
}
} else {
removeSSLocalConfFile()
StopSSLocal()
}
SyncPac()
SyncPrivoxy()
SyncKcptun()
}
// --------------------------------------------------------------------------------
// MARK: privoxy
func generatePrivoxyLauchAgentPlist() -> Bool {
let privoxyPath = NSHomeDirectory() + APP_SUPPORT_DIR + "privoxy"
let logFilePath = NSHomeDirectory() + "/Library/Logs/privoxy.log"
let launchAgentDirPath = NSHomeDirectory() + LAUNCH_AGENT_DIR
let plistFilepath = launchAgentDirPath + LAUNCH_AGENT_CONF_PRIVOXY_NAME
// Ensure launch agent directory is existed.
let fileMgr = FileManager.default
if !fileMgr.fileExists(atPath: launchAgentDirPath) {
try! fileMgr.createDirectory(atPath: launchAgentDirPath, withIntermediateDirectories: true, attributes: nil)
}
let oldSha1Sum = getFileSHA1Sum(plistFilepath)
let arguments = [privoxyPath, "--no-daemon", "privoxy.config"]
// For a complete listing of the keys, see the launchd.plist manual page.
let dict: NSMutableDictionary = [
"Label": "com.qiuyuzhou.shadowsocksX-NG.http",
"WorkingDirectory": NSHomeDirectory() + APP_SUPPORT_DIR,
"KeepAlive": true,
"StandardOutPath": logFilePath,
"StandardErrorPath": logFilePath,
"ProgramArguments": arguments
]
dict.write(toFile: plistFilepath, atomically: true)
let Sha1Sum = getFileSHA1Sum(plistFilepath)
if oldSha1Sum != Sha1Sum {
return true
} else {
return false
}
}
func StartPrivoxy() {
let bundle = Bundle.main
let installerPath = bundle.path(forResource: "start_privoxy.sh", ofType: nil)
let task = Process.launchedProcess(launchPath: installerPath!, arguments: [""])
task.waitUntilExit()
if task.terminationStatus == 0 {
NSLog("Start privoxy succeeded.")
} else {
NSLog("Start privoxy failed.")
}
}
func StopPrivoxy() {
let bundle = Bundle.main
let installerPath = bundle.path(forResource: "stop_privoxy.sh", ofType: nil)
let task = Process.launchedProcess(launchPath: installerPath!, arguments: [""])
task.waitUntilExit()
if task.terminationStatus == 0 {
NSLog("Stop privoxy succeeded.")
} else {
NSLog("Stop privoxy failed.")
}
}
func InstallPrivoxy() {
let fileMgr = FileManager.default
let homeDir = NSHomeDirectory()
let appSupportDir = homeDir+APP_SUPPORT_DIR
if !fileMgr.fileExists(atPath: appSupportDir + "privoxy-\(PRIVOXY_VERSION)/privoxy") {
let bundle = Bundle.main
let installerPath = bundle.path(forResource: "install_privoxy.sh", ofType: nil)
let task = Process.launchedProcess(launchPath: installerPath!, arguments: [""])
task.waitUntilExit()
if task.terminationStatus == 0 {
NSLog("Install privoxy succeeded.")
} else {
NSLog("Install privoxy failed.")
}
}
}
func writePrivoxyConfFile() -> Bool {
do {
let defaults = UserDefaults.standard
let bundle = Bundle.main
let examplePath = bundle.path(forResource: "privoxy.config.example", ofType: nil)
var example = try String(contentsOfFile: examplePath!, encoding: .utf8)
example = example.replacingOccurrences(of: "{http}", with: defaults.string(forKey: "LocalHTTP.ListenAddress")! + ":" + String(defaults.integer(forKey: "LocalHTTP.ListenPort")))
example = example.replacingOccurrences(of: "{socks5}", with: defaults.string(forKey: "LocalSocks5.ListenAddress")! + ":" + String(defaults.integer(forKey: "LocalSocks5.ListenPort")))
let data = example.data(using: .utf8)
let filepath = NSHomeDirectory() + APP_SUPPORT_DIR + "privoxy.config"
let oldSum = getFileSHA1Sum(filepath)
try data?.write(to: URL(fileURLWithPath: filepath), options: .atomic)
let newSum = getFileSHA1Sum(filepath)
if oldSum == newSum {
return false
}
return true
} catch {
NSLog("Write privoxy file failed.")
}
return false
}
func removePrivoxyConfFile() {
do {
let filepath = NSHomeDirectory() + APP_SUPPORT_DIR + "privoxy.config"
try FileManager.default.removeItem(atPath: filepath)
} catch {
}
}
func SyncPrivoxy() {
var changed: Bool = false
changed = changed || generatePrivoxyLauchAgentPlist()
let mgr = ServerProfileManager.instance
if mgr.activeProfileId != nil {
changed = changed || writePrivoxyConfFile()
let on = UserDefaults.standard.bool(forKey: "LocalHTTPOn")
if on {
StopPrivoxy()
StartPrivoxy()
}
else {
removePrivoxyConfFile()
StopPrivoxy()
}
}
}
// --------------------------------------------------------------------------------
// kcptun
func generateKcptunLauchAgentPlist() -> Bool {
let sslocalPath = NSHomeDirectory() + APP_SUPPORT_DIR + "kcptun_client"
let logFilePath = NSHomeDirectory() + "/Library/Logs/kcptun_client.log"
let launchAgentDirPath = NSHomeDirectory() + LAUNCH_AGENT_DIR
let plistFilepath = launchAgentDirPath + LAUNCH_AGENT_CONF_KCPTUN_NAME
// Ensure launch agent directory is existed.
let fileMgr = FileManager.default
if !fileMgr.fileExists(atPath: launchAgentDirPath) {
try! fileMgr.createDirectory(atPath: launchAgentDirPath, withIntermediateDirectories: true, attributes: nil)
}
let oldSha1Sum = getFileSHA1Sum(plistFilepath)
let arguments = [sslocalPath, "-c", "kcptun-config.json"]
// For a complete listing of the keys, see the launchd.plist manual page.
let dict: NSMutableDictionary = [
"Label": "com.qiuyuzhou.shadowsocksX-NG.kcptun",
"WorkingDirectory": NSHomeDirectory() + APP_SUPPORT_DIR,
"KeepAlive": true,
"StandardOutPath": logFilePath,
"StandardErrorPath": logFilePath,
"ProgramArguments": arguments,
"EnvironmentVariables": ["DYLD_LIBRARY_PATH": NSHomeDirectory() + APP_SUPPORT_DIR]
]
dict.write(toFile: plistFilepath, atomically: true)
let Sha1Sum = getFileSHA1Sum(plistFilepath)
if oldSha1Sum != Sha1Sum {
return true
} else {
return false
}
}
func InstallKcptunClient() {
let fileMgr = FileManager.default
let homeDir = NSHomeDirectory()
let appSupportDir = homeDir+APP_SUPPORT_DIR
if !fileMgr.fileExists(atPath: appSupportDir + "kcptun_\(KCPTUN_CLIENT_VERSION)/kcptun_client") {
let bundle = Bundle.main
let installerPath = bundle.path(forResource: "install_kcptun", ofType: "sh")
let task = Process.launchedProcess(launchPath: installerPath!, arguments: [""])
task.waitUntilExit()
if task.terminationStatus == 0 {
NSLog("Install kcptun succeeded.")
} else {
NSLog("Install kcptun failed.")
}
}
}
func writeKcptunConfFile(_ conf:[String:AnyObject]) -> Bool {
do {
let filepath = NSHomeDirectory() + APP_SUPPORT_DIR + "kcptun-config.json"
let data: Data = try JSONSerialization.data(withJSONObject: conf, options: .prettyPrinted)
let oldSum = getFileSHA1Sum(filepath)
try data.write(to: URL(fileURLWithPath: filepath), options: .atomic)
let newSum = getFileSHA1Sum(filepath)
if oldSum == newSum {
return false
}
return true
} catch {
NSLog("Write kcptun config file failed.")
}
return false
}
func isEnabledKcptun() -> Bool {
let mgr = ServerProfileManager.instance
if let profile = mgr.getActiveProfile() {
return profile.enabledKcptun
}
return false
}
func removeKcptunConfFile() {
do {
let filepath = NSHomeDirectory() + APP_SUPPORT_DIR + "kcptun-config.json"
try FileManager.default.removeItem(atPath: filepath)
} catch {
}
}
func StartKcptun() {
if isEnabledKcptun() {
let bundle = Bundle.main
let installerPath = bundle.path(forResource: "start_kcptun.sh", ofType: nil)
let task = Process.launchedProcess(launchPath: installerPath!, arguments: [""])
task.waitUntilExit()
if task.terminationStatus == 0 {
NSLog("Start kcptun succeeded.")
} else {
NSLog("Start kcptun failed.")
}
}
}
func StopKcptun() {
let bundle = Bundle.main
let installerPath = bundle.path(forResource: "stop_kcptun.sh", ofType: nil)
let task = Process.launchedProcess(launchPath: installerPath!, arguments: [""])
task.waitUntilExit()
if task.terminationStatus == 0 {
NSLog("Stop kcptun succeeded.")
} else {
NSLog("Stop kcptun failed.")
}
}
func SyncKcptun() {
var changed: Bool = false
changed = changed || generateKcptunLauchAgentPlist()
let mgr = ServerProfileManager.instance
if let profile = mgr.getActiveProfile() {
if profile.enabledKcptun {
changed = changed || writeKcptunConfFile(profile.toKcptunJsonConfig())
let on = UserDefaults.standard.bool(forKey: "ShadowsocksOn")
if on {
StopKcptun()
StartKcptun()
return
}
}
}
StopKcptun()
removeKcptunConfFile()
}