Merge nucleic/mellow-dewy-falcon-rjhr into main

This commit is contained in:
2026-08-07 02:30:26 -07:00
parent d64b3f3e9b
commit e7163de22f
9 changed files with 403 additions and 47 deletions
+31 -5
View File
@@ -6,10 +6,16 @@ import Virtualization
public enum ImageBuildStage: Sendable, Equatable {
/// Downloading the IPSW.
case downloadingIPSW(fraction: Double)
/// Reading the restore image and deriving a hardware configuration.
/// Working out which file the operator meant and whether it is usable.
case preparing
/// Inside `VZMacOSRestoreImage.image(from:)`, which reports no progress of
/// its own and is the longest silent stretch of a local-IPSW build.
case loadingRestoreImage
/// Creating the disk, NVRAM, and bundle metadata.
case creatingBundle
case creatingBundle(diskGB: Int)
/// An out-of-band remark about the stage in flight — printed on its own
/// line rather than replacing the status line.
case note(String)
/// `VZMacOSInstaller` is writing macOS onto the disk.
case installing(fraction: Double)
/// First boot; waiting for Setup Assistant, a DHCP lease, and SSH.
@@ -89,7 +95,28 @@ public struct ImageBuilder: Sendable {
let provider = IPSWProvider(downloadDirectory: store.ipswDir)
let restoreImage: VZMacOSRestoreImage
if let ipswPath {
progress?(.downloadingIPSW(fraction: 1.0))
progress?(.preparing)
progress?(.loadingRestoreImage)
// Reading a 21 GB archive's metadata takes a while and the
// framework says nothing while it does. A build that looks frozen
// is the single most-reported symptom of this command, so say out
// loud what the other likely explanation is rather than letting the
// operator guess.
let watchdog = Task {
try? await Task.sleep(for: .seconds(60))
// Cancelling is how a *fast* load ends this task, and a
// cancelled sleep returns rather than throwing past `try?` — so
// without this the note prints on every quick failure, which is
// precisely when it is misleading.
guard !Task.isCancelled else { return }
progress?(
.note(
"still loading — a truncated or partially downloaded .ipsw can block here; "
+ "verify the download completed"))
}
defer { watchdog.cancel() }
restoreImage = try await provider.load(localPath: ipswPath)
} else {
progress?(.downloadingIPSW(fraction: 0))
@@ -100,8 +127,7 @@ public struct ImageBuilder: Sendable {
}
// 2/3. Hardware model and bundle.
progress?(.preparing)
progress?(.creatingBundle)
progress?(.creatingBundle(diskGB: config.guest.diskGB))
let bundle = try await createBundle(name: name, restoreImage: restoreImage, config: config)
// 4. Install.