forked from I2P_Developers/i2p.i2p
OSX Launcher: Big rewrite of swift code where it now has the capability of creating services.
The router management has been much easier with this approach as it uses launchd to do the dirty work. This code also uses java_home as a wrapper instead of locating the java binary by itself. This also contribute to the improvements.
This commit is contained in:
69
launchers/macosx/I2PLauncher/Utils/LaunchAgent+Status.swift
Normal file
69
launchers/macosx/I2PLauncher/Utils/LaunchAgent+Status.swift
Normal file
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// LaunchAgent+Status.swift
|
||||
// I2PLauncher
|
||||
//
|
||||
// Created by Mikal Villa on 05/10/2018.
|
||||
// Copyright © 2018 The I2P Project. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum AgentStatus: Equatable {
|
||||
|
||||
case running(pid: Int)
|
||||
case loaded
|
||||
case unloaded
|
||||
|
||||
public static func ==(lhs: AgentStatus, rhs: AgentStatus) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case ( let .running(lhpid), let .running(rhpid) ):
|
||||
return lhpid == rhpid
|
||||
case (.loaded, .loaded):
|
||||
return true
|
||||
case (.unloaded, .unloaded):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension LaunchAgent {
|
||||
|
||||
/// Run `launchctl start` on the agent
|
||||
///
|
||||
/// Check the status of the job with `.status()`
|
||||
public func start() {
|
||||
LaunchAgentManager.shared.start(self)
|
||||
}
|
||||
|
||||
/// Run `launchctl stop` on the agent
|
||||
///
|
||||
/// Check the status of the job with `.status()`
|
||||
public func stop() {
|
||||
LaunchAgentManager.shared.stop(self)
|
||||
}
|
||||
|
||||
/// Run `launchctl load` on the agent
|
||||
///
|
||||
/// Check the status of the job with `.status()`
|
||||
public func load() throws {
|
||||
try LaunchAgentManager.shared.load(self)
|
||||
}
|
||||
|
||||
/// Run `launchctl unload` on the agent
|
||||
///
|
||||
/// Check the status of the job with `.status()`
|
||||
public func unload() throws {
|
||||
try LaunchAgentManager.shared.unload(self)
|
||||
}
|
||||
|
||||
/// Retreives the status of the LaunchAgent from `launchctl`
|
||||
///
|
||||
/// - Returns: the agent's status
|
||||
public func status() -> AgentStatus {
|
||||
return LaunchAgentManager.shared.status(self)
|
||||
}
|
||||
|
||||
}
|
||||
124
launchers/macosx/I2PLauncher/Utils/LaunchAgent.swift
Normal file
124
launchers/macosx/I2PLauncher/Utils/LaunchAgent.swift
Normal file
@@ -0,0 +1,124 @@
|
||||
//
|
||||
// LaunchAgent.swift
|
||||
// I2PLauncher
|
||||
//
|
||||
// Created by Mikal Villa on 05/10/2018.
|
||||
// Copyright © 2018 The I2P Project. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum ProcessType: String, Codable {
|
||||
case standard = "Standard"
|
||||
case background = "Background"
|
||||
case adaptive = "Adaptive"
|
||||
case interactive = "Interactive"
|
||||
}
|
||||
|
||||
public class LaunchAgent: Codable {
|
||||
|
||||
public var url: URL? = nil
|
||||
|
||||
// Basic Properties
|
||||
public var label: String
|
||||
public var disabled: Bool? = nil
|
||||
public var enableGlobbing: Bool? = nil
|
||||
public var program: String? = nil {
|
||||
didSet {
|
||||
if program != nil {
|
||||
programArguments = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var programArguments: [String]? = nil {
|
||||
didSet {
|
||||
guard let args = programArguments else {
|
||||
return
|
||||
}
|
||||
if args.count == 1 {
|
||||
self.program = args.first
|
||||
programArguments = nil
|
||||
} else {
|
||||
program = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var processType: ProcessType? = nil
|
||||
|
||||
// Program
|
||||
public var workingDirectory: String? = nil
|
||||
public var standardOutPath: String? = nil
|
||||
public var standardErrorPath: String? = nil
|
||||
public var environmentVariables: [String: String]? = nil
|
||||
|
||||
// Run Conditions
|
||||
public var runAtLoad: Bool? = nil
|
||||
public var startInterval: Int? = nil
|
||||
public var onDemand: Bool? = nil
|
||||
public var keepAlive: Bool? = nil
|
||||
public var watchPaths: [String]? = nil
|
||||
|
||||
// Security
|
||||
public var umask: Int? = nil
|
||||
// System Daemon Security
|
||||
public var groupName: String? = nil
|
||||
public var userName: String? = nil
|
||||
public var rootDirectory: String? = nil
|
||||
|
||||
|
||||
// Run Constriants
|
||||
public var launchOnlyOnce: Bool? = nil
|
||||
public var limitLoadToSessionType: [String]? = nil
|
||||
|
||||
public init(label: String, program: [String]) {
|
||||
self.label = label
|
||||
if program.count == 1 {
|
||||
self.program = program.first
|
||||
} else {
|
||||
self.programArguments = program
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public convenience init(label: String, program: String...) {
|
||||
self.init(label: label, program: program)
|
||||
}
|
||||
|
||||
public enum CodingKeys: String, CodingKey {
|
||||
case label = "Label"
|
||||
case disabled = "Disabled"
|
||||
case program = "Program"
|
||||
case programArguments = "ProgramArguments"
|
||||
|
||||
// Program
|
||||
case workingDirectory = "WorkingDirectory"
|
||||
case standardOutPath = "StandardOutPath"
|
||||
case standardErrorPath = "StandardErrorPath"
|
||||
case environmentVariables = "EnvironmentVariables"
|
||||
|
||||
// Run Conditions
|
||||
case runAtLoad = "RunAtLoad"
|
||||
case startInterval = "StartInterval"
|
||||
case onDemand = "OnDemand"
|
||||
case keepAlive = "KeepAlive"
|
||||
case watchPaths = "WatchPaths"
|
||||
|
||||
// Security
|
||||
case umask = "Umask"
|
||||
case groupName = "GroupName"
|
||||
case userName = "UserName"
|
||||
case rootDirectory = "RootDirectory"
|
||||
|
||||
// Run Constriants
|
||||
case launchOnlyOnce = "LaunchOnlyOnce"
|
||||
case limitLoadToSessionType = "LimitLoadToSessionType"
|
||||
|
||||
// Process type
|
||||
case processType = "ProcessType"
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
191
launchers/macosx/I2PLauncher/Utils/LaunchAgentManager.swift
Normal file
191
launchers/macosx/I2PLauncher/Utils/LaunchAgentManager.swift
Normal file
@@ -0,0 +1,191 @@
|
||||
//
|
||||
// LaunchAgentManager.swift
|
||||
// I2PLauncher
|
||||
//
|
||||
// Created by Mikal Villa on 07/10/2018.
|
||||
// Copyright © 2018 The I2P Project. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
public enum LaunchAgentManagerError: Swift.Error {
|
||||
case urlNotSet(label: String)
|
||||
|
||||
public var localizedDescription: String {
|
||||
switch self {
|
||||
case .urlNotSet(let label):
|
||||
return "The URL is not set for agent \(label)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class LaunchAgentManager {
|
||||
public static let shared = LaunchAgentManager()
|
||||
|
||||
static let launchctl = "/bin/launchctl"
|
||||
|
||||
var lastState: AgentStatus?
|
||||
|
||||
let encoder = PropertyListEncoder()
|
||||
let decoder = PropertyListDecoder()
|
||||
|
||||
init() {
|
||||
encoder.outputFormat = .xml
|
||||
}
|
||||
|
||||
func launchAgentsURL() throws -> URL {
|
||||
let library = try FileManager.default.url(for: .libraryDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
|
||||
|
||||
return library.appendingPathComponent("LaunchAgents")
|
||||
}
|
||||
|
||||
public func read(agent called: String) throws -> LaunchAgent {
|
||||
let url = try launchAgentsURL().appendingPathComponent(called)
|
||||
|
||||
return try read(from: url)
|
||||
}
|
||||
|
||||
public func read(from url: URL) throws -> LaunchAgent {
|
||||
return try decoder.decode(LaunchAgent.self, from: Data(contentsOf: url))
|
||||
}
|
||||
|
||||
public func write(_ agent: LaunchAgent, called: String) throws {
|
||||
let url = try launchAgentsURL().appendingPathComponent(called)
|
||||
|
||||
try write(agent, to: url)
|
||||
}
|
||||
|
||||
public func write(_ agent: LaunchAgent, to url: URL) throws {
|
||||
try encoder.encode(agent).write(to: url)
|
||||
|
||||
agent.url = url
|
||||
}
|
||||
|
||||
public func setURL(for agent: LaunchAgent) throws {
|
||||
let contents = try FileManager.default.contentsOfDirectory(
|
||||
at: try launchAgentsURL(),
|
||||
includingPropertiesForKeys: nil,
|
||||
options: [.skipsPackageDescendants, .skipsHiddenFiles, .skipsSubdirectoryDescendants]
|
||||
)
|
||||
|
||||
contents.forEach { url in
|
||||
let testAgent = try? self.read(from: url)
|
||||
|
||||
if agent.label == testAgent?.label {
|
||||
agent.url = url
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension LaunchAgentManager {
|
||||
|
||||
/// Run `launchctl start` on the agent
|
||||
///
|
||||
/// Check the status of the job with `.status(_: LaunchAgent)`
|
||||
public func start(_ agent: LaunchAgent) {
|
||||
let arguments = ["start", agent.label]
|
||||
Process.launchedProcess(launchPath: LaunchAgentManager.launchctl, arguments: arguments)
|
||||
}
|
||||
|
||||
/// Run `launchctl stop` on the agent
|
||||
///
|
||||
/// Check the status of the job with `.status(_: LaunchAgent)`
|
||||
public func stop(_ agent: LaunchAgent) {
|
||||
let arguments = ["stop", agent.label]
|
||||
Process.launchedProcess(launchPath: LaunchAgentManager.launchctl, arguments: arguments)
|
||||
}
|
||||
|
||||
/// Run `launchctl load` on the agent
|
||||
///
|
||||
/// Check the status of the job with `.status(_: LaunchAgent)`
|
||||
public func load(_ agent: LaunchAgent) throws {
|
||||
guard let agentURL = agent.url else {
|
||||
throw LaunchAgentManagerError.urlNotSet(label: agent.label)
|
||||
}
|
||||
|
||||
let arguments = ["load", agentURL.path]
|
||||
Process.launchedProcess(launchPath: LaunchAgentManager.launchctl, arguments: arguments)
|
||||
}
|
||||
|
||||
/// Run `launchctl unload` on the agent
|
||||
///
|
||||
/// Check the status of the job with `.status(_: LaunchAgent)`
|
||||
public func unload(_ agent: LaunchAgent) throws {
|
||||
guard let agentURL = agent.url else {
|
||||
throw LaunchAgentManagerError.urlNotSet(label: agent.label)
|
||||
}
|
||||
|
||||
let arguments = ["unload", agentURL.path]
|
||||
Process.launchedProcess(launchPath: LaunchAgentManager.launchctl, arguments: arguments)
|
||||
}
|
||||
|
||||
/// Retreives the status of the LaunchAgent from `launchctl`
|
||||
///
|
||||
/// - Returns: the agent's status
|
||||
public func status(_ agent: LaunchAgent) -> AgentStatus {
|
||||
|
||||
let launchctlTask = Process()
|
||||
let grepTask = Process()
|
||||
let cutTask = Process()
|
||||
|
||||
launchctlTask.launchPath = "/bin/launchctl"
|
||||
launchctlTask.arguments = ["list"]
|
||||
|
||||
grepTask.launchPath = "/usr/bin/grep"
|
||||
grepTask.arguments = [agent.label]
|
||||
|
||||
cutTask.launchPath = "/usr/bin/cut"
|
||||
cutTask.arguments = ["-f1"]
|
||||
|
||||
let pipeLaunchCtlToGrep = Pipe()
|
||||
launchctlTask.standardOutput = pipeLaunchCtlToGrep
|
||||
grepTask.standardInput = pipeLaunchCtlToGrep
|
||||
|
||||
let pipeGrepToCut = Pipe()
|
||||
grepTask.standardOutput = pipeGrepToCut
|
||||
cutTask.standardInput = pipeGrepToCut
|
||||
|
||||
let pipeCutToFile = Pipe()
|
||||
cutTask.standardOutput = pipeCutToFile
|
||||
|
||||
let fileHandle: FileHandle = pipeCutToFile.fileHandleForReading as FileHandle
|
||||
|
||||
launchctlTask.launch()
|
||||
grepTask.launch()
|
||||
cutTask.launch()
|
||||
|
||||
|
||||
let data = fileHandle.readDataToEndOfFile()
|
||||
let stringResult = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .newlines) ?? ""
|
||||
|
||||
let em = RouterManager.shared().eventManager
|
||||
|
||||
switch stringResult {
|
||||
case "-":
|
||||
if (self.lastState != AgentStatus.loaded) {
|
||||
self.lastState = AgentStatus.loaded
|
||||
em.trigger(eventName: "launch_agent_loaded")
|
||||
}
|
||||
|
||||
return .loaded
|
||||
case "":
|
||||
if (self.lastState != AgentStatus.unloaded) {
|
||||
self.lastState = AgentStatus.unloaded
|
||||
em.trigger(eventName: "launch_agent_unloaded")
|
||||
}
|
||||
return .unloaded
|
||||
default:
|
||||
if (self.lastState != AgentStatus.running(pid: Int(stringResult)!)) {
|
||||
self.lastState = AgentStatus.running(pid: Int(stringResult)!)
|
||||
em.trigger(eventName: "launch_agent_running")
|
||||
}
|
||||
return .running(pid: Int(stringResult)!)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user