diff --git a/Scripting/UTM.sdef b/Scripting/UTM.sdef
index 64a2a0c6..eb9742dc 100644
--- a/Scripting/UTM.sdef
+++ b/Scripting/UTM.sdef
@@ -31,6 +31,11 @@
+
+
+
+
+
@@ -713,6 +718,12 @@
description="List of display configuration.">
+
+
+
+
diff --git a/Scripting/UTMScripting.swift b/Scripting/UTMScripting.swift
index 909d3084..a19fdab2 100644
--- a/Scripting/UTMScripting.swift
+++ b/Scripting/UTMScripting.swift
@@ -51,6 +51,12 @@ import ScriptingBridge
case detailed = 0x6c776474 /* 'lwdt' */
}
+// MARK: UTMScriptingOperatingSystem
+@objc public enum UTMScriptingOperatingSystem : AEKeyword {
+ case linux = 0x41704f6c /* 'ApOl' */
+ case macOS = 0x41704f6d /* 'ApOm' */
+}
+
// MARK: UTMScriptingBackend
@objc public enum UTMScriptingBackend : AEKeyword {
case apple = 0x4170506c /* 'ApPl' */
diff --git a/Scripting/UTMScriptingCreateCommand.swift b/Scripting/UTMScriptingCreateCommand.swift
index fb6fbdf8..53faf2e0 100644
--- a/Scripting/UTMScriptingCreateCommand.swift
+++ b/Scripting/UTMScriptingCreateCommand.swift
@@ -15,6 +15,7 @@
//
import Foundation
+import Virtualization
@MainActor
@objc(UTMScriptingCreateCommand)
@@ -103,12 +104,49 @@ class UTMScriptingCreateCommand: NSCreateCommand, UTMScriptable {
throw ScriptingError.nameNotSpecified
}
let config = UTMAppleConfiguration()
- config.system.boot = try UTMAppleConfigurationBoot(for: .linux)
+ // enumerations arrive as an AEKeyword and not a string
+ var bootOS = UTMAppleConfigurationBoot.OperatingSystem.linux
+ if let osKeyword = record["os"] as? AEKeyword {
+ guard let scriptingOS = UTMScriptingOperatingSystem(rawValue: osKeyword) else {
+ throw ScriptingError.operatingSystemNotSupported
+ }
+ switch scriptingOS {
+ case .linux: bootOS = .linux
+ case .macOS: bootOS = .macOS
+ }
+ }
+ config.system.boot = try UTMAppleConfigurationBoot(for: bootOS)
+ var macGuestMajorVersion = 0
+ if bootOS == .macOS {
+ guard let ipsw = record["macRecoveryIpsw"] as? URL else {
+ throw ScriptingError.macRecoveryIpswNotSpecified
+ }
+ config.system.boot.macRecoveryIpswURL = ipsw
+ #if arch(arm64)
+ // the hardware model must match the IPSW the caller chose
+ let image = try await VZMacOSRestoreImage.image(from: ipsw)
+ guard let model = image.mostFeaturefulSupportedConfiguration?.hardwareModel else {
+ throw ScriptingError.ipswNotSupported
+ }
+ config.system.macPlatform = UTMAppleConfigurationMacPlatform(newHardware: model)
+ macGuestMajorVersion = image.operatingSystemVersion.majorVersion
+ #else
+ throw ScriptingError.operatingSystemNotSupported
+ #endif
+ }
config.virtualization.hasBalloon = true
config.virtualization.hasEntropy = true
config.networks = [UTMAppleConfigurationNetwork()]
- // remove any display devices
- config.displays = []
+ if bootOS == .macOS {
+ // macOS is not usable headless so it gets the same devices as the wizard
+ config.displays = [UTMAppleConfigurationDisplay(width: 1920, height: 1200)]
+ config.virtualization.hasAudio = true
+ config.virtualization.keyboard = .generic
+ config.virtualization.pointer = macGuestMajorVersion >= 13 ? .trackpad : .mouse
+ } else {
+ // remove any display devices
+ config.displays = []
+ }
// add a default serial device
var serial = UTMAppleConfigurationSerial()
serial.mode = .ptty
@@ -131,6 +169,9 @@ class UTMScriptingCreateCommand: NSCreateCommand, UTMScriptable {
case configurationNotFound
case nameNotSpecified
case architectureNotSpecified
+ case operatingSystemNotSupported
+ case macRecoveryIpswNotSpecified
+ case ipswNotSupported
var errorDescription: String? {
switch self {
@@ -140,6 +181,9 @@ class UTMScriptingCreateCommand: NSCreateCommand, UTMScriptable {
case .configurationNotFound: return NSLocalizedString("A valid configuration must be specified.", comment: "UTMScriptingAppDelegate")
case .nameNotSpecified: return NSLocalizedString("No name specified in the configuration.", comment: "UTMScriptingAppDelegate")
case .architectureNotSpecified: return NSLocalizedString("No architecture specified in the configuration.", comment: "UTMScriptingAppDelegate")
+ case .operatingSystemNotSupported: return NSLocalizedString("The specified operating system is not supported on your machine.", comment: "UTMScriptingAppDelegate")
+ case .macRecoveryIpswNotSpecified: return NSLocalizedString("An IPSW recovery image must be specified to create a macOS virtual machine.", comment: "UTMScriptingAppDelegate")
+ case .ipswNotSupported: return NSLocalizedString("Your machine does not support running this IPSW.", comment: "UTMScriptingAppDelegate")
}
}
}