Skip to content

Add macOS disk resize support#678

Open
balcsida wants to merge 3 commits into
insidegui:mainfrom
balcsida:feat/macos-disk-resize-compact
Open

Add macOS disk resize support#678
balcsida wants to merge 3 commits into
insidegui:mainfrom
balcsida:feat/macos-disk-resize-compact

Conversation

@balcsida
Copy link
Copy Markdown

@balcsida balcsida commented May 23, 2026

What the resize feature does is that when a guest VM starts the next time, before the guest OS boots, VirtualBuddy inspects the guest disk image by temporarily attaching it on the host with hdiutil and reading the partition layout. If it finds an APFS-on-GPT layout, it treats it as a macOS guest disk and checks for locked APFS volumes / FileVault first:

  • If the disk is encrypted, the resize is blocked. I couldn't find a reliable way to resize FileVault-protected APFS offline, even after trying the recovery key in multiple formats. I may revisit this later.
  • If the disk is not encrypted, the disk image is expanded to the requested size. Then diskutil expands the GPT layout and the APFS container to match. If APFS does not pick up the new ceiling on the first pass, which sometimes happens, the resizer applies a small shrink-and-grow nudge to force APFS to recompute the available space.

@balcsida balcsida force-pushed the feat/macos-disk-resize-compact branch 2 times, most recently from f5ac0a5 to 2cfd5fe Compare May 23, 2026 21:28
@balcsida balcsida mentioned this pull request May 23, 2026
@balcsida balcsida force-pushed the feat/macos-disk-resize-compact branch from 2cfd5fe to 5e9a160 Compare May 23, 2026 21:45
Copy link
Copy Markdown
Owner

@insidegui insidegui left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some comments about the code, mostly just nitpicks.

I couldn't test it yet though, as I couldn't figure out how to access the disk resizing feature. At first I thought the resizing arrow icon was a button, but it's just an indicator, and the ellipsis button that gives access to the disk settings remains disabled for the boot disk, even when it indicates that it's resizable.

Image

Comment on lines +115 to 127
public var displayName: String {
switch self {
case .raw:
return "Raw Image"
case .dmg:
return "Disk Image (DMG)"
case .sparse:
return "Sparse Image"
case .asif:
return "Apple Sparse Image Format (ASIF)"
}
}
}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: reformat this to not use an explicit return. There's probably older code in the repo that still does it, but this is more idiomatic in modern Swift:

public var displayName: String {
    switch self {
    case .raw: "Raw Image"
    case .dmg: "Disk Image (DMG)"
    case .sparse: "Sparse Image"
    case .asif: "Apple Sparse Image Format (ASIF)"
    }
}

Comment on lines +152 to 160
public var canBeResized: Bool {
switch format {
case .raw, .sparse:
return true
case .dmg, .asif:
return false
}
}
}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same nitpick about the explicit return :)

return current
}
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is an extension that's all about disk resizing, let's move it into a separate file

if case let VBDiskResizeError.apfsVolumesLocked(container) = error {
let alert = NSAlert()
alert.messageText = "Unlock FileVault to Finish Resizing"
alert.informativeText = "VirtualBuddy enlarged the disk image, but the APFS container \(container) is still locked. Start the guest, sign in to unlock FileVault, then use Disk Utility (or run 'diskutil apfs resizeContainer disk0s2 0') inside the guest to claim the newly added space."
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not requesting a change here, just noting that this is probably something we can make the VirtualBuddyGuest app do automatically in a future development 🤔

alert.runModal()
}
// Log resize errors but don't fail VM start
NSLog("Warning: Failed to resize disk images: \(error)")
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of things:

1 - We don't use NSLog; use a Logger instance if logging is needed
2 - Shouldn't the user be told about the error in the UI, even if it doesn't prevent the VM from continuing startup?

import VirtualCore

struct ManagedDiskImageEditor: View {
@EnvironmentObject var viewModel: VMConfigurationViewModel
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't look like the view is observing any property of the view model, it's using it to call checkFileVaultForDiskImage on the underlying VBVirtualMachine. In general we should try to avoid having the isolated configuration UI components depend on the configuration view model directly, so in this case this property could probably be a let virtualMachine: VBVirtualMachine.

}
.onChange(of: image) { _, newValue in
onSave(newValue)
if isExistingDiskImage && canResize && newValue.size != minimumSize {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't do this right now, but add a // TODO: comment here to extract this slider with confirmation behavior into a separate component 🤓

Comment on lines +149 to +159
if canResize {
return "Boot disk can be expanded, but not shrunk. Choose your size carefully."
} else {
return "Be sure to reserve enough space, since it won't be possible to change the size of the disk later."
}
} else {
return "It's not possible to change the size of an existing storage device."
if canResize {
return "This disk can be expanded to a larger size, but cannot be shrunk."
} else {
return "It's not possible to change the size of an existing storage device."
}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same nitpick about the explicit return

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants