utmctl: start UTM if needed

This commit is contained in:
osy
2022-12-11 15:48:47 -08:00
parent f368e6b605
commit f6f60fa77f
3 changed files with 50 additions and 3 deletions
+2
View File
@@ -93,6 +93,7 @@ extension UTMAPI {
/// Error codes
enum APIError: Error, LocalizedError {
case serverNotFound
case notConnected
case invalidCommand
case handlerNotFound
@@ -105,6 +106,7 @@ extension UTMAPI {
var localizedDescription: String {
switch self {
case .serverNotFound: return "Server not found."
case .notConnected: return "Not connected to API server."
case .invalidCommand: return "Invalid command."
case .handlerNotFound: return "Command handler not found."
+14 -2
View File
@@ -25,6 +25,11 @@ actor UTMAPIClient {
/// The client socket when connected
private var clientSocket: Socket?
/// True if the client socket is connected
var isConnected: Bool {
clientSocket != nil
}
init(connectPathUrl: URL) {
self.connectPathUrl = connectPathUrl
}
@@ -34,10 +39,17 @@ actor UTMAPIClient {
guard clientSocket == nil else {
return
}
guard FileManager.default.isReadableFile(atPath: connectPathUrl.path) else {
throw UTMAPI.APIError.serverNotFound
}
let address = UnixSocketAddress(path: FilePath(connectPathUrl.path))
let socket = try await Socket(UnixStreamProtocol.stream)
try socket.fileDescriptor.closeIfThrows {
try socket.fileDescriptor.connect(to: address)
do {
try socket.fileDescriptor.closeIfThrows {
try socket.fileDescriptor.connect(to: address)
}
} catch Errno.connectionRefused {
throw UTMAPI.APIError.serverNotFound
}
logger.debug("[API Client] connected to server")
clientSocket = socket
+34 -1
View File
@@ -15,6 +15,7 @@
//
import Foundation
import AppKit
import ArgumentParser
import Logging
@@ -47,11 +48,32 @@ extension UTMAPICommand {
logger.logLevel = environment.debug ? .debug : .info
let socketUrl = environment.socketPath?.asFileURL ?? defaultSocketUrl
let client = UTMAPIClient(connectPathUrl: socketUrl)
try await client.connect()
try await connectClient(client)
try await run(with: client)
try await client.disconnect()
}
/// Attempt to connect to the server and spawn UTM if it is not running
/// - Parameter client: API client
private func connectClient(_ client: UTMAPIClient) async throws {
do {
try await client.connect()
} catch UTMAPI.APIError.serverNotFound {
logger.info("Launching UTM...")
let config = NSWorkspace.OpenConfiguration()
config.arguments = ["cli-request"]
try await NSWorkspace.shared.openApplication(at: utmAppUrl, configuration: config)
repeat {
do {
try await client.connect()
} catch UTMAPI.APIError.serverNotFound {
// try again after a cooldown
try await Task.sleep(nanoseconds: 1 * NSEC_PER_SEC)
}
} while await !client.isConnected
}
}
/// Socket either in app group or app sandbox
private var defaultSocketUrl: URL {
let appGroup = Bundle.main.infoDictionary?["AppGroupIdentifier"] as? String
@@ -70,6 +92,17 @@ extension UTMAPICommand {
return parentURL.appendingPathComponent("api.sock")
}
/// Find the path to UTM.app
private var utmAppUrl: URL {
if let executableURL = Bundle.main.executableURL {
let utmURL = executableURL.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent()
if utmURL.lastPathComponent == "UTM.app" {
return utmURL
}
}
return URL(fileURLWithPath: "/Applications/UTM.app")
}
/// Default implementation of run command
/// - Parameter client: Connected client
func run(with client: UTMAPIClient) async throws {