473 lines
16 KiB
Swift
473 lines
16 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 {
|
|
if changed {
|
|
StopSSLocal()
|
|
DispatchQueue.main.asyncAfter(
|
|
deadline: DispatchTime.now() + DispatchTimeInterval.seconds(1),
|
|
execute: {
|
|
() in
|
|
StartSSLocal()
|
|
})
|
|
} else {
|
|
StartSSLocal()
|
|
}
|
|
} else {
|
|
StopSSLocal()
|
|
}
|
|
} 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 {
|
|
if changed {
|
|
StopPrivoxy()
|
|
DispatchQueue.main.asyncAfter(
|
|
deadline: DispatchTime.now() + DispatchTimeInterval.seconds(1),
|
|
execute: {
|
|
() in
|
|
StartPrivoxy()
|
|
})
|
|
} else {
|
|
StartPrivoxy()
|
|
}
|
|
} else {
|
|
StopPrivoxy()
|
|
}
|
|
} 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 {
|
|
if changed {
|
|
StopKcptun()
|
|
DispatchQueue.main.asyncAfter(
|
|
deadline: DispatchTime.now() + DispatchTimeInterval.seconds(1),
|
|
execute: {
|
|
() in
|
|
StartKcptun()
|
|
})
|
|
} else {
|
|
StartKcptun()
|
|
}
|
|
} else {
|
|
StopKcptun()
|
|
}
|
|
return
|
|
}
|
|
}
|
|
StopKcptun()
|
|
removeKcptunConfFile()
|
|
}
|