Skip to content

Commit 865db70

Browse files
committed
Ready for new beta
1 parent 9ab3e40 commit 865db70

File tree

8 files changed

+102
-80
lines changed

8 files changed

+102
-80
lines changed

Package.resolved

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ let package = Package(
1212
// Dependencies declare other packages that this package depends on.
1313
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.1.4"),
1414
.package(url: "https://github.com/soto-project/soto.git", from: "6.0.0"),
15-
.package(url: "https://github.com/jkmassel/prlctl.git", from: "1.20.0"),
15+
.package(url: "https://github.com/jkmassel/prlctl.git", from: "1.21.0"),
1616
.package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),
1717
.package(url: "https://github.com/jkmassel/kcpassword-swift.git", from: "1.0.0"),
1818
.package(url: "https://github.com/swiftpackages/DotEnv.git", from: "3.0.0"),

Sources/hostmgr/HostMgrCommand.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import libhostmgr
55
@main
66
struct Hostmgr: AsyncParsableCommand {
77

8-
private static var appVersion = "0.15.0-beta.4"
8+
private static var appVersion = "0.15.0-beta.5"
99

1010
static var configuration = CommandConfiguration(
1111
abstract: "A utility for managing VM hosts",

Sources/hostmgr/commands/vm/VMClean.swift

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,6 @@ struct VMCleanCommand: ParsableCommand {
1010
)
1111

1212
func run() throws {
13-
let repository = LocalVMRepository(imageDirectory: FileManager.default.temporaryDirectory)
14-
try repository.list().forEach { localVM in
15-
Console.info("Removing temp VM file for \(localVM.filename)")
16-
try repository.delete(image: localVM)
17-
}
18-
19-
try ParallelsVMRepository().lookupVMs().forEach { parallelsVM in
20-
Console.info("Removing Registered VM \(parallelsVM.name)")
21-
try parallelsVM.unregister()
22-
}
23-
24-
Console.success("Cleanup Complete")
13+
try libhostmgr.resetVMStorage()
2514
}
2615
}

Sources/hostmgr/commands/vm/VMFetch.swift

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,17 @@ struct VMFetchCommand: AsyncParsableCommand {
1616

1717
func run() async throws {
1818

19-
guard try LocalVMRepository().lookupVM(withName: name) == nil else {
20-
Console.exit(
21-
message: "VM is present locally",
22-
style: .success
23-
)
19+
if let localVM = try LocalVMRepository().lookupVM(withName: name) {
20+
if localVM.state == .packaged {
21+
try await libhostmgr.unpackVM(name: localVM.basename)
22+
return
23+
} else {
24+
Console.exit(
25+
message: "VM is present locally",
26+
style: .success
27+
)
28+
}
29+
2430
}
2531

2632
try await libhostmgr.fetchRemoteImage(name: self.name)

Sources/hostmgr/commands/vm/VMStart.swift

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,39 +16,6 @@ struct VMStartCommand: AsyncParsableCommand {
1616
var wait: Bool = false
1717

1818
func run() async throws {
19-
let importedVM = try await libhostmgr.importVM(name: name)
20-
21-
Console.info("Applying VM Settings")
22-
23-
// Always leave 4GB available to the VM host – the VM can have the rest
24-
let vmAvailableMemory = ProcessInfo().physicalMemory - (4096 * 1024 * 1024)
25-
let cpuCoreCount = ProcessInfo().physicalProcessorCount
26-
27-
Console.printTable(data: [
28-
["Total System Memory", Format.memoryBytes(ProcessInfo().physicalMemory)],
29-
["VM System Memory", Format.memoryBytes(vmAvailableMemory)],
30-
["VM CPU Cores", "\(cpuCoreCount)"],
31-
["Hypervisor Type", "apple"],
32-
["Networking Type", "bridged"]
33-
])
34-
35-
try [
36-
.memorySize(Int(vmAvailableMemory / 1024 / 1024)),
37-
.cpuCount(ProcessInfo().physicalProcessorCount),
38-
.hypervisorType(.apple),
39-
.networkType(.bridged),
40-
.isolateVM(.on),
41-
.sharedCamera(.off)
42-
].forEach { try importedVM.set($0) }
43-
44-
// These are optional, and it's possible they've already been removed, so they may fail
45-
do {
46-
try importedVM.set(.withoutSoundDevice())
47-
try importedVM.set(.withoutCDROMDevice())
48-
} catch {
49-
Console.warn("Unable to remove device: \(error.localizedDescription)")
50-
}
51-
52-
try await libhostmgr.startVM(importedVM)
19+
try await libhostmgr.startVM(name: self.name)
5320
}
5421
}

Sources/libhostmgr/Foundation+Extensions.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,14 @@ extension FileManager {
108108
public func createFile(at url: URL, contents: Data) throws {
109109
createFile(atPath: url.path, contents: contents)
110110
}
111+
112+
public func removeItemIfExists(at url: URL) throws {
113+
guard fileExists(at: url) else {
114+
return
115+
}
116+
117+
try removeItem(at: url)
118+
}
111119
}
112120

113121
extension URL {

Sources/libhostmgr/libhostmgr.swift

Lines changed: 77 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import prlctl
66
/// This method is the preferred way to install a remote image on a VM Host.
77
public 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

Comments
 (0)