@@ -6,7 +6,6 @@ import prlctl
66/// This method is the preferred way to install a remote image on a VM Host.
77public func fetchRemoteImage( name: String ) async throws {
88 try await downloadRemoteImage ( name: name)
9- try await importVM ( name: name)
109
1110 Console . success ( " VM \( name) is ready " )
1211}
@@ -84,37 +83,36 @@ public func unpackVM(name: String) async throws {
8483 let unpackedVM = try package . unpack ( )
8584 Console . success ( " Unpacked \( package . name) " )
8685
87- Console . info ( " Cleaning up " )
86+ // If we simply rename the `.pvmp` file, the underlying `pvm` file may retain its original name. We should
87+ // update the file on disk to reference this name
88+ if name != unpackedVM. name {
89+ Console . info ( " Fixing Parallels VM Label " )
90+ try unpackedVM. rename ( to: name)
91+ Console . success ( " Parallels VM Label Fixed " )
92+ }
93+
94+ Console . success ( " Finished Unpacking VM " )
95+
96+ Console . info ( " Cleaning Up " )
8897 try unpackedVM. unregister ( )
89- Console . info ( " Done " )
98+ Console . success ( " Done " )
9099}
91100
92- /// Prepares a local VM for use by the VM host. Automatically unpacks it first, if needed .
101+ /// Resets local VM storage by removing all registered Parallels VMs and temporary VM clones .
93102///
94- @ discardableResult
95- public func importVM ( name : String ) async throws -> StoppedVM {
96-
97- guard let sourceVM = try LocalVMRepository ( ) . lookupVM ( withName : name ) else {
98- Console . crash ( message : " VM \( name ) could not be found " , reason : . fileNotFound )
103+ public func resetVMStorage ( ) throws {
104+ let repository = LocalVMRepository ( imageDirectory : FileManager . default . temporaryDirectory )
105+ try repository . list ( ) . forEach { localVM in
106+ Console . info ( " Removing temp VM file for \( localVM . filename ) " )
107+ try repository . delete ( image : localVM )
99108 }
100109
101- if sourceVM . state == . packaged {
102- try await unpackVM ( name: name )
103- return try await importVM ( name : name )
110+ try ParallelsVMRepository ( ) . lookupVMs ( ) . forEach { parallelsVM in
111+ Console . info ( " Removing Registered VM \( parallelsVM . name) " )
112+ try parallelsVM . unregister ( )
104113 }
105114
106- let destination = FileManager . default. temporaryFilePath ( named: name + " .tmp.pvm " )
107-
108- try FileManager . default. copyItem ( at: sourceVM. path, to: destination)
109- Console . info ( " Created temporary VM at \( destination) " )
110-
111- guard let importedVirtualMachine = try Parallels ( ) . importVM ( at: destination) ? . asStoppedVM ( ) else {
112- Console . crash ( message: " Unable to import VM: \( destination) " , reason: . unableToImportVM)
113- }
114-
115- Console . success ( " Successfully Imported \( importedVirtualMachine. name) with UUID \( importedVirtualMachine. uuid) " )
116-
117- return importedVirtualMachine
115+ Console . success ( " Cleanup Complete " )
118116}
119117
120118/// Deletes local VM image files from the disk
@@ -185,9 +183,63 @@ public func lookupParallelsVMOrExit(
185183 return parallelsVirtualMachine
186184}
187185
188- public func startVM( _ parallelsVM : StoppedVM ) async throws {
186+ public func startVM( name : String ) async throws {
189187 let startDate = Date ( )
190188
189+ guard let sourceVM = try LocalVMRepository ( ) . lookupVM ( withName: name) else {
190+ Console . crash ( message: " VM \( name) could not be found " , reason: . fileNotFound)
191+ }
192+
193+ try resetVMStorage ( )
194+
195+ if sourceVM. state == . packaged {
196+ try await unpackVM ( name: name)
197+ return try await startVM ( name: name)
198+ }
199+
200+ let destination = FileManager . default. temporaryFilePath ( named: name + " .tmp.pvm " )
201+
202+ try FileManager . default. removeItemIfExists ( at: destination)
203+ try FileManager . default. copyItem ( at: sourceVM. path, to: destination)
204+ Console . info ( " Created temporary VM at \( destination) " )
205+
206+ guard let parallelsVM = try Parallels ( ) . importVM ( at: destination) ? . asStoppedVM ( ) else {
207+ Console . crash ( message: " Unable to import VM: \( destination) " , reason: . unableToImportVM)
208+ }
209+
210+ Console . success ( " Successfully Imported \( parallelsVM. name) with UUID \( parallelsVM. uuid) " )
211+
212+ Console . info ( " Applying VM Settings " )
213+
214+ // Always leave 4GB available to the VM host – the VM can have the rest
215+ let vmAvailableMemory = ProcessInfo ( ) . physicalMemory - ( 4096 * 1024 * 1024 )
216+ let cpuCoreCount = ProcessInfo ( ) . physicalProcessorCount
217+
218+ Console . printTable ( data: [
219+ [ " Total System Memory " , Format . memoryBytes ( ProcessInfo ( ) . physicalMemory) ] ,
220+ [ " VM System Memory " , Format . memoryBytes ( vmAvailableMemory) ] ,
221+ [ " VM CPU Cores " , " \( cpuCoreCount) " ] ,
222+ [ " Hypervisor Type " , " apple " ] ,
223+ [ " Networking Type " , " bridged " ]
224+ ] )
225+
226+ try [
227+ . memorySize( Int ( vmAvailableMemory / 1024 / 1024 ) ) ,
228+ . cpuCount( ProcessInfo ( ) . physicalProcessorCount) ,
229+ . hypervisorType( . apple) ,
230+ . networkType( . bridged) ,
231+ . isolateVM( . on) ,
232+ . sharedCamera( . off)
233+ ] . forEach { try parallelsVM. set ( $0) }
234+
235+ // These are optional, and it's possible they've already been removed, so they may fail
236+ do {
237+ try parallelsVM. set ( . withoutSoundDevice( ) )
238+ try parallelsVM. set ( . withoutCDROMDevice( ) )
239+ } catch {
240+ Console . warn ( " Unable to remove device: \( error. localizedDescription) " )
241+ }
242+
191243 try parallelsVM. start ( )
192244
193245 let _: Void = try await withCheckedThrowingContinuation { continuation in
0 commit comments