diff options
Diffstat (limited to 'tstest/build-macos-base-vm/install.swift')
| -rw-r--r-- | tstest/build-macos-base-vm/install.swift | 203 |
1 files changed, 88 insertions, 115 deletions
diff --git a/tstest/build-macos-base-vm/install.swift b/tstest/build-macos-base-vm/install.swift index 181e1a14e..a8ceae082 100644 --- a/tstest/build-macos-base-vm/install.swift +++ b/tstest/build-macos-base-vm/install.swift @@ -2,73 +2,54 @@ // SPDX-License-Identifier: BSD-3-Clause // This is a helper program invoked by the Go build-macos-base-vm command. -// It uses Apple's Virtualization.framework to download a macOS IPSW -// restore image and install macOS into a VM disk image. +// It uses Apple's Virtualization.framework for two operations: // -// Usage: installer <vm-dir> <ipsw-path> +// fetch-ipsw-url — prints the URL of the latest supported IPSW +// install <vm-dir> <ipsw> — installs macOS from a local IPSW into a VM import Foundation import Virtualization -guard CommandLine.arguments.count == 3 else { - fputs("usage: installer <vm-dir> <ipsw-path>\n", stderr) +guard CommandLine.arguments.count >= 2 else { + fputs("usage: installer {fetch-ipsw-url | install <vm-dir> <ipsw-path>}\n", stderr) exit(1) } -let vmDir = CommandLine.arguments[1] -let ipswPath = CommandLine.arguments[2] +let mode = CommandLine.arguments[1] -let diskURL = URL(fileURLWithPath: vmDir).appendingPathComponent("Disk.img") -let auxURL = URL(fileURLWithPath: vmDir).appendingPathComponent("AuxiliaryStorage") -let hwModelURL = URL(fileURLWithPath: vmDir).appendingPathComponent("HardwareModel") -let machineIdURL = URL(fileURLWithPath: vmDir).appendingPathComponent("MachineIdentifier") - -let diskSize: Int64 = 72 * 1024 * 1024 * 1024 // 72 GB sparse -let memorySize: UInt64 = 8 * 1024 * 1024 * 1024 // 8 GB - -// Step 1: Download IPSW if needed. -func downloadIPSW(to path: String, completion: @escaping (URL) -> Void) { - let url = URL(fileURLWithPath: path) - if FileManager.default.fileExists(atPath: path) { - print("Using existing IPSW at \(path)") - completion(url) - return - } - print("Downloading latest macOS restore image...") +switch mode { +case "fetch-ipsw-url": VZMacOSRestoreImage.fetchLatestSupported { result in switch result { case .failure(let error): fputs("Failed to fetch restore image info: \(error)\n", stderr) exit(1) case .success(let image): - print("Downloading from \(image.url)...") - let task = URLSession.shared.downloadTask(with: image.url) { localURL, _, error in - if let error = error { - fputs("Download failed: \(error)\n", stderr) - exit(1) - } - do { - try FileManager.default.moveItem(at: localURL!, to: url) - } catch { - fputs("Failed to move IPSW: \(error)\n", stderr) - exit(1) - } - print("Downloaded IPSW to \(path)") - completion(url) - } - task.progress.observe(\.fractionCompleted, options: [.new]) { _, change in - let pct = Int((change.newValue ?? 0) * 100) - print(" download: \(pct)%") - } - task.resume() + // Print URL to stdout for the Go caller to parse. + print(image.url.absoluteString) + exit(0) } } -} + RunLoop.main.run() + +case "install": + guard CommandLine.arguments.count == 4 else { + fputs("usage: installer install <vm-dir> <ipsw-path>\n", stderr) + exit(1) + } + let vmDir = CommandLine.arguments[2] + let ipswPath = CommandLine.arguments[3] + + let diskURL = URL(fileURLWithPath: vmDir).appendingPathComponent("Disk.img") + let auxURL = URL(fileURLWithPath: vmDir).appendingPathComponent("AuxiliaryStorage") + let hwModelURL = URL(fileURLWithPath: vmDir).appendingPathComponent("HardwareModel") + let machineIdURL = URL(fileURLWithPath: vmDir).appendingPathComponent("MachineIdentifier") -// Step 2: Install macOS from IPSW. -func installMacOS(ipswURL: URL) { - print("Loading IPSW...") - VZMacOSRestoreImage.load(from: ipswURL) { result in + let diskSize: Int64 = 72 * 1024 * 1024 * 1024 + let memorySize: UInt64 = 8 * 1024 * 1024 * 1024 + + fputs("Loading IPSW...\n", stderr) + VZMacOSRestoreImage.load(from: URL(fileURLWithPath: ipswPath)) { result in switch result { case .failure(let error): fputs("Failed to load IPSW: \(error)\n", stderr) @@ -83,82 +64,74 @@ func installMacOS(ipswURL: URL) { exit(1) } DispatchQueue.main.async { - doInstall(restoreImage: restoreImage, macOSConfig: macOSConfig) - } - } - } -} - -func doInstall(restoreImage: VZMacOSRestoreImage, macOSConfig: VZMacOSConfigurationRequirements) { - // Create disk image. - let fd = open(diskURL.path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR) - guard fd != -1 else { fputs("Cannot create disk image.\n", stderr); exit(1) } - guard ftruncate(fd, diskSize) == 0 else { fputs("ftruncate failed.\n", stderr); exit(1) } - close(fd) - - // Create platform config. - let platform = VZMacPlatformConfiguration() - platform.auxiliaryStorage = try! VZMacAuxiliaryStorage( - creatingStorageAt: auxURL, - hardwareModel: macOSConfig.hardwareModel, - options: []) - platform.hardwareModel = macOSConfig.hardwareModel - platform.machineIdentifier = VZMacMachineIdentifier() + // Create disk image (sparse). + let fd = open(diskURL.path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR) + guard fd != -1 else { fputs("Cannot create disk image.\n", stderr); exit(1) } + guard ftruncate(fd, diskSize) == 0 else { fputs("ftruncate failed.\n", stderr); exit(1) } + close(fd) - // Save hardware model and machine identifier for future boots. - try! platform.hardwareModel.dataRepresentation.write(to: hwModelURL) - try! platform.machineIdentifier.dataRepresentation.write(to: machineIdURL) + // Create platform config. + let platform = VZMacPlatformConfiguration() + platform.auxiliaryStorage = try! VZMacAuxiliaryStorage( + creatingStorageAt: auxURL, + hardwareModel: macOSConfig.hardwareModel, + options: []) + platform.hardwareModel = macOSConfig.hardwareModel + platform.machineIdentifier = VZMacMachineIdentifier() + try! platform.hardwareModel.dataRepresentation.write(to: hwModelURL) + try! platform.machineIdentifier.dataRepresentation.write(to: machineIdURL) - // Build VM config (minimal — just enough for installation). - let vmConfig = VZVirtualMachineConfiguration() - vmConfig.platform = platform - vmConfig.bootLoader = VZMacOSBootLoader() + // Build VM config (minimal — just enough for installation). + let vmConfig = VZVirtualMachineConfiguration() + vmConfig.platform = platform + vmConfig.bootLoader = VZMacOSBootLoader() - var cpuCount = ProcessInfo.processInfo.processorCount - 1 - cpuCount = max(cpuCount, VZVirtualMachineConfiguration.minimumAllowedCPUCount) - cpuCount = min(cpuCount, VZVirtualMachineConfiguration.maximumAllowedCPUCount) - vmConfig.cpuCount = cpuCount + var cpuCount = ProcessInfo.processInfo.processorCount - 1 + cpuCount = max(cpuCount, VZVirtualMachineConfiguration.minimumAllowedCPUCount) + cpuCount = min(cpuCount, VZVirtualMachineConfiguration.maximumAllowedCPUCount) + vmConfig.cpuCount = cpuCount - var mem = memorySize - mem = max(mem, VZVirtualMachineConfiguration.minimumAllowedMemorySize) - mem = min(mem, VZVirtualMachineConfiguration.maximumAllowedMemorySize) - vmConfig.memorySize = mem + var mem = memorySize + mem = max(mem, VZVirtualMachineConfiguration.minimumAllowedMemorySize) + mem = min(mem, VZVirtualMachineConfiguration.maximumAllowedMemorySize) + vmConfig.memorySize = mem - let gfx = VZMacGraphicsDeviceConfiguration() - gfx.displays = [VZMacGraphicsDisplayConfiguration(widthInPixels: 1920, heightInPixels: 1200, pixelsPerInch: 80)] - vmConfig.graphicsDevices = [gfx] + let gfx = VZMacGraphicsDeviceConfiguration() + gfx.displays = [VZMacGraphicsDisplayConfiguration(widthInPixels: 1920, heightInPixels: 1200, pixelsPerInch: 80)] + vmConfig.graphicsDevices = [gfx] - let disk = try! VZDiskImageStorageDeviceAttachment(url: diskURL, readOnly: false) - vmConfig.storageDevices = [VZVirtioBlockDeviceConfiguration(attachment: disk)] - vmConfig.networkDevices = [] - vmConfig.pointingDevices = [VZMacTrackpadConfiguration()] - vmConfig.keyboards = [VZMacKeyboardConfiguration()] + let disk = try! VZDiskImageStorageDeviceAttachment(url: diskURL, readOnly: false) + vmConfig.storageDevices = [VZVirtioBlockDeviceConfiguration(attachment: disk)] + vmConfig.networkDevices = [] + vmConfig.pointingDevices = [VZMacTrackpadConfiguration()] + vmConfig.keyboards = [VZMacKeyboardConfiguration()] - try! vmConfig.validate() + try! vmConfig.validate() - let vm = VZVirtualMachine(configuration: vmConfig) + let vm = VZVirtualMachine(configuration: vmConfig) - // Install. - print("Starting macOS installation...") - let installer = VZMacOSInstaller(virtualMachine: vm, restoringFromImageAt: restoreImage.url) - installer.install { result in - switch result { - case .success: - print("Installation complete.") - exit(0) - case .failure(let error): - fputs("Installation failed: \(error)\n", stderr) - exit(1) + fputs("Starting macOS installation...\n", stderr) + let installer = VZMacOSInstaller(virtualMachine: vm, restoringFromImageAt: restoreImage.url) + installer.install { result in + switch result { + case .success: + fputs("Installation complete.\n", stderr) + exit(0) + case .failure(let error): + fputs("Installation failed: \(error)\n", stderr) + exit(1) + } + } + _ = installer.progress.observe(\.fractionCompleted, options: [.initial, .new]) { _, change in + let pct = Int((change.newValue ?? 0) * 100) + fputs(" install: \(pct)%\n", stderr) + } + } } } - _ = installer.progress.observe(\.fractionCompleted, options: [.initial, .new]) { _, change in - let pct = Int((change.newValue ?? 0) * 100) - print(" install: \(pct)%") - } -} + RunLoop.main.run() -// Main flow. -downloadIPSW(to: ipswPath) { url in - installMacOS(ipswURL: url) +default: + fputs("unknown mode: \(mode)\n", stderr) + exit(1) } -RunLoop.main.run() |
